Files
hugo/tpl/css/build_integration_test.go
T
Bjørn Erik Pedersen e51e761d9c css: Make css.Build's file-loader URLs absolute to web context root
When CSS imports assets via the file loader (fonts, images), the emitted
URLs were relative to the CSS output directory. That broke when the CSS
was inlined into HTML, since browsers then resolved the URLs against the
page rather than the CSS file.

Set esbuild's PublicPath to the CSS output directory joined with the
site base path so URLs work whether the CSS is published as a file or
inlined.

Fixes #14849
2026-05-10 19:08:08 +02:00

734 lines
21 KiB
Go

// Copyright 2026 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 css_test
import (
"strings"
"testing"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/htesting"
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/internal/js/esbuild"
)
func TestCSSBuild(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
-- assets/a/pixel.png --
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
-- assets/css/main.css --
@import "tailwindcss";
@import url('https://example.org/foo.css');
@import "./foo.css";
@import "./bar.css" layer(mylayer);
@import "./bar.css" layer;
@import './baz.css' screen and (min-width: 800px);
@import 'relnodot.css' supports(display: grid);
@import 'relnodotboth.css';
@import '/relnodotboth2.css';
body {
background-image: url("a/pixel.png");
}
.mask1 {
mask-image: url(a/pixel.png);
mask-repeat: no-repeat;
}
-- assets/css/foo.css --
p { background: red; }
-- assets/css/bar.css --
div { background: blue; }
-- assets/css/baz.css --
span { background: green; }
-- assets/css/relnodot.css --
article { background: yellow; }
-- assets/css/relnodotboth.css --
.relnodotbothrelative { background: green; }
-- assets/relnodotboth.css --
.relnodotbothroot { background: blue; }
-- assets/css/relnodotboth2.css --
.relnodotboth2relative { background: green; }
-- assets/relnodotboth2.css --
.relnodotboth2root { background: blue; }
-- layouts/home.html --
{{ with resources.Get "css/main.css" }}
{{ $opts := (dict "minify" true "target" (slice "chrome108" "firefox116" "safari16.4" "edge115" "ios16.4" "opera101") "loaders" (dict ".png" "dataurl") "externals" (slice "tailwindcss"))}}
{{ with . | css.Build $opts }}
<link rel="stylesheet" href="{{ .RelPermalink }}" />
{{ end }}
{{ end }}
`
b := hugolib.Test(t, files, hugolib.TestOptOsFs())
b.AssertFileContent("public/css/main.css",
`-webkit-mask-repeat`, // browser prefix.
"data:image/png;base64", // dataurl loader
`@import"tailwindcss";`, // external
`@import"https://example.org/foo.css";`, // external
"mylayer{div", // imported layer
"@media screen and (min-width:800px){span", // imported media query
"@supports (display: grid){article", // imported supports
"relnodotbothrelative", //@import 'relnodotboth.css'; file exists in both assets and project root.
"relnodotboth2root", //@import '/relnodotboth2.css'; file exists in both assets and project root.
)
}
func TestCSSBuildEdit(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ["taxonomy", "term", "rss"]
-- assets/images/bar.svg --
barsvg
-- assets/images/foo.svg --
foosvg
-- assets/css/main.css --
@import "./foo.css";
@import "./bar.css" layer(mylayer);
.main { background: red; }
-- assets/css/foo.css --
.foo { background: green; }
-- assets/css/bar.css --
.bar { background-image: url("images/bar.svg"); }
-- layouts/all.html --
All. No CSS here.
-- layouts/home.html --
{{ with resources.Get "css/main.css" }}
{{ $opts := (dict "minify" true) }}
{{ with . | css.Build $opts }}
<link rel="stylesheet" href="{{ .RelPermalink }}" />
{{ end }}
{{ end }}
`
b := hugolib.TestRunning(t, files, hugolib.TestOptOsFs())
b.AssertFileContent("public/css/main.css", `.foo{background:green}@layer mylayer{.bar{background-image:url("/css/bar-Y35ORVQM.svg")}}`)
// Edit svg
b.EditFileReplaceAll("assets/images/bar.svg", "barsvg", "newbarsvg").Build()
b.AssertRenderCountPage(1)
b.AssertFileContent("public/css/main.css", `/css/bar-LVHHRPN5.svg`) // new hash.
b.AssertFileContent("public/css/bar-LVHHRPN5.svg", "newbarsvg")
// Edit foo.css
b.EditFileReplaceAll("assets/css/foo.css", "green", "red").Build()
b.AssertRenderCountPage(1)
b.AssertFileContent("public/css/main.css", `.foo{background:red}`) // updated content.
// Edit bar.css
b.EditFileReplaceAll("assets/css/bar.css", "bar.svg", "foo.svg").Build()
b.AssertRenderCountPage(1)
b.AssertFileContent("public/css/main.css", `/css/foo-52JTT5GU.svg`)
b.AssertFileContent("public/css/foo-52JTT5GU.svg", "foosvg")
// Edit main.css
b.EditFileReplaceAll("assets/css/main.css", "red", "blue").Build()
b.AssertRenderCountPage(1)
b.AssertFileContent("public/css/main.css", `.main{background:#00f}`) // updated content, blue gets minified to #00f.
}
func TestCSSBuildEditOptions(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ["taxonomy", "term", "rss"]
disableLiveReload = true
-- assets/css/main.css --
body {
background: red;
}
-- layouts/home.html --
Home.
{{ with resources.Get "css/main.css" }}
{{ $opts := dict "minify" false }}
{{ with . | css.Build $opts }}
<link rel="stylesheet" href="{{ .RelPermalink }}" />
{{ end }}
{{ end }}
`
b := hugolib.TestRunning(t, files, hugolib.TestOptOsFs())
b.AssertFileContent("public/css/main.css", ` background: red;`)
b.EditFileReplaceAll("layouts/home.html", `"minify" false`, `"minify" true`).Build()
b.AssertFileContent("public/css/main.css", `{background:red}`)
}
func TestCSSBuildEditOptionsMultiple(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ["taxonomy", "term", "rss"]
disableLiveReload = true
-- assets/css/main.css --
body {
background: red;
}
-- layouts/_partials/css.html --
{{ with resources.Get "css/main.css" }}
{{ $opts := dict "minify" false }}
{{ with . | css.Build $opts }}
<link rel="stylesheet" href="{{ .RelPermalink }}" />
{{ end }}
{{ end }}
-- layouts/all.html --
All. {{ partial "css.html" . }}
-- content/p1.md --
-- content/p2.md --
-- content/p3.md --
`
b := hugolib.TestRunning(t, files, hugolib.TestOptOsFs())
for range 3 {
b.AssertFileContent("public/css/main.css", ` background: red;`)
b.EditFileReplaceAll("layouts/_partials/css.html", `"minify" false`, `"minify" true`).Build()
b.AssertFileContent("public/css/main.css", `{background:red}`)
b.EditFileReplaceAll("layouts/_partials/css.html", `"minify" true`, `"minify" false`).Build()
}
}
func TestCSSBuildSourceMaps(t *testing.T) {
t.Parallel()
filesTemplate := `
-- hugo.toml --
-- assets/a/pixel.png --
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
-- assets/css/main.css --
@import "./foo.css";
@import "./bar.css" layer(mylayer);
@import "./bar.css" layer;
@import './baz.css' screen and (min-width: 800px);
.main {
background-color: red;
}
-- assets/css/foo.css --
p { background: red; }
-- assets/css/bar.css --
div { background: blue; }
-- assets/css/baz.css --
span { background: green; }
-- assets/css/qux.css --
article { background: yellow; }
-- layouts/home.html --
{{ with resources.Get "css/main.css" }}
{{ $opts := (dict "minify" MINIFY "sourceMap" "SOURCE_MAP" "sourcesContent" SOURCES_CONTENT "loaders" (dict ".png" "dataurl"))}}
{{ with . | css.Build $opts }}
<link rel="stylesheet" href="{{ .RelPermalink }}" />
{{ end }}
{{ end }}
`
var (
r *strings.Replacer
files string
b *hugolib.IntegrationTestBuilder
)
for _, minify := range []string{"true", "false"} {
r = strings.NewReplacer(
"SOURCE_MAP", "linked",
"SOURCES_CONTENT", "true",
"MINIFY", minify,
)
files = r.Replace(filesTemplate)
b = hugolib.Test(t, files, hugolib.TestOptOsFs())
b.AssertFileContent("public/css/main.css", "/*# sourceMappingURL=main.css.map */")
b.AssertFileContent("public/css/main.css.map",
`"sourcesContent":["`,
`"mappings":"`,
)
sources := esbuild.SourcesFromSourceMap(b.FileContent("public/css/main.css.map"))
// main.css + foo.css + bar.css + baz.css = 4 sources.
b.Assert(len(sources), qt.Equals, 4)
}
r = strings.NewReplacer(
"SOURCE_MAP", "external",
"SOURCES_CONTENT", "true",
"MINIFY", "false",
)
files = r.Replace(filesTemplate)
b = hugolib.Test(t, files, hugolib.TestOptOsFs())
b.AssertFileContent("public/css/main.css", "! sourceMappingURL")
b.AssertFileContent("public/css/main.css.map",
`"sourcesContent":["p { background: red; }"`,
"AAAA;AAAI,cAAY;AAAK",
)
r = strings.NewReplacer(
"SOURCE_MAP", "external",
"SOURCES_CONTENT", "false",
"MINIFY", "false",
)
files = r.Replace(filesTemplate)
b = hugolib.Test(t, files, hugolib.TestOptOsFs())
b.AssertFileContent("public/css/main.css.map",
`"sourcesContent":null,"`,
"AAAA;AAAI,cAAY;AAAK",
)
r = strings.NewReplacer(
"SOURCE_MAP", "inline",
"SOURCES_CONTENT", "false",
"MINIFY", "false",
)
files = r.Replace(filesTemplate)
b = hugolib.Test(t, files, hugolib.TestOptOsFs())
b.AssertFileExists("public/css/main.css.map", false)
b.AssertFileContent("public/css/main.css", "sourceMappingURL=data:application/json;base64,")
}
func TestCSSBuildMultihost(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ["taxonomy", "term"]
defaultContentLanguage = "en"
defaultContentLanguageInSubDir = true
[languages]
[languages.en]
baseURL = "https://example.en"
weight = 1
[languages.fr]
baseURL = "https://example.fr"
weight = 2
-- assets/a/pixel.png --
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
-- assets/css/main.css --
body {
background-image: url("a/pixel.png");
color: red;
}
-- layouts/home.html --
{{ with resources.Get "css/main.css" }}
{{ with . | css.Build (dict "minify" true) }}
CSS: {{ .RelPermalink }}|{{ .Content }}
{{ end }}
{{ end }}
`
b := hugolib.Test(t, files, hugolib.TestOptOsFs())
for _, lang := range []string{"en", "fr"} {
b.AssertFileContent("public/"+lang+"/css/main.css", `/css/pixel-NJRUOINY.png`)
b.AssertFileExists("public/"+lang+"/css/pixel-NJRUOINY.png", true)
}
}
func TestCSSBuildLoadersDefault(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
-- assets/a/pixel.png --
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
-- static/b/issue14619.png --
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
-- assets/css/main.css --
body {
background-image: url("a/pixel.png");
}
div {
background-image: url("static/b/issue14619.png");
}
-- layouts/home.html --
{{ with resources.Get "css/main.css" }}
{{ with . | css.Build (dict "minify" true) }}
<link rel="stylesheet" href="{{ .RelPermalink }}" />
{{ end }}
{{ end }}
`
b := hugolib.Test(t, files, hugolib.TestOptOsFs())
b.AssertFileContent("public/css/main.css", `/css/pixel-NJRUOINY.png`)
b.AssertFileExists("public/css/pixel-NJRUOINY.png", true)
b.AssertFileContent("public/css/main.css", `url("/css/issue14619-NJRUOINY.png")`)
b.AssertFileExists("public/css/issue14619-NJRUOINY.png", true)
}
// Issue #14849
func TestCSSBuildFileLoaderURLRelativeToWebContextRoot(t *testing.T) {
t.Parallel()
filesTemplate := `
-- hugo.toml --
BASEURL
disableKinds = ['rss','section','sitemap','taxonomy','term']
-- assets/css/main.css --
@import "components/header.css";
@import "/fonts/comic_neue/fonts.css";
body { color: #222; }
-- assets/css/components/header.css --
header { border-bottom: 1px solid #222; }
-- assets/fonts/comic_neue/fonts.css --
@font-face {
font-family: 'Comic Neue';
src: url(ComicNeue-Regular.ttf) format('truetype');
}
-- assets/fonts/comic_neue/ComicNeue-Regular.ttf --
fakefontdata
-- layouts/home.html --
{{ with resources.Get "css/main.css" }}
{{ with . | css.Build (dict "minify" true) }}
INLINE: <style>{{ .Content | safeCSS }}</style>
LINKED: <link rel="stylesheet" href="{{ .RelPermalink }}" />
{{ end }}
{{ end }}
`
// Default baseURL: URLs in inlined CSS must be resolvable from any page,
// not just relative to the (non-existent) /css/main.css location.
files := strings.ReplaceAll(filesTemplate, "BASEURL", "")
b := hugolib.Test(t, files, hugolib.TestOptOsFs())
b.AssertFileContent("public/index.html", `url("/css/ComicNeue-Regular-`)
b.AssertFileContent("public/css/main.css", `url("/css/ComicNeue-Regular-`)
// baseURL with a subpath: URLs must include that subpath.
files = strings.ReplaceAll(filesTemplate, "BASEURL", `baseURL = "https://example.org/mysite/"`)
b = hugolib.Test(t, files, hugolib.TestOptOsFs())
b.AssertFileContent("public/index.html", `url("/mysite/css/ComicNeue-Regular-`)
b.AssertFileContent("public/css/main.css", `url("/mysite/css/ComicNeue-Regular-`)
}
// Issue #14849
func TestCSSBuildFileLoaderURLRelativeToWebContextRootMultihost(t *testing.T) {
t.Parallel()
// The CSS is built once for all hosts. The deepest base path among the
// hosts is baked into the file-loader URLs so the URL is reachable on every
// host, and the artifact is published at a per-host compensated path so the
// URL resolves correctly on each host. EN_WEIGHT/FR_WEIGHT swap which host
// renders first, to verify the result is independent of build order.
filesTemplate := `
-- hugo.toml --
defaultContentLanguage = "en"
defaultContentLanguageInSubdir = false
disableKinds = ['rss','section','sitemap','taxonomy','term']
[languages]
[languages.en]
baseURL = "https://example.com/docs/"
weight = EN_WEIGHT
[languages.fr]
baseURL = "https://example.fr/"
weight = FR_WEIGHT
-- assets/css/main.css --
@import "/fonts/comic_neue/fonts.css";
body { color: #222; }
-- assets/fonts/comic_neue/fonts.css --
@font-face {
font-family: 'Comic Neue';
src: url(ComicNeue-Regular.ttf) format('truetype');
}
-- assets/fonts/comic_neue/ComicNeue-Regular.ttf --
fakefontdata
-- layouts/home.html --
{{ with resources.Get "css/main.css" }}
{{ with . | css.Build (dict "minify" true) }}
INLINE: <style>{{ .Content | safeCSS }}</style>
LINKED: <link rel="stylesheet" href="{{ .RelPermalink }}" />
{{ end }}
{{ end }}
`
assertResult := func(t *testing.T, b *hugolib.IntegrationTestBuilder) {
t.Helper()
b.Assert(b.H.Conf.IsMultihost(), qt.Equals, true)
b.AssertFileContent("public/en/index.html", `url("/docs/css/ComicNeue-Regular-`)
b.AssertFileContent("public/en/css/main.css", `url("/docs/css/ComicNeue-Regular-`)
b.AssertFileContent("public/fr/index.html", `url("/docs/css/ComicNeue-Regular-`)
b.AssertFileContent("public/fr/css/main.css", `url("/docs/css/ComicNeue-Regular-`)
b.AssertPublishDir("en/css/ComicNeue-Regular-UA4ODE7N.ttf")
b.AssertPublishDir("fr/docs/css/ComicNeue-Regular-UA4ODE7N.ttf")
}
t.Run("en first", func(t *testing.T) {
t.Parallel()
files := strings.NewReplacer("EN_WEIGHT", "10", "FR_WEIGHT", "20").Replace(filesTemplate)
assertResult(t, hugolib.Test(t, files, hugolib.TestOptOsFs()))
})
t.Run("fr first", func(t *testing.T) {
t.Parallel()
files := strings.NewReplacer("EN_WEIGHT", "20", "FR_WEIGHT", "10").Replace(filesTemplate)
assertResult(t, hugolib.Test(t, files, hugolib.TestOptOsFs()))
})
}
// Issue #14623
func TestCSSBuildLoadersPartial(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
-- layouts/home.html --
{{ $opts := dict "loaders" (dict ".svg" "dataurl") }}
{{ with resources.Get "css/main.css" | css.Build $opts }}
<link rel="stylesheet" href="{{ .RelPermalink }}">
{{ end }}
-- assets/css/main.css --
body { background-image: url('images/pixel.png'); }
div { background-image: url('images/foo.svg'); }
-- assets/images/pixel.png --
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
-- assets/images/foo.svg --
SVG file.
`
b := hugolib.Test(t, files, hugolib.TestOptOsFs())
b.AssertFileExists("public/css/pixel-NJRUOINY.png", true)
b.AssertFileContent("public/css/main.css", `url(data:image/svg`) // svg should be inlined as dataurl.
b.AssertFileContent("public/css/main.css", `pixel-NJRUOINY.png`) // png should be referenced as a hashed file, not inlined.
}
func TestCSSBuildBootstrapFromNPM(t *testing.T) {
htesting.SkipSlowTestUnlessCI(t)
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ["taxonomy", "term"]
-- assets/css/main.css --
@import "bootstrap";
-- package.json --
{
"devDependencies": {
"bootstrap": "5.3.8"
}
}
-- layouts/home.html --
{{ with resources.Get "css/main.css" }}
{{ with . | css.Build (dict "minify" true) }}
CSS size: {{ .Content | len }}|{{ .RelPermalink }}
{{ end }}
{{ end }}
`
b := hugolib.Test(t, files, hugolib.TestOptOsFs(), hugolib.TestOptWithNpmInstall())
b.AssertFileContent("public/css/main.css", `--bs-indigo: #6610f2;`)
}
func TestCSSBuildVars(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
-- assets/css/main.css --
@import "hugo:vars";
body {
background-color: var(--primary-color);
font-size: var(--font-size);
color: var(--text-color);
}
-- layouts/home.html --
{{ with resources.Get "css/main.css" }}
{{ $opts := dict
"vars" (dict "primary-color" "blue" "font-size" "24px" "text-color" "#333" "--already-prefixed" "red")
}}
{{ with . | css.Build $opts }}
<link rel="stylesheet" href="{{ .RelPermalink }}" />
{{ end }}
{{ end }}
`
b := hugolib.Test(t, files, hugolib.TestOptOsFs())
b.AssertFileContent("public/css/main.css",
"--primary-color: blue;",
"--font-size: 24px;",
"--text-color: #333;",
"--already-prefixed: red;",
"background-color: var(--primary-color)",
)
}
func TestCSSBuildVarsNestedIssue14705(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
-- assets/css/main.css --
@import "hugo:vars";
@import "hugo:vars/mobile" (max-width: 650px);
body {
background-color: var(--primary-color);
}
-- layouts/home.html --
{{ with resources.Get "css/main.css" }}
{{ $opts := dict
"vars" (dict "primary-color" "blue" "mobile" (dict "primary-color" "red" "font-size" "12px"))
}}
{{ with . | css.Build $opts }}
<link rel="stylesheet" href="{{ .RelPermalink }}" />
{{ end }}
{{ end }}
`
b := hugolib.Test(t, files, hugolib.TestOptOsFs())
b.AssertFileContent("public/css/main.css",
"--primary-color: blue;",
"@media (max-width: 650px)",
"--primary-color: red;",
"! --mobile:",
"--font-size: 12px;",
"background-color: var(--primary-color)",
)
}
func TestCSSBuildVarsEmpty(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
-- assets/css/main.css --
@import "hugo:vars";
body {
background-color: red;
}
-- layouts/home.html --
{{ with resources.Get "css/main.css" }}
{{ with . | css.Build }}
<link rel="stylesheet" href="{{ .RelPermalink }}" />
{{ end }}
{{ end }}
`
b := hugolib.Test(t, files, hugolib.TestOptOsFs())
b.AssertFileContent("public/css/main.css", "background-color: red;")
}
func TestCSSBuildVarsNestedUpperCase(t *testing.T) {
t.Parallel()
filesTemplate := `
-- hugo.toml --
-- assets/css/main.css --
@import "hugo:vars";
@import "hugo:vars/MOBILE1" (max-width: 650px);
body {
background-color: var(--primary-color);
}
-- layouts/home.html --
{{ with resources.Get "css/main.css" }}
{{ $opts := dict
"vars" (dict "primary-color" "blue" "MOBILE2" (dict "primary-color" "red" "font-size" "12px" "MixedCaseKey" "value"))
}}
{{ with . | css.Build $opts }}
<link rel="stylesheet" href="{{ .RelPermalink }}" />
{{ end }}
{{ end }}
`
files := strings.ReplaceAll(filesTemplate, "MOBILE1", "Mobile")
files = strings.ReplaceAll(files, "MOBILE2", "mobile")
b := hugolib.Test(t, files, hugolib.TestOptOsFs())
b.AssertFileContent("public/css/main.css", "primary-color: red;")
files = strings.ReplaceAll(filesTemplate, "MOBILE1", "mobile")
files = strings.ReplaceAll(files, "MOBILE2", "MobilE")
b = hugolib.Test(t, files, hugolib.TestOptOsFs())
b.AssertFileContent("public/css/main.css", "primary-color: red;")
}
func TestCSSBuildVarsQuoted(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
-- assets/css/main.css --
@import "hugo:vars";
body {
font-family: var(--font-family);
color: var(--brand-color);
}
-- layouts/home.html --
{{ with resources.Get "css/main.css" }}
{{ $opts := dict
"vars" (dict "font-family" (css.Quoted "Arial, sans-serif") "brand-color" "hsl(0, 0%, 20%)")
}}
{{ with . | css.Build $opts }}
<link rel="stylesheet" href="{{ .RelPermalink }}" />
{{ end }}
{{ end }}
`
b := hugolib.Test(t, files, hugolib.TestOptOsFs())
b.AssertFileContent("public/css/main.css",
`--font-family: "Arial, sans-serif";`,
"--brand-color: hsl(0, 0%, 20%);",
)
}
func TestCSSBuildVarsFromParams(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
[params.styles]
primary-color = "blue"
font-size = "24px"
text-color = "#333"
-- assets/css/main.css --
@import "hugo:vars";
-- assets/css/main.css --
@import "hugo:vars";
body {
background-color: var(--primary-color);
font-size: var(--font-size);
color: var(--text-color);
}
-- layouts/home.html --
{{ with resources.Get "css/main.css" }}
{{ $opts := dict
"vars" site.Params.styles
}}
{{ with . | css.Build $opts }}
<link rel="stylesheet" href="{{ .RelPermalink }}" />
{{ end }}
{{ end }}
`
b := hugolib.Test(t, files, hugolib.TestOptOsFs())
b.AssertFileContent("public/css/main.css",
"--primary-color: blue;",
"--font-size: 24px;",
"--text-color: #333;",
)
}