Add importContext option to css.Build, js.Build, css.Sass and css.TailwindCSS

This allows @import statements to be resolved in a set of user provided resources (e.g. from resources.FromString or css.ChromaStyles) before the assets filesystem.

The option also applies to css.PostCSS via the shared import inlining, and css.Sass requires the dartsass transpiler. The import context is part of the transformation cache key.

Fixes #15103

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Bjørn Erik Pedersen
2026-08-03 12:14:54 +02:00
parent 8a468df065
commit 70db201ed4
28 changed files with 697 additions and 110 deletions
+3 -6
View File
@@ -16,13 +16,12 @@ package images
import (
"encoding/hex"
"fmt"
"hash/fnv"
"image/color"
"math"
"slices"
"strings"
"github.com/gohugoio/hugo/common/hstrings"
"slices"
)
type colorGoProvider interface {
@@ -65,10 +64,8 @@ func (c Color) String() string {
// For hashstructure. This struct is used in template func options
// that needs to be able to hash a Color.
// For internal use only.
func (c Color) Hash() (uint64, error) {
h := fnv.New64a()
h.Write([]byte(c.hex))
return h.Sum64(), nil
func (c Color) Key() string {
return c.hex
}
func (c *Color) init() error {
+3 -1
View File
@@ -13,7 +13,9 @@
package internal
import "github.com/gohugoio/hugo/common/hashing"
import (
"github.com/gohugoio/hugo/common/hashing"
)
// ResourceTransformationKey are provided by the different transformation implementations.
// It identifies the transformation (name) and its configuration (elements).
+3
View File
@@ -96,6 +96,9 @@ type ResourceSourceDescriptor struct {
// Delay publishing until either Permalink or RelPermalink is called. Maybe never.
LazyPublish bool
// Whether to include the hash of the source content in the resource key.
IncludeHashInKey bool
// Set when its known up front, else it's resolved from the target filename.
MediaType media.Type
+59 -43
View File
@@ -20,6 +20,8 @@ import (
"slices"
"strings"
"github.com/gohugoio/hashstructure"
"github.com/gohugoio/hugo/common/hashing"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hreflect"
"github.com/gohugoio/hugo/common/paths"
@@ -33,44 +35,56 @@ var _ ResourceFinder = (*Resources)(nil)
// I.e. both pages and images etc.
type Resources []Resource
type resourceMount struct {
R Resources
Base string
Target string
}
func (r resourceMount) Get(namev any) Resource {
name1, err := cast.ToStringE(namev)
if err != nil {
panic(err)
}
isTargetAbs := strings.HasPrefix(r.Target, "/")
if r.Target != "" {
name1 = strings.TrimPrefix(name1, r.Target)
if !isTargetAbs {
name1 = paths.TrimLeading(name1)
}
}
if r.Base != "" && isTargetAbs {
name1 = path.Join(r.Base, name1)
}
for _, res := range r.R {
name2 := res.Name()
if r.Base != "" && !isTargetAbs {
name2 = paths.TrimLeading(strings.TrimPrefix(name2, r.Base))
}
if strings.EqualFold(name1, name2) {
return res
}
}
return nil
}
// Mount mounts the given resources from base to the given target path.
// Note that leading slashes in target marks an absolute path.
// This method is currently only useful in js.Batch.
// This method can be used in any of the template funcs that takes an importContext option, e.g. css.Build.
func (r Resources) Mount(base, target string) ResourceGetter {
return resourceGetterFunc(func(namev any) Resource {
name1, err := cast.ToStringE(namev)
if err != nil {
panic(err)
}
isTargetAbs := strings.HasPrefix(target, "/")
if target != "" {
name1 = strings.TrimPrefix(name1, target)
if !isTargetAbs {
name1 = paths.TrimLeading(name1)
}
}
if base != "" && isTargetAbs {
name1 = path.Join(base, name1)
}
for _, res := range r {
name2 := res.Name()
if base != "" && !isTargetAbs {
name2 = paths.TrimLeading(strings.TrimPrefix(name2, base))
}
if strings.EqualFold(name1, name2) {
return res
}
}
return nil
})
return resourceMount{
R: r,
Base: base,
Target: target,
}
}
type ResourcesProvider interface {
@@ -276,12 +290,6 @@ type StaleInfoResourceGetter interface {
ResourceGetter
}
type resourceGetterFunc func(name any) Resource
func (f resourceGetterFunc) Get(name any) Resource {
return f(name)
}
// ResourceFinder provides methods to find Resources.
// Note that GetRemote (as found in resources.GetRemote) is
// not covered by this interface, as this is only available as a global template function.
@@ -318,6 +326,8 @@ type ResourceFinder interface {
ByType(typ any) Resources
}
var _ hashstructure.Hashable = (*cachedResourceGetter)(nil)
// NewCachedResourceGetter creates a new ResourceGetter from the given objects.
// If multiple objects are provided, they are merged into one where
// the first match wins.
@@ -329,12 +339,19 @@ func NewCachedResourceGetter(os ...any) *cachedResourceGetter {
}
}
hash := hashing.HashUint64(getters)
return &cachedResourceGetter{
cache: hmaps.NewCache[string, Resource](),
delegate: getters,
hash: hash,
}
}
func (c *cachedResourceGetter) Hash() (uint64, error) {
return c.hash, nil
}
type multiResourceGetter []ResourceGetter
func (m multiResourceGetter) Get(name any) Resource {
@@ -354,6 +371,7 @@ var (
type cachedResourceGetter struct {
cache *hmaps.Cache[string, Resource]
delegate ResourceGetter
hash uint64
}
func (c *cachedResourceGetter) Get(name any) Resource {
@@ -390,8 +408,6 @@ func unwrapResourceGetter(v any) (ResourceGetter, bool) {
return vv, true
case ResourcesProvider:
return vv.Resources(), true
case func(name any) Resource:
return resourceGetterFunc(vv), true
default:
vvv, ok := hreflect.ToSliceAny(v)
if !ok {
@@ -304,8 +304,9 @@ func (c *Client) FromOpts(opts Options) (resource.Resource, error) {
}
return c.rs.NewResource(
resources.ResourceSourceDescriptor{
LazyPublish: true,
GroupIdentity: identity.Anonymous, // All usage of this resource are tracked via its string content.
LazyPublish: true,
IncludeHashInKey: !opts.TargetPathHasHash,
GroupIdentity: identity.Anonymous, // All usage of this resource are tracked via its string content.
OpenReadSeekCloser: func() (hugio.ReadSeekCloser, error) {
return newReadSeeker()
},
+1 -1
View File
@@ -201,7 +201,7 @@ func (r *Spec) NewResource(rd ResourceSourceDescriptor) (resource.Resource, erro
h: &resourceHash{},
publishInit: &hsync.OnceMore{},
keyInit: &sync.Once{},
includeHashInKey: isImage,
includeHashInKey: isImage || rd.IncludeHashInKey,
paths: rp,
spec: r,
sd: rd,
@@ -14,6 +14,7 @@
package cssjs
import (
"context"
"crypto/sha256"
"encoding/hex"
"errors"
@@ -30,6 +31,8 @@ import (
"github.com/gohugoio/hugo/common/text"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/resources"
"github.com/gohugoio/hugo/resources/resource"
"github.com/spf13/afero"
)
@@ -46,10 +49,12 @@ type fileOffset struct {
}
type importResolver struct {
ctx context.Context
r io.Reader
inPath string
opts InlineImports
importContext resource.ResourceGetter
contentSeen map[string]bool
dependencyManager identity.Manager
linemap map[int]fileOffset
@@ -57,8 +62,9 @@ type importResolver struct {
logger loggers.Logger
}
func newImportResolver(r io.Reader, inPath string, opts InlineImports, fs afero.Fs, logger loggers.Logger, dependencyManager identity.Manager) *importResolver {
return &importResolver{
func newImportResolver(ctx context.Context, r io.Reader, inPath string, opts InlineImports, fs afero.Fs, logger loggers.Logger, dependencyManager identity.Manager) *importResolver {
imp := &importResolver{
ctx: ctx,
r: r,
dependencyManager: dependencyManager,
inPath: inPath,
@@ -66,6 +72,10 @@ func newImportResolver(r io.Reader, inPath string, opts InlineImports, fs afero.
linemap: make(map[int]fileOffset), contentSeen: make(map[string]bool),
opts: opts,
}
if opts.ImportContext != nil {
imp.importContext = resource.NewCachedResourceGetter(opts.ImportContext)
}
return imp
}
func (imp *importResolver) contentHash(filename string) ([]byte, string) {
@@ -73,9 +83,13 @@ func (imp *importResolver) contentHash(filename string) ([]byte, string) {
if err != nil {
return nil, ""
}
return b, hashBytes(b)
}
func hashBytes(b []byte) string {
h := sha256.New()
h.Write(b)
return b, hex.EncodeToString(h.Sum(nil))
return hex.EncodeToString(h.Sum(nil))
}
func (imp *importResolver) importRecursive(
@@ -105,8 +119,35 @@ func (imp *importResolver) importRecursive(
} else {
path := strings.Trim(strings.TrimPrefix(line, importIdentifier), " \"';")
filename := filepath.Join(basePath, path)
imp.dependencyManager.AddIdentity(identity.CleanStringIdentity(filename))
importContent, hash := imp.contentHash(filename)
var (
importContent []byte
hash string
nestedInPath string
)
if imp.importContext != nil {
// Try first the path as written in the import statement,
// then resolved relative to the importing file.
for _, name := range []string{path, filepath.ToSlash(filename)} {
r := imp.importContext.Get(name)
if r == nil {
continue
}
imp.dependencyManager.AddIdentity(identity.FirstIdentity(r))
s, err := resources.InternalResourceSourceContent(imp.ctx, r)
if err != nil {
return 0, "", err
}
importContent, hash = []byte(s), hashBytes([]byte(s))
nestedInPath = name
break
}
}
if importContent == nil {
imp.dependencyManager.AddIdentity(identity.CleanStringIdentity(filename))
importContent, hash = imp.contentHash(filename)
nestedInPath = filepath.ToSlash(filename)
}
if importContent == nil {
if imp.opts.SkipInlineImportsNotFound {
@@ -135,7 +176,7 @@ func (imp *importResolver) importRecursive(
imp.contentSeen[hash] = true
// Handle recursive imports.
l, nested, err := imp.importRecursive(i+lineNum, string(importContent), filepath.ToSlash(filename))
l, nested, err := imp.importRecursive(i+lineNum, string(importContent), nestedInPath)
if err != nil {
return 0, "", err
}
@@ -14,6 +14,7 @@
package cssjs
import (
"context"
"regexp"
"strings"
"testing"
@@ -21,10 +22,12 @@ import (
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/htesting/hqt"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/resources/resource"
"github.com/gohugoio/hugo/helpers"
"github.com/spf13/afero"
"github.com/spf13/cast"
qt "github.com/frankban/quicktest"
)
@@ -103,6 +106,7 @@ LOCAL_STYLE
@import "e.css";`)
imp := newImportResolver(
t.Context(),
mainStyles,
"styles.css",
InlineImports{},
@@ -130,6 +134,70 @@ E_STYLE`)
})
}
func TestImportResolverImportContext(t *testing.T) {
c := qt.New(t)
fs := afero.NewMemMapFs()
writeFile := func(name, content string) {
c.Assert(afero.WriteFile(fs, name, []byte(content), 0o777), qt.IsNil)
}
// Loses to the import context entry with the same name.
writeFile("css/a.css", "A_STYLE_FS")
writeFile("css/b.css", "B_STYLE_FS")
importContext := testImportContext{
"a.css": "@import \"c.css\";\nA_STYLE",
"c.css": "C_STYLE",
"css/d.css": "D_STYLE",
}
mainStyles := strings.NewReader(`@import "a.css";
@import "b.css";
@import "./d.css";
LOCAL_STYLE`)
imp := newImportResolver(
t.Context(),
mainStyles,
"css/styles.css",
InlineImports{ImportContext: importContext},
fs, loggers.NewDefault(),
identity.NopManager,
)
r, err := imp.resolve()
c.Assert(err, qt.IsNil)
rs := helpers.ReaderToString(r)
result := regexp.MustCompile(`\n+`).ReplaceAllString(rs, "\n")
c.Assert(result, hqt.IsSameString, `C_STYLE
A_STYLE
B_STYLE_FS
D_STYLE
LOCAL_STYLE`)
}
type testImportContext map[string]string
func (g testImportContext) Get(name any) resource.Resource {
s := cast.ToString(name)
if content, found := g[s]; found {
return testImportContextResource{name: s, content: content}
}
return nil
}
type testImportContextResource struct {
resource.Resource
name string
content string
}
func (r testImportContextResource) Name() string { return r.name }
func (r testImportContextResource) Content(context.Context) (any, error) { return r.content, nil }
func BenchmarkImportResolver(b *testing.B) {
c := qt.New(b)
fs := afero.NewMemMapFs()
@@ -161,6 +229,7 @@ LOCAL_STYLE
for b.Loop() {
b.StopTimer()
imp := newImportResolver(
b.Context(),
strings.NewReader(mainStyles),
"styles.css",
InlineImports{},
@@ -87,6 +87,10 @@ type InlineImports struct {
// Note that the inline importer does not process url location or imports with media queries,
// so those will be left as-is even without enabling this option.
SkipInlineImportsNotFound bool
// User provided import context. If set, imports are looked up here first,
// by the path as written in the @import statement, then in the assets filesystem.
ImportContext any
}
// Some of the options from https://github.com/postcss/postcss-cli
@@ -213,6 +217,7 @@ func (t *postcssTransformation) Transform(ctx *resources.ResourceTransformationC
src := ctx.From
imp := newImportResolver(
ctx.Ctx,
ctx.From,
ctx.InPath,
options.InlineImports,
@@ -124,6 +124,7 @@ func (t *tailwindcssTransformation) Transform(ctx *resources.ResourceTransformat
src := ctx.From
imp := newImportResolver(
ctx.Ctx,
ctx.From,
ctx.InPath,
options.InlineImports,
@@ -64,6 +64,45 @@ CSS: {{ $css.Content | safeCSS }}|
b.AssertFileContent("public/index.html", "/*! tailwindcss v4.")
}
// See issue 15103.
func TestTailwindCSSImportContext(t *testing.T) {
t.Parallel()
htesting.SkipSlowTestUnlessCI(t)
files := `
-- hugo.toml --
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
-- assets/css/main.css --
@import "tailwindcss";
@import "foo.css";
@import "bar.css";
-- assets/css/foo.css --
.foo {color: orange;}
-- layouts/home.html --
{{ $foo := resources.FromString "foo.css" ".foo {color: blue;}" }}
{{ $bar := resources.FromString "bar.css" ".bar {color: green;}" }}
{{ $opts := dict "importContext" (slice $foo $bar) }}
{{ $css := resources.Get "css/main.css" | css.TailwindCSS $opts }}
CSS: {{ $css.Content | safeCSS }}|
-- package.json --
{
"devDependencies": {
"@tailwindcss/cli": "^4.1.7",
"tailwindcss": "^4.1.7"
}
}
`
b := hugolib.Test(t, files, hugolib.TestOptOsFs(), hugolib.TestOptWithNpmInstall(), hugolib.TestOptInfo())
// foo.css resolves in the import context before the assets filesystem.
b.AssertFileContent("public/index.html",
".foo {\n color: blue;\n }",
".bar {\n color: green;\n }",
)
}
func TestTailwindCSSNoInlineImportsIssue13719(t *testing.T) {
t.Parallel()
htesting.SkipSlowTestUnlessCI(t)
@@ -42,6 +42,8 @@ func New(fs *filesystems.SourceFilesystem, rs *resources.Spec, cssMode bool) *Cl
}
// Process processes a resource with the user provided options.
// If importContext is not nil, imports are resolved in it first,
// then in the assets filesystem (the same precedence as in js.Batch).
func (c *Client) Process(res resources.ResourceTransformer, opts map[string]any) (resource.Resource, error) {
return res.Transform(
&buildTransformation{c: c, optsm: opts},
@@ -18,10 +18,14 @@ import (
"path"
"path/filepath"
"github.com/evanw/esbuild/pkg/api"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/internal/js/esbuild"
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/resources"
"github.com/gohugoio/hugo/resources/internal"
"github.com/gohugoio/hugo/resources/resource"
)
type buildTransformation struct {
@@ -69,6 +73,29 @@ func (t *buildTransformation) Transform(ctx *resources.ResourceTransformationCtx
opts.MediaType = ctx.InMediaType
opts.Stdin = true
opts.IsCSS = t.c.c.CssMode
var ic resource.ResourceGetter
if opts.ImportContext != nil {
ic = resource.NewCachedResourceGetter(opts.ImportContext)
}
if ic != nil {
resolved := hmaps.NewCache[string, resource.Resource]()
opts.ImportOnResolveFunc = func(imp string, args api.OnResolveArgs) string {
if r := esbuild.ResolveResource(imp, ic); r != nil {
p := esbuild.PrefixHugoVirtual + resources.InternalResourceTargetPath(r)
resolved.Set(p, r)
ctx.DependencyManager.AddIdentity(identity.FirstIdentity(r))
return p
}
return ""
}
opts.ImportOnLoadFunc = func(args api.OnLoadArgs) (string, error) {
if r, found := resolved.Get(args.Path); found {
return resources.InternalResourceSourceContent(ctx.Ctx, r)
}
return "", nil
}
}
_, err = t.c.transform(opts, ctx)
@@ -158,6 +158,10 @@ type Options struct {
// $color: vars.$color;
Vars map[string]any
// User provided import context. If set, imports are looked up here first,
// then in the assets filesystem.
ImportContext any
// Deprecations IDs in this slice will be silenced.
// The IDs can be found in the Dart Sass log output, e.g. "import" in
// WARN Dart Sass: DEPRECATED [import].
@@ -51,6 +51,35 @@ T1: {{ $r.Content }}
b.AssertFileContent("public/index.html", `T1: moo{color:#fff}`)
}
// See issue 15103.
func TestTransformImportContext(t *testing.T) {
t.Parallel()
if !dartsass.Supports() {
t.Skip()
}
files := `
-- hugo.toml --
-- assets/scss/_foo.scss --
body { color: orange; }
-- assets/scss/main.scss --
@import "foo";
@import "bar";
-- layouts/home.html --
{{ $foo := resources.FromString "foo.scss" "body { color: blue; }" }}
{{ $bar := resources.FromString "bar.scss" "@import \"baz\";\nbody { color: green; }" }}
{{ $baz := resources.FromString "baz.scss" "p { color: red; }" }}
{{ $opts := dict "transpiler" "dartsass" "outputStyle" "compressed" "importContext" (slice $foo $bar $baz) }}
{{ $r := resources.Get "scss/main.scss" | css.Sass $opts }}
T1: {{ $r.Content }}
`
b := hugolib.Test(t, files, hugolib.TestOptOsFs())
// foo resolves in the import context before the assets filesystem.
b.AssertFileContent("public/index.html", `T1: body{color:blue}p{color:red}body{color:green}`)
}
func TestTransformImportRegularCSS(t *testing.T) {
t.Parallel()
if !dartsass.Supports() {
@@ -14,6 +14,7 @@
package dartsass
import (
"context"
"fmt"
"io"
"path"
@@ -27,6 +28,7 @@ import (
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/resources"
"github.com/gohugoio/hugo/resources/resource"
"github.com/gohugoio/hugo/resources/internal"
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/sass"
@@ -38,6 +40,10 @@ import (
"github.com/bep/godartsass/v2"
)
// Prefix for canonical URLs of stylesheets resolved in the user provided import context.
// Note: This prefix must be all lower case.
const dartSassImportContextPrefix = "hugoimportcontext:"
// Supports returns whether sass, dart-sass, or dart-sass-embedded is found in $PATH.
func Supports() bool {
if htesting.SupportsAll() {
@@ -76,6 +82,11 @@ func (t *transform) Transform(ctx *resources.ResourceTransformationCtx) error {
filename += t.c.sfs.RealFilename(ctx.SourcePath)
}
var ic resource.ResourceGetter
if opts.ImportContext != nil {
ic = resource.NewCachedResourceGetter(opts.ImportContext)
}
args := godartsass.Args{
URL: filename,
IncludePaths: t.c.sfs.RealDirs(baseDir),
@@ -83,6 +94,8 @@ func (t *transform) Transform(ctx *resources.ResourceTransformationCtx) error {
baseDir: baseDir,
c: t.c,
dependencyManager: ctx.DependencyManager,
importContext: ic,
ctx: ctx.Ctx,
vars: opts.Vars,
},
@@ -132,6 +145,8 @@ type importResolver struct {
baseDir string
c *Client
dependencyManager identity.Manager
importContext resource.ResourceGetter
ctx context.Context
vars map[string]any
}
@@ -140,6 +155,11 @@ func (t importResolver) CanonicalizeURL(url string) (string, error) {
return strings.ToLower(url), nil
}
if r := t.resolveInImportContext(url); r != nil {
t.dependencyManager.AddIdentity(identity.FirstIdentity(r))
return dartSassImportContextPrefix + paths.ToSlashTrimLeading(resources.InternalResourceTargetPath(r)), nil
}
filePath, isURL := paths.UrlStringToFilename(url)
var prevDir string
var pathDir string
@@ -160,21 +180,7 @@ func (t importResolver) CanonicalizeURL(url string) (string, error) {
name := filepath.Base(filePath)
// 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", "_%s.css"}
} else {
namePatterns = []string{
"_%s.scss", "%s.scss",
"_%s.sass", "%s.sass",
"_%s.css", "%s.css",
"%s/_index.scss", "%s/_index.sass",
"%s/index.scss", "%s/index.sass",
}
}
namePatterns := sassNamePatterns(name)
name = strings.TrimPrefix(name, "_")
for _, namePattern := range namePatterns {
@@ -192,21 +198,72 @@ func (t importResolver) CanonicalizeURL(url string) (string, error) {
return "", nil
}
func sassNamePatterns(name string) []string {
if strings.Contains(name, ".") {
return []string{"_%s", "%s"}
}
if strings.HasPrefix(name, "_") {
return []string{"_%s.scss", "_%s.sass", "_%s.css"}
}
return []string{
"_%s.scss", "%s.scss",
"_%s.sass", "%s.sass",
"_%s.css", "%s.css",
"%s/_index.scss", "%s/_index.sass",
"%s/index.scss", "%s/index.sass",
}
}
// resolveInImportContext resolves url in the user provided import context, if any,
// using the same name patterns as for the file system.
func (t importResolver) resolveInImportContext(url string) resource.Resource {
if t.importContext == nil {
return nil
}
url = strings.TrimPrefix(url, dartSassImportContextPrefix)
if r := t.importContext.Get(url); r != nil {
return r
}
dir, name := path.Split(url)
namePatterns := sassNamePatterns(name)
name = strings.TrimPrefix(name, "_")
for _, namePattern := range namePatterns {
if r := t.importContext.Get(path.Join(dir, fmt.Sprintf(namePattern, name))); r != nil {
return r
}
}
return nil
}
func (t importResolver) Load(url string) (godartsass.Import, error) {
if subPath, ok := sass.HugoVarsSubPath(url); ok {
return godartsass.Import{
Content: sass.CreateVarsStyleSheet(sass.TranspilerDart, sass.ResolveVars(t.vars, subPath)),
}, nil
}
if strings.HasPrefix(url, dartSassImportContextPrefix) {
r := t.resolveInImportContext(url)
if r == nil {
return godartsass.Import{}, fmt.Errorf("could not find %q in the import context", url)
}
content, err := resources.InternalResourceSourceContent(t.ctx, r)
return godartsass.Import{Content: content, SourceSyntax: sassSourceSyntax(url)}, err
}
filename, _ := paths.UrlStringToFilename(url)
b, err := afero.ReadFile(hugofs.Os, filename)
sourceSyntax := godartsass.SourceSyntaxSCSS
if strings.HasSuffix(filename, ".sass") {
sourceSyntax = godartsass.SourceSyntaxSASS
} else if strings.HasSuffix(filename, ".css") {
sourceSyntax = godartsass.SourceSyntaxCSS
}
return godartsass.Import{Content: string(b), SourceSyntax: sourceSyntax}, err
return godartsass.Import{Content: string(b), SourceSyntax: sassSourceSyntax(filename)}, err
}
func sassSourceSyntax(name string) godartsass.SourceSyntax {
switch {
case strings.HasSuffix(name, ".sass"):
return godartsass.SourceSyntaxSASS
case strings.HasSuffix(name, ".css"):
return godartsass.SourceSyntaxCSS
default:
return godartsass.SourceSyntaxSCSS
}
}