ref: f219ac09f6b7e26d84599401512233d77c1bdb4c
parent: 786f72302f65580ca8d1df2132a7756584539ea0
author: Bjørn Erik Pedersen <[email protected]>
date: Tue Jul 31 05:34:56 EDT 2018
tocss/scss: Improve SCSS project vs themes import resolution Before this commit, only SASS/SCSS components imported from main.scss at first level can be overwritten by homonymous files in projects or over-preceding theme components. This commit fixes that by implementing a custom import resolver which will be tried first. This resolver will make sure that the project/theme hierarchy is always respected. Fixes #5008
--- a/hugolib/resource_chain_test.go
+++ b/hugolib/resource_chain_test.go
@@ -79,6 +79,76 @@
}
+func TestSCSSWithThemeOverrides(t *testing.T) {
+ if !scss.Supports() {
+ t.Skip("Skip SCSS")
+ }
+ assert := require.New(t)
+ workDir, clean, err := createTempDir("hugo-scss-include")
+ assert.NoError(err)
+ defer clean()
+
+ theme := "mytheme"
+ themesDir := filepath.Join(workDir, "themes")
+ themeDirs := filepath.Join(themesDir, theme)
+ v := viper.New()
+ v.Set("workingDir", workDir)
+ v.Set("theme", theme)
+ b := newTestSitesBuilder(t).WithLogger(loggers.NewWarningLogger())
+ b.WithViper(v)
+ b.WithWorkingDir(workDir)
+ // Need to use OS fs for this.
+ b.Fs = hugofs.NewDefault(v)
+
+ fooDir := filepath.Join(workDir, "node_modules", "foo")
+ scssDir := filepath.Join(workDir, "assets", "scss")
+ scssThemeDir := filepath.Join(themeDirs, "assets", "scss")
+ assert.NoError(os.MkdirAll(fooDir, 0777))
+ assert.NoError(os.MkdirAll(filepath.Join(workDir, "content", "sect"), 0777))
+ assert.NoError(os.MkdirAll(filepath.Join(workDir, "data"), 0777))
+ assert.NoError(os.MkdirAll(filepath.Join(workDir, "i18n"), 0777))
+ assert.NoError(os.MkdirAll(filepath.Join(workDir, "layouts", "shortcodes"), 0777))
+ assert.NoError(os.MkdirAll(filepath.Join(workDir, "layouts", "_default"), 0777))
+ assert.NoError(os.MkdirAll(filepath.Join(scssDir, "components"), 0777))
+ assert.NoError(os.MkdirAll(filepath.Join(scssThemeDir, "components"), 0777))
+
+ b.WithSourceFile(filepath.Join(scssThemeDir, "components", "_imports.scss"), `
+@import "moo";
+
+`)
+
+ b.WithSourceFile(filepath.Join(scssThemeDir, "components", "_moo.scss"), `
+$moolor: #fff;
+
+moo {
+ color: $moolor;
+}
+`)
+
+ b.WithSourceFile(filepath.Join(scssThemeDir, "main.scss"), `
+@import "components/imports";
+
+`)
+
+ b.WithSourceFile(filepath.Join(scssDir, "components", "_moo.scss"), `
+$moolor: #ccc;
+
+moo {
+ color: $moolor;
+}
+`)
+
+ b.WithTemplatesAdded("index.html", `
+{{ $cssOpts := (dict "includePaths" (slice "node_modules/foo" ) ) }}
+{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts | minify }}
+T1: {{ $r.Content }}
+`)
+ b.Build(BuildCfg{})
+
+ b.AssertFileContent(filepath.Join(workDir, "public/index.html"), `T1: moo{color:#ccc}`)
+
+}
+
func TestResourceChain(t *testing.T) {
t.Parallel()
--- a/resource/tocss/scss/tocss.go
+++ b/resource/tocss/scss/tocss.go
@@ -26,6 +26,7 @@
"github.com/bep/go-tocss/scss/libsass"
"github.com/bep/go-tocss/tocss"
"github.com/gohugoio/hugo/helpers"
+ "github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/resource"
)
@@ -48,12 +49,62 @@
outName = path.Base(ctx.OutPath)
options := t.options
+ baseDir := path.Dir(ctx.SourcePath)
+ options.to.IncludePaths = t.c.sfs.RealDirs(baseDir)
- options.to.IncludePaths = t.c.sfs.RealDirs(path.Dir(ctx.SourcePath))
-
// Append any workDir relative include paths
for _, ip := range options.from.IncludePaths {
options.to.IncludePaths = append(options.to.IncludePaths, t.c.workFs.RealDirs(filepath.Clean(ip))...)
+ }
+
+ // To allow for overrides of SCSS files anywhere in the project/theme hierarchy, we need
+ // to help libsass revolve the filename by looking in the composite filesystem first.
+ // We add the entry directories for both project and themes to the include paths list, but
+ // that only work for overrides on the top level.
+ options.to.ImportResolver = func(url string, prev string) (newUrl string, body string, resolved bool) {
+ // We get URL paths from LibSASS, but we need file paths.
+ url = filepath.FromSlash(url)
+ prev = filepath.FromSlash(prev)
+
+ var basePath string
+ urlDir := filepath.Dir(url)
+ var prevDir string
+ if prev == "stdin" {
+ prevDir = baseDir
+ } else {
+ prevDir = t.c.sfs.MakePathRelative(filepath.Dir(prev))
+ if prevDir == "" {
+ // Not a member of this filesystem. Let LibSASS handle it.
+ return "", "", false
+ }
+ }
+
+ basePath = filepath.Join(prevDir, urlDir)
+ name := filepath.Base(url)
+
+ // Libsass throws an error in cases where you have several possible candidates.
+ // We make this simpler and pick the first match.
+ var namePatterns []string
+ if strings.Contains(name, ".") {
+ namePatterns = []string{"_%s", "%s"}
+ } else if strings.HasPrefix(name, "_") {
+ namePatterns = []string{"_%s.scss", "_%s.sass"}
+ } else {
+ namePatterns = []string{"_%s.scss", "%s.scss", "_%s.sass", "%s.sass"}
+ }
+
+ for _, namePattern := range namePatterns {
+ filenameToCheck := filepath.Join(basePath, fmt.Sprintf(namePattern, name))
+ fi, err := t.c.sfs.Fs.Stat(filenameToCheck)
+ if err == nil {
+ if fir, ok := fi.(hugofs.RealFilenameInfo); ok {
+ return fir.RealFilename(), "", true
+ }
+ }
+ }
+
+ // Not found, let LibSASS handle it
+ return "", "", false
}
if ctx.InMediaType.SubType == media.SASSType.SubType {