aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <[email protected]>2022-12-19 18:49:02 +0100
committerBjørn Erik Pedersen <[email protected]>2022-12-20 19:36:30 +0100
commit41a080b26877a737e74444f83fe54c46a9c9f6bc (patch)
tree00fa04128dc9eff07e28c0b36dd97d3fbcc58dd9
parent9a215d6950e6705f9109497e9f38cc3844172612 (diff)
downloadhugo-41a080b26877a737e74444f83fe54c46a9c9f6bc.tar.gz
hugo-41a080b26877a737e74444f83fe54c46a9c9f6bc.zip
tocss: Add vars option
This commit adds a new `vars` option to both the Sass transpilers (Dart Sass and Libsass). This means that you can pass a map with key/value pairs to the transpiler: ```handlebars {{ $vars := dict "$color1" "blue" "$color2" "green" "$font_size" "24px" }} {{ $cssOpts := (dict "transpiler" "dartsass" "outputStyle" "compressed" "vars" $vars ) }} {{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts }} ``` And the the variables will be available in the `hugo:vars` namespace. Example usage for Dart Sass: ```scss @use "hugo:vars" as v; p { color: v.$color1; font-size: v.$font_size; } ``` Note that Libsass does not support the `use` keyword, so you need to `import` them as global variables: ```scss @import "hugo:vars"; p { color: $color1; font-size: $font_size; } ``` Hugo will: * Add a missing leading `$` for the variable names if needed. * Wrap the values in `unquote('VALUE')` (Sass built-in) to get proper handling of identifiers vs other strings. This means that you can pull variables directly from e.g. the site config: ```toml [params] [params.sassvars] color1 = "blue" color2 = "green" font_size = "24px" image = "images/hero.jpg" ``` ```handlebars {{ $vars := site.Params.sassvars}} {{ $cssOpts := (dict "transpiler" "dartsass" "outputStyle" "compressed" "vars" $vars ) }} {{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts }} ``` Fixes #10555
-rw-r--r--resources/resource_transformers/tocss/dartsass/client.go6
-rw-r--r--resources/resource_transformers/tocss/dartsass/integration_test.go95
-rw-r--r--resources/resource_transformers/tocss/dartsass/transform.go13
-rw-r--r--resources/resource_transformers/tocss/internal/sass/helpers.go48
-rw-r--r--resources/resource_transformers/tocss/scss/client.go4
-rw-r--r--resources/resource_transformers/tocss/scss/integration_test.go46
-rw-r--r--resources/resource_transformers/tocss/scss/tocss.go7
7 files changed, 218 insertions, 1 deletions
diff --git a/resources/resource_transformers/tocss/dartsass/client.go b/resources/resource_transformers/tocss/dartsass/client.go
index f5f42a6dc..49cafb52d 100644
--- a/resources/resource_transformers/tocss/dartsass/client.go
+++ b/resources/resource_transformers/tocss/dartsass/client.go
@@ -93,6 +93,7 @@ func (c *Client) toCSS(args godartsass.Args, src io.Reader) (godartsass.Result,
var res godartsass.Result
in := helpers.ReaderToString(src)
+
args.Source = in
res, err := c.transpiler.Execute(args)
@@ -130,6 +131,11 @@ type Options struct {
// If enabled, sources will be embedded in the generated source map.
SourceMapIncludeSources bool
+
+ // Vars will be available in 'hugo:vars', e.g:
+ // @use "hugo:vars";
+ // $color: vars.$color;
+ Vars map[string]string
}
func decodeOptions(m map[string]any) (opts Options, err error) {
diff --git a/resources/resource_transformers/tocss/dartsass/integration_test.go b/resources/resource_transformers/tocss/dartsass/integration_test.go
index c127057a5..083cef14f 100644
--- a/resources/resource_transformers/tocss/dartsass/integration_test.go
+++ b/resources/resource_transformers/tocss/dartsass/integration_test.go
@@ -24,6 +24,7 @@ import (
)
func TestTransformIncludePaths(t *testing.T) {
+ t.Parallel()
if !dartsass.Supports() {
t.Skip()
}
@@ -55,6 +56,7 @@ T1: {{ $r.Content }}
}
func TestTransformImportRegularCSS(t *testing.T) {
+ t.Parallel()
if !dartsass.Supports() {
t.Skip()
}
@@ -108,6 +110,7 @@ T1: {{ $r.Content | safeHTML }}
}
func TestTransformThemeOverrides(t *testing.T) {
+ t.Parallel()
if !dartsass.Supports() {
t.Skip()
}
@@ -169,6 +172,7 @@ zoo {
}
func TestTransformLogging(t *testing.T) {
+ t.Parallel()
if !dartsass.Supports() {
t.Skip()
}
@@ -200,6 +204,7 @@ T1: {{ $r.Content }}
}
func TestTransformErrors(t *testing.T) {
+ t.Parallel()
if !dartsass.Supports() {
t.Skip()
}
@@ -271,3 +276,93 @@ T1: {{ $r.Content }}
})
}
+
+func TestOptionVars(t *testing.T) {
+ t.Parallel()
+ if !dartsass.Supports() {
+ t.Skip()
+ }
+
+ files := `
+-- assets/scss/main.scss --
+@use "hugo:vars";
+
+body {
+ body {
+ background: url(vars.$image) no-repeat center/cover;
+ }
+}
+
+p {
+ color: vars.$color1;
+ font-size: vars.$font_size;
+}
+
+b {
+ color: vars.$color2;
+}
+-- layouts/index.html --
+{{ $image := "images/hero.jpg" }}
+{{ $vars := dict "$color1" "blue" "$color2" "green" "font_size" "24px" "image" $image }}
+{{ $cssOpts := (dict "transpiler" "dartsass" "outputStyle" "compressed" "vars" $vars ) }}
+{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts }}
+T1: {{ $r.Content }}
+ `
+
+ b := hugolib.NewIntegrationTestBuilder(
+ hugolib.IntegrationTestConfig{
+ T: t,
+ TxtarString: files,
+ NeedsOsFS: true,
+ }).Build()
+
+ b.AssertFileContent("public/index.html", `T1: body body{background:url(images/hero.jpg) no-repeat center/cover}p{color:blue;font-size:24px}b{color:green}`)
+}
+
+func TestOptionVarsParams(t *testing.T) {
+ t.Parallel()
+ if !dartsass.Supports() {
+ t.Skip()
+ }
+
+ files := `
+-- config.toml --
+[params]
+[params.sassvars]
+color1 = "blue"
+color2 = "green"
+font_size = "24px"
+image = "images/hero.jpg"
+-- assets/scss/main.scss --
+@use "hugo:vars";
+
+body {
+ body {
+ background: url(vars.$image) no-repeat center/cover;
+ }
+}
+
+p {
+ color: vars.$color1;
+ font-size: vars.$font_size;
+}
+
+b {
+ color: vars.$color2;
+}
+-- layouts/index.html --
+{{ $vars := site.Params.sassvars}}
+{{ $cssOpts := (dict "transpiler" "dartsass" "outputStyle" "compressed" "vars" $vars ) }}
+{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts }}
+T1: {{ $r.Content }}
+ `
+
+ b := hugolib.NewIntegrationTestBuilder(
+ hugolib.IntegrationTestConfig{
+ T: t,
+ TxtarString: files,
+ NeedsOsFS: true,
+ }).Build()
+
+ b.AssertFileContent("public/index.html", `T1: body body{background:url(images/hero.jpg) no-repeat center/cover}p{color:blue;font-size:24px}b{color:green}`)
+}
diff --git a/resources/resource_transformers/tocss/dartsass/transform.go b/resources/resource_transformers/tocss/dartsass/transform.go
index 3aca916fc..1a5b81b47 100644
--- a/resources/resource_transformers/tocss/dartsass/transform.go
+++ b/resources/resource_transformers/tocss/dartsass/transform.go
@@ -1,4 +1,4 @@
-// Copyright 2020 The Hugo Authors. All rights reserved.
+// Copyright 2022 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.
@@ -28,6 +28,7 @@ import (
"github.com/gohugoio/hugo/resources"
"github.com/gohugoio/hugo/resources/internal"
+ "github.com/gohugoio/hugo/resources/resource_transformers/tocss/internal/sass"
"github.com/spf13/afero"
@@ -84,6 +85,8 @@ func (t *transform) Transform(ctx *resources.ResourceTransformationCtx) error {
ImportResolver: importResolver{
baseDir: baseDir,
c: t.c,
+
+ varsStylesheet: sass.CreateVarsStyleSheet(opts.Vars),
},
OutputStyle: godartsass.ParseOutputStyle(opts.OutputStyle),
EnableSourceMap: opts.EnableSourceMap,
@@ -128,9 +131,14 @@ func (t *transform) Transform(ctx *resources.ResourceTransformationCtx) error {
type importResolver struct {
baseDir string
c *Client
+
+ varsStylesheet string
}
func (t importResolver) CanonicalizeURL(url string) (string, error) {
+ if url == sass.HugoVarsNamespace {
+ return url, nil
+ }
filePath, isURL := paths.UrlToFilename(url)
var prevDir string
var pathDir string
@@ -177,6 +185,9 @@ func (t importResolver) CanonicalizeURL(url string) (string, error) {
}
func (t importResolver) Load(url string) (string, error) {
+ if url == sass.HugoVarsNamespace {
+ return t.varsStylesheet, nil
+ }
filename, _ := paths.UrlToFilename(url)
b, err := afero.ReadFile(hugofs.Os, filename)
return string(b), err
diff --git a/resources/resource_transformers/tocss/internal/sass/helpers.go b/resources/resource_transformers/tocss/internal/sass/helpers.go
new file mode 100644
index 000000000..8128fd4d7
--- /dev/null
+++ b/resources/resource_transformers/tocss/internal/sass/helpers.go
@@ -0,0 +1,48 @@
+// Copyright 2022 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 sass
+
+import (
+ "fmt"
+ "sort"
+ "strings"
+)
+
+const (
+ HugoVarsNamespace = "hugo:vars"
+)
+
+func CreateVarsStyleSheet(vars map[string]string) string {
+ if vars == nil {
+ return ""
+ }
+ var varsStylesheet string
+
+ var varsSlice []string
+ for k, v := range vars {
+ var prefix string
+ if !strings.HasPrefix(k, "$") {
+ prefix = "$"
+ }
+ // These variables can be a combination of Sass identifiers (e.g. sans-serif), which
+ // should not be quoted, and URLs et, which should be quoted.
+ // unquote() is knowing what to do with each.
+ varsSlice = append(varsSlice, fmt.Sprintf("%s%s: unquote('%s');", prefix, k, v))
+ }
+ sort.Strings(varsSlice)
+ varsStylesheet = strings.Join(varsSlice, "\n")
+
+ return varsStylesheet
+
+}
diff --git a/resources/resource_transformers/tocss/scss/client.go b/resources/resource_transformers/tocss/scss/client.go
index ecaceaa6c..0d027d888 100644
--- a/resources/resource_transformers/tocss/scss/client.go
+++ b/resources/resource_transformers/tocss/scss/client.go
@@ -60,6 +60,10 @@ type Options struct {
// When enabled, Hugo will generate a source map.
EnableSourceMap bool
+
+ // Vars will be available in 'hugo:vars', e.g:
+ // @import "hugo:vars";
+ Vars map[string]string
}
func DecodeOptions(m map[string]any) (opts Options, err error) {
diff --git a/resources/resource_transformers/tocss/scss/integration_test.go b/resources/resource_transformers/tocss/scss/integration_test.go
index 13b664cc7..799c70ee5 100644
--- a/resources/resource_transformers/tocss/scss/integration_test.go
+++ b/resources/resource_transformers/tocss/scss/integration_test.go
@@ -25,6 +25,7 @@ import (
)
func TestTransformIncludePaths(t *testing.T) {
+ t.Parallel()
if !scss.Supports() {
t.Skip()
}
@@ -57,6 +58,7 @@ T1: {{ $r.Content }}
}
func TestTransformImportRegularCSS(t *testing.T) {
+ t.Parallel()
if !scss.Supports() {
t.Skip()
}
@@ -113,6 +115,7 @@ moo {
}
func TestTransformThemeOverrides(t *testing.T) {
+ t.Parallel()
if !scss.Supports() {
t.Skip()
}
@@ -175,6 +178,7 @@ zoo {
}
func TestTransformErrors(t *testing.T) {
+ t.Parallel()
if !scss.Supports() {
t.Skip()
}
@@ -245,3 +249,45 @@ T1: {{ $r.Content }}
})
}
+
+func TestOptionVars(t *testing.T) {
+ t.Parallel()
+ if !scss.Supports() {
+ t.Skip()
+ }
+
+ files := `
+-- assets/scss/main.scss --
+@import "hugo:vars";
+
+body {
+ body {
+ background: url($image) no-repeat center/cover;
+ }
+}
+
+p {
+ color: $color1;
+ font-size: var$font_size;
+}
+
+b {
+ color: $color2;
+}
+-- layouts/index.html --
+{{ $image := "images/hero.jpg" }}
+{{ $vars := dict "$color1" "blue" "$color2" "green" "font_size" "24px" "image" $image }}
+{{ $cssOpts := (dict "transpiler" "libsass" "outputStyle" "compressed" "vars" $vars ) }}
+{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts }}
+T1: {{ $r.Content }}
+ `
+
+ b := hugolib.NewIntegrationTestBuilder(
+ hugolib.IntegrationTestConfig{
+ T: t,
+ TxtarString: files,
+ NeedsOsFS: true,
+ }).Build()
+
+ b.AssertFileContent("public/index.html", `T1: body body{background:url(images/hero.jpg) no-repeat center/cover}p{color:blue;font-size:var 24px}b{color:green}`)
+}
diff --git a/resources/resource_transformers/tocss/scss/tocss.go b/resources/resource_transformers/tocss/scss/tocss.go
index 10bd1f6f8..7e44f327e 100644
--- a/resources/resource_transformers/tocss/scss/tocss.go
+++ b/resources/resource_transformers/tocss/scss/tocss.go
@@ -31,6 +31,7 @@ import (
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/resources"
+ "github.com/gohugoio/hugo/resources/resource_transformers/tocss/internal/sass"
)
// Used in tests. This feature requires Hugo to be built with the extended tag.
@@ -63,11 +64,17 @@ func (t *toCSSTransformation) Transform(ctx *resources.ResourceTransformationCtx
}
}
+ varsStylesheet := sass.CreateVarsStyleSheet(options.from.Vars)
+
// 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) {
+ if url == sass.HugoVarsNamespace {
+ return url, varsStylesheet, true
+ }
+
// We get URL paths from LibSASS, but we need file paths.
url = filepath.FromSlash(url)
prev = filepath.FromSlash(prev)