shithub: hugo

Download patch

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 {