Add workaround for regular CSS imports in SCSS
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Mon, 16 Mar 2020 16:49:47 +0000 (17:49 +0100)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Mon, 16 Mar 2020 22:21:44 +0000 (23:21 +0100)
Fixes #7059

hugolib/resource_chain_test.go
resources/resource_transformers/tocss/scss/client.go
resources/resource_transformers/tocss/scss/client_test.go [new file with mode: 0644]
resources/resource_transformers/tocss/scss/tocss.go

index 3b5150deb46f41627647e22cef1aaeb4ce677471..39279b5bcfbc3c20107214999d9340b3f41da6b1 100644 (file)
@@ -87,6 +87,69 @@ T1: {{ $r.Content }}
 
 }
 
+func TestSCSSWithRegularCSSImport(t *testing.T) {
+       if !scss.Supports() {
+               t.Skip("Skip SCSS")
+       }
+       c := qt.New(t)
+       workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-scss-include")
+       c.Assert(err, qt.IsNil)
+       defer clean()
+
+       v := viper.New()
+       v.Set("workingDir", workDir)
+       b := newTestSitesBuilder(t).WithLogger(loggers.NewErrorLogger())
+       // Need to use OS fs for this.
+       b.Fs = hugofs.NewDefault(v)
+       b.WithWorkingDir(workDir)
+       b.WithViper(v)
+
+       scssDir := filepath.Join(workDir, "assets", "scss")
+       c.Assert(os.MkdirAll(filepath.Join(workDir, "content", "sect"), 0777), qt.IsNil)
+       c.Assert(os.MkdirAll(filepath.Join(workDir, "data"), 0777), qt.IsNil)
+       c.Assert(os.MkdirAll(filepath.Join(workDir, "i18n"), 0777), qt.IsNil)
+       c.Assert(os.MkdirAll(filepath.Join(workDir, "layouts", "shortcodes"), 0777), qt.IsNil)
+       c.Assert(os.MkdirAll(filepath.Join(workDir, "layouts", "_default"), 0777), qt.IsNil)
+       c.Assert(os.MkdirAll(filepath.Join(scssDir), 0777), qt.IsNil)
+
+       b.WithSourceFile(filepath.Join(scssDir, "_moo.scss"), `
+$moolor: #fff;
+
+moo {
+  color: $moolor;
+}
+`)
+
+       b.WithSourceFile(filepath.Join(scssDir, "main.scss"), `
+@import "moo";
+@import "regular.css";
+@import "moo";
+@import "another.css";
+
+/* foo */
+`)
+
+       b.WithTemplatesAdded("index.html", `
+{{ $r := resources.Get "scss/main.scss" |  toCSS  }}
+T1: {{ $r.Content | safeHTML }}
+`)
+       b.Build(BuildCfg{})
+
+       b.AssertFileContent(filepath.Join(workDir, "public/index.html"), `
+ T1: moo {
+ color: #fff; }
+
+@import "regular.css";
+moo {
+ color: #fff; }
+
+@import "another.css";
+/* foo */
+        
+`)
+
+}
+
 func TestSCSSWithThemeOverrides(t *testing.T) {
        if !scss.Supports() {
                t.Skip("Skip SCSS")
index 9309e3fe55d749fc3207b3bcae768478f40f5539..85f6e525537f3dfdf5e57d9e6200fcf55f1b78d0 100644 (file)
@@ -14,6 +14,8 @@
 package scss
 
 import (
+       "regexp"
+
        "github.com/gohugoio/hugo/helpers"
        "github.com/gohugoio/hugo/hugolib/filesystems"
        "github.com/gohugoio/hugo/resources"
@@ -72,3 +74,17 @@ func DecodeOptions(m map[string]interface{}) (opts Options, err error) {
 
        return
 }
+
+var (
+       regularCSSImportTo   = regexp.MustCompile(`.*(@import "(.*.css)";).*`)
+       regularCSSImportFrom = regexp.MustCompile(`.*(\/\* HUGO_IMPORT_START (.*) HUGO_IMPORT_END \*\/).*`)
+)
+
+func replaceRegularImportsIn(s string) (string, bool) {
+       replaced := regularCSSImportTo.ReplaceAllString(s, "/* HUGO_IMPORT_START $2 HUGO_IMPORT_END */")
+       return replaced, s != replaced
+}
+
+func replaceRegularImportsOut(s string) string {
+       return regularCSSImportFrom.ReplaceAllString(s, "@import \"$2\";")
+}
diff --git a/resources/resource_transformers/tocss/scss/client_test.go b/resources/resource_transformers/tocss/scss/client_test.go
new file mode 100644 (file)
index 0000000..8cc7913
--- /dev/null
@@ -0,0 +1,49 @@
+// Copyright 2020 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package scss
+
+import (
+       "testing"
+
+       qt "github.com/frankban/quicktest"
+)
+
+func TestReplaceRegularCSSImports(t *testing.T) {
+       c := qt.New(t)
+
+       scssWithImport := `
+       
+@import "moo";
+@import "regular.css";
+@import "moo";
+@import "another.css";
+
+/* foo */`
+
+       scssWithoutImport := `
+@import "moo";
+/* foo */`
+
+       res, replaced := replaceRegularImportsIn(scssWithImport)
+       c.Assert(replaced, qt.Equals, true)
+       c.Assert(res, qt.Equals, "\n\t\n@import \"moo\";\n/* HUGO_IMPORT_START regular.css HUGO_IMPORT_END */\n@import \"moo\";\n/* HUGO_IMPORT_START another.css HUGO_IMPORT_END */\n\n/* foo */")
+
+       res2, replaced2 := replaceRegularImportsIn(scssWithoutImport)
+       c.Assert(replaced2, qt.Equals, false)
+       c.Assert(res2, qt.Equals, scssWithoutImport)
+
+       reverted := replaceRegularImportsOut(res)
+       c.Assert(reverted, qt.Equals, scssWithImport)
+
+}
index a776b9f3bf0bb639cbc1db409e058a35d95b722b..20f0efbb9505412a80208316a554ba8c59649524 100644 (file)
@@ -65,7 +65,6 @@ func (t *toCSSTransformation) Transform(ctx *resources.ResourceTransformationCtx
        // 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)
@@ -170,12 +169,26 @@ func (c *Client) toCSS(options libsass.Options, dst io.Writer, src io.Reader) (l
        }
 
        in := helpers.ReaderToString(src)
+
+       // See https://github.com/gohugoio/hugo/issues/7059
+       // We need to preserver the regular CSS imports. This is by far
+       // a perfect solution, and only works for the main entry file, but
+       // that should cover many use cases, e.g. using SCSS as a preprocessor
+       // for Tailwind.
+       var importsReplaced bool
+       in, importsReplaced = replaceRegularImportsIn(in)
+
        res, err = transpiler.Execute(in)
        if err != nil {
                return res, errors.Wrap(err, "SCSS processing failed")
        }
 
-       _, err = io.WriteString(dst, res.CSS)
+       out := res.CSS
+       if importsReplaced {
+               out = replaceRegularImportsOut(out)
+       }
+
+       _, err = io.WriteString(dst, out)
 
        return res, err
 }