hugo/resources/resource_transformers/tocss/dartsass/transform.go

217 lines
5.7 KiB
Go

// Copyright 2024 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 dartsass
import (
"fmt"
"io"
"path"
"path/filepath"
"strings"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/htesting"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/resources"
"github.com/gohugoio/hugo/resources/internal"
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/sass"
"github.com/spf13/afero"
"github.com/gohugoio/hugo/hugofs"
godartsassv1 "github.com/bep/godartsass"
"github.com/bep/godartsass/v2"
)
// Supports returns whether sass, dart-sass, or dart-sass-embedded is found in $PATH.
func Supports() bool {
if htesting.SupportsAll() {
return true
}
return hugo.DartSassBinaryName != ""
}
type transform struct {
optsm map[string]any
c *Client
}
func (t *transform) Key() internal.ResourceTransformationKey {
return internal.NewResourceTransformationKey(transformationName, t.optsm)
}
func (t *transform) Transform(ctx *resources.ResourceTransformationCtx) error {
ctx.OutMediaType = media.Builtin.CSSType
opts, err := decodeOptions(t.optsm)
if err != nil {
return err
}
if opts.TargetPath != "" {
ctx.OutPath = opts.TargetPath
} else {
ctx.ReplaceOutPathExtension(".css")
}
baseDir := path.Dir(ctx.SourcePath)
filename := dartSassStdinPrefix
if ctx.SourcePath != "" {
filename += t.c.sfs.RealFilename(ctx.SourcePath)
}
args := godartsass.Args{
URL: filename,
IncludePaths: t.c.sfs.RealDirs(baseDir),
ImportResolver: importResolver{
baseDir: baseDir,
c: t.c,
dependencyManager: ctx.DependencyManager,
varsStylesheet: godartsass.Import{Content: sass.CreateVarsStyleSheet(sass.TranspilerDart, opts.Vars)},
},
OutputStyle: godartsass.ParseOutputStyle(opts.OutputStyle),
EnableSourceMap: opts.EnableSourceMap,
SourceMapIncludeSources: opts.SourceMapIncludeSources,
}
// Append any workDir relative include paths
for _, ip := range opts.IncludePaths {
info, err := t.c.workFs.Stat(filepath.Clean(ip))
if err == nil {
filename := info.(hugofs.FileMetaInfo).Meta().Filename
args.IncludePaths = append(args.IncludePaths, filename)
}
}
if ctx.InMediaType.SubType == media.Builtin.SASSType.SubType {
args.SourceSyntax = godartsass.SourceSyntaxSASS
}
res, err := t.c.toCSS(args, ctx.From)
if err != nil {
return err
}
out := res.CSS
_, err = io.WriteString(ctx.To, out)
if err != nil {
return err
}
if opts.EnableSourceMap && res.SourceMap != "" {
if err := ctx.PublishSourceMap(res.SourceMap); err != nil {
return err
}
_, err = fmt.Fprintf(ctx.To, "\n\n/*# sourceMappingURL=%s */", path.Base(ctx.OutPath)+".map")
}
return err
}
type importResolver struct {
baseDir string
c *Client
dependencyManager identity.Manager
varsStylesheet godartsass.Import
}
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
if isURL {
var found bool
prevDir, found = t.c.sfs.MakePathRelative(filepath.Dir(filePath), true)
if !found {
// Not a member of this filesystem, let Dart Sass handle it.
return "", nil
}
} else {
prevDir = t.baseDir
pathDir = path.Dir(url)
}
basePath := filepath.Join(prevDir, pathDir)
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",
}
}
name = strings.TrimPrefix(name, "_")
for _, namePattern := range namePatterns {
filenameToCheck := filepath.Join(basePath, fmt.Sprintf(namePattern, name))
fi, err := t.c.sfs.Fs.Stat(filenameToCheck)
if err == nil {
if fim, ok := fi.(hugofs.FileMetaInfo); ok {
t.dependencyManager.AddIdentity(identity.CleanStringIdentity(filenameToCheck))
return "file://" + filepath.ToSlash(fim.Meta().Filename), nil
}
}
}
// Not found, let Dart Sass handle it
return "", nil
}
func (t importResolver) Load(url string) (godartsass.Import, error) {
if url == sass.HugoVarsNamespace {
return t.varsStylesheet, nil
}
filename, _ := paths.UrlToFilename(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
}
type importResolverV1 struct {
godartsass.ImportResolver
}
func (t importResolverV1) Load(url string) (godartsassv1.Import, error) {
res, err := t.ImportResolver.Load(url)
return godartsassv1.Import{Content: res.Content, SourceSyntax: godartsassv1.SourceSyntax(res.SourceSyntax)}, err
}