Clean up the css related template funcs package structure

Deprecate and move:

* resources.ToCSS => css.SASS
* resources.PostProcess => css.PostProcess
* resources.Babel => js.Babel

Updates #12618
This commit is contained in:
Bjørn Erik Pedersen 2024-06-23 12:10:35 +02:00
parent 1687a9a585
commit eddcd2bac6
7 changed files with 241 additions and 162 deletions

View file

@ -2,17 +2,40 @@ package css
import ( import (
"context" "context"
"errors"
"fmt"
"sync"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/types/css" "github.com/gohugoio/hugo/common/types/css"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/resources"
"github.com/gohugoio/hugo/resources/resource"
"github.com/gohugoio/hugo/resources/resource_transformers/babel"
"github.com/gohugoio/hugo/resources/resource_transformers/postcss"
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/dartsass"
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/scss"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
"github.com/gohugoio/hugo/tpl/internal/resourcehelpers"
"github.com/spf13/cast" "github.com/spf13/cast"
) )
const name = "css" const name = "css"
// Namespace provides template functions for the "css" namespace. // Namespace provides template functions for the "css" namespace.
type Namespace struct{} type Namespace struct {
d *deps.Deps
scssClientLibSass *scss.Client
postcssClient *postcss.Client
babelClient *babel.Client
// The Dart Client requires a os/exec process, so only
// create it if we really need it.
// This is mostly to avoid creating one per site build test.
scssClientDartSassInit sync.Once
scssClientDartSass *dartsass.Client
}
// Quoted returns a string that needs to be quoted in CSS. // Quoted returns a string that needs to be quoted in CSS.
func (ns *Namespace) Quoted(v any) css.QuotedString { func (ns *Namespace) Quoted(v any) css.QuotedString {
@ -26,17 +49,135 @@ func (ns *Namespace) Unquoted(v any) css.UnquotedString {
return css.UnquotedString(s) return css.UnquotedString(s)
} }
// PostCSS processes the given Resource with PostCSS.
func (ns *Namespace) PostCSS(args ...any) (resource.Resource, error) {
if len(args) > 2 {
return nil, errors.New("must not provide more arguments than resource object and options")
}
r, m, err := resourcehelpers.ResolveArgs(args)
if err != nil {
return nil, err
}
return ns.postcssClient.Process(r, m)
}
// Sass processes the given Resource with Sass.
func (ns *Namespace) Sass(args ...any) (resource.Resource, error) {
if len(args) > 2 {
return nil, errors.New("must not provide more arguments than resource object and options")
}
const (
// Transpiler implementation can be controlled from the client by
// setting the 'transpiler' option.
// Default is currently 'libsass', but that may change.
transpilerDart = "dartsass"
transpilerLibSass = "libsass"
)
var (
r resources.ResourceTransformer
m map[string]any
targetPath string
err error
ok bool
transpiler = transpilerLibSass
)
r, targetPath, ok = resourcehelpers.ResolveIfFirstArgIsString(args)
if !ok {
r, m, err = resourcehelpers.ResolveArgs(args)
if err != nil {
return nil, err
}
}
if m != nil {
if t, _, found := maps.LookupEqualFold(m, "transpiler"); found {
switch t {
case transpilerDart, transpilerLibSass:
transpiler = cast.ToString(t)
default:
return nil, fmt.Errorf("unsupported transpiler %q; valid values are %q or %q", t, transpilerLibSass, transpilerDart)
}
}
}
if transpiler == transpilerLibSass {
var options scss.Options
if targetPath != "" {
options.TargetPath = paths.ToSlashTrimLeading(targetPath)
} else if m != nil {
options, err = scss.DecodeOptions(m)
if err != nil {
return nil, err
}
}
return ns.scssClientLibSass.ToCSS(r, options)
}
if m == nil {
m = make(map[string]any)
}
if targetPath != "" {
m["targetPath"] = targetPath
}
client, err := ns.getscssClientDartSass()
if err != nil {
return nil, err
}
return client.ToCSS(r, m)
}
func init() { func init() {
f := func(d *deps.Deps) *internal.TemplateFuncsNamespace { f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
ctx := &Namespace{} scssClient, err := scss.New(d.BaseFs.Assets, d.ResourceSpec)
if err != nil {
panic(err)
}
ctx := &Namespace{
d: d,
scssClientLibSass: scssClient,
postcssClient: postcss.New(d.ResourceSpec),
babelClient: babel.New(d.ResourceSpec),
}
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, Name: name,
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil }, Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
} }
ns.AddMethodMapping(ctx.Sass,
[]string{"toCSS"},
[][2]string{},
)
ns.AddMethodMapping(ctx.PostCSS,
[]string{"postCSS"},
[][2]string{},
)
return ns return ns
} }
internal.AddTemplateFuncsNamespace(f) internal.AddTemplateFuncsNamespace(f)
} }
func (ns *Namespace) getscssClientDartSass() (*dartsass.Client, error) {
var err error
ns.scssClientDartSassInit.Do(func() {
ns.scssClientDartSass, err = dartsass.New(ns.d.BaseFs.Assets, ns.d.ResourceSpec)
if err != nil {
return
}
ns.d.BuildClosers.Add(ns.scssClientDartSass)
})
return ns.scssClientDartSass, err
}

View file

@ -51,6 +51,9 @@ type TemplateFuncsNamespace struct {
// This is the method receiver. // This is the method receiver.
Context func(ctx context.Context, v ...any) (any, error) Context func(ctx context.Context, v ...any) (any, error)
// OnCreated is called when all the namespaces are ready.
OnCreated func(namespaces map[string]any)
// Additional info, aliases and examples, per method name. // Additional info, aliases and examples, per method name.
MethodMappings map[string]TemplateFuncMethodMapping MethodMappings map[string]TemplateFuncMethodMapping
} }

View file

@ -31,6 +31,11 @@ func init() {
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil }, Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
} }
ns.AddMethodMapping(ctx.Babel,
[]string{"babel"},
[][2]string{},
)
return ns return ns
} }

View file

@ -15,9 +15,12 @@
package js package js
import ( import (
"errors"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/resources" "github.com/gohugoio/hugo/resources"
"github.com/gohugoio/hugo/resources/resource" "github.com/gohugoio/hugo/resources/resource"
"github.com/gohugoio/hugo/resources/resource_transformers/babel"
"github.com/gohugoio/hugo/resources/resource_transformers/js" "github.com/gohugoio/hugo/resources/resource_transformers/js"
"github.com/gohugoio/hugo/tpl/internal/resourcehelpers" "github.com/gohugoio/hugo/tpl/internal/resourcehelpers"
) )
@ -29,12 +32,14 @@ func New(deps *deps.Deps) *Namespace {
} }
return &Namespace{ return &Namespace{
client: js.New(deps.BaseFs.Assets, deps.ResourceSpec), client: js.New(deps.BaseFs.Assets, deps.ResourceSpec),
babelClient: babel.New(deps.ResourceSpec),
} }
} }
// Namespace provides template functions for the "js" namespace. // Namespace provides template functions for the "js" namespace.
type Namespace struct { type Namespace struct {
client *js.Client client *js.Client
babelClient *babel.Client
} }
// Build processes the given Resource with ESBuild. // Build processes the given Resource with ESBuild.
@ -62,3 +67,24 @@ func (ns *Namespace) Build(args ...any) (resource.Resource, error) {
return ns.client.Process(r, m) return ns.client.Process(r, m)
} }
// Babel processes the given Resource with Babel.
func (ns *Namespace) Babel(args ...any) (resource.Resource, error) {
if len(args) > 2 {
return nil, errors.New("must not provide more arguments than resource object and options")
}
r, m, err := resourcehelpers.ResolveArgs(args)
if err != nil {
return nil, err
}
var options babel.Options
if m != nil {
options, err = babel.DecodeOptions(m)
if err != nil {
return nil, err
}
}
return ns.babelClient.Process(r, options)
}

View file

@ -17,7 +17,9 @@ import (
"context" "context"
"github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/tpl/css"
"github.com/gohugoio/hugo/tpl/internal" "github.com/gohugoio/hugo/tpl/internal"
"github.com/gohugoio/hugo/tpl/js"
) )
const name = "resources" const name = "resources"
@ -33,6 +35,22 @@ func init() {
ns := &internal.TemplateFuncsNamespace{ ns := &internal.TemplateFuncsNamespace{
Name: name, Name: name,
Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil }, Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
OnCreated: func(m map[string]any) {
for _, v := range m {
switch v := v.(type) {
case *css.Namespace:
ctx.cssNs = v
case *js.Namespace:
ctx.jsNs = v
}
}
if ctx.cssNs == nil {
panic("css namespace not found")
}
if ctx.jsNs == nil {
panic("js namespace not found")
}
},
} }
ns.AddMethodMapping(ctx.Get, ns.AddMethodMapping(ctx.Get,
@ -57,21 +75,6 @@ func init() {
[][2]string{}, [][2]string{},
) )
ns.AddMethodMapping(ctx.ToCSS,
[]string{"toCSS"},
[][2]string{},
)
ns.AddMethodMapping(ctx.PostCSS,
[]string{"postCSS"},
[][2]string{},
)
ns.AddMethodMapping(ctx.Babel,
[]string{"babel"},
[][2]string{},
)
return ns return ns
} }

View file

@ -18,12 +18,12 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"sync"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/maps" "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/tpl/internal/resourcehelpers" "github.com/gohugoio/hugo/tpl/css"
"github.com/gohugoio/hugo/tpl/js"
"github.com/gohugoio/hugo/resources/postpub" "github.com/gohugoio/hugo/resources/postpub"
@ -33,13 +33,9 @@ import (
"github.com/gohugoio/hugo/resources/resource_factories/bundler" "github.com/gohugoio/hugo/resources/resource_factories/bundler"
"github.com/gohugoio/hugo/resources/resource_factories/create" "github.com/gohugoio/hugo/resources/resource_factories/create"
"github.com/gohugoio/hugo/resources/resource_transformers/babel"
"github.com/gohugoio/hugo/resources/resource_transformers/integrity" "github.com/gohugoio/hugo/resources/resource_transformers/integrity"
"github.com/gohugoio/hugo/resources/resource_transformers/minifier" "github.com/gohugoio/hugo/resources/resource_transformers/minifier"
"github.com/gohugoio/hugo/resources/resource_transformers/postcss"
"github.com/gohugoio/hugo/resources/resource_transformers/templates" "github.com/gohugoio/hugo/resources/resource_transformers/templates"
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/dartsass"
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/scss"
"github.com/spf13/cast" "github.com/spf13/cast"
) )
@ -50,11 +46,6 @@ func New(deps *deps.Deps) (*Namespace, error) {
return &Namespace{}, nil return &Namespace{}, nil
} }
scssClient, err := scss.New(deps.BaseFs.Assets, deps.ResourceSpec)
if err != nil {
return nil, err
}
minifyClient, err := minifier.New(deps.ResourceSpec) minifyClient, err := minifier.New(deps.ResourceSpec)
if err != nil { if err != nil {
return nil, err return nil, err
@ -62,14 +53,11 @@ func New(deps *deps.Deps) (*Namespace, error) {
return &Namespace{ return &Namespace{
deps: deps, deps: deps,
scssClientLibSass: scssClient,
createClient: create.New(deps.ResourceSpec), createClient: create.New(deps.ResourceSpec),
bundlerClient: bundler.New(deps.ResourceSpec), bundlerClient: bundler.New(deps.ResourceSpec),
integrityClient: integrity.New(deps.ResourceSpec), integrityClient: integrity.New(deps.ResourceSpec),
minifyClient: minifyClient, minifyClient: minifyClient,
postcssClient: postcss.New(deps.ResourceSpec),
templatesClient: templates.New(deps.ResourceSpec, deps), templatesClient: templates.New(deps.ResourceSpec, deps),
babelClient: babel.New(deps.ResourceSpec),
}, nil }, nil
} }
@ -81,31 +69,14 @@ type Namespace struct {
createClient *create.Client createClient *create.Client
bundlerClient *bundler.Client bundlerClient *bundler.Client
scssClientLibSass *scss.Client
integrityClient *integrity.Client integrityClient *integrity.Client
minifyClient *minifier.Client minifyClient *minifier.Client
postcssClient *postcss.Client
babelClient *babel.Client
templatesClient *templates.Client templatesClient *templates.Client
// The Dart Client requires a os/exec process, so only // We moved some CSS and JS related functions to the css and js package in Hugo 0.128.0.
// create it if we really need it. // Keep this here until the deprecation period is over.
// This is mostly to avoid creating one per site build test. cssNs *css.Namespace
scssClientDartSassInit sync.Once jsNs *js.Namespace
scssClientDartSass *dartsass.Client
}
func (ns *Namespace) getscssClientDartSass() (*dartsass.Client, error) {
var err error
ns.scssClientDartSassInit.Do(func() {
ns.scssClientDartSass, err = dartsass.New(ns.deps.BaseFs.Assets, ns.deps.ResourceSpec)
if err != nil {
return
}
ns.deps.BuildClosers.Add(ns.scssClientDartSass)
})
return ns.scssClientDartSass, err
} }
// Copy copies r to the new targetPath in s. // Copy copies r to the new targetPath in s.
@ -337,89 +308,17 @@ func (ns *Namespace) Minify(r resources.ResourceTransformer) (resource.Resource,
// ToCSS converts the given Resource to CSS. You can optional provide an Options object // ToCSS converts the given Resource to CSS. You can optional provide an Options object
// as second argument. As an option, you can e.g. specify e.g. the target path (string) // as second argument. As an option, you can e.g. specify e.g. the target path (string)
// for the converted CSS resource. // for the converted CSS resource.
// Deprecated: Moved to the css namespace in Hugo 0.128.0.
func (ns *Namespace) ToCSS(args ...any) (resource.Resource, error) { func (ns *Namespace) ToCSS(args ...any) (resource.Resource, error) {
if len(args) > 2 { hugo.Deprecate("resources.ToCSS", "Use css.SASS.", "v0.128.0")
return nil, errors.New("must not provide more arguments than resource object and options") return ns.cssNs.Sass(args...)
}
const (
// Transpiler implementation can be controlled from the client by
// setting the 'transpiler' option.
// Default is currently 'libsass', but that may change.
transpilerDart = "dartsass"
transpilerLibSass = "libsass"
)
var (
r resources.ResourceTransformer
m map[string]any
targetPath string
err error
ok bool
transpiler = transpilerLibSass
)
r, targetPath, ok = resourcehelpers.ResolveIfFirstArgIsString(args)
if !ok {
r, m, err = resourcehelpers.ResolveArgs(args)
if err != nil {
return nil, err
}
}
if m != nil {
if t, _, found := maps.LookupEqualFold(m, "transpiler"); found {
switch t {
case transpilerDart, transpilerLibSass:
transpiler = cast.ToString(t)
default:
return nil, fmt.Errorf("unsupported transpiler %q; valid values are %q or %q", t, transpilerLibSass, transpilerDart)
}
}
}
if transpiler == transpilerLibSass {
var options scss.Options
if targetPath != "" {
options.TargetPath = paths.ToSlashTrimLeading(targetPath)
} else if m != nil {
options, err = scss.DecodeOptions(m)
if err != nil {
return nil, err
}
}
return ns.scssClientLibSass.ToCSS(r, options)
}
if m == nil {
m = make(map[string]any)
}
if targetPath != "" {
m["targetPath"] = targetPath
}
client, err := ns.getscssClientDartSass()
if err != nil {
return nil, err
}
return client.ToCSS(r, m)
} }
// PostCSS processes the given Resource with PostCSS // PostCSS processes the given Resource with PostCSS.
// Deprecated: Moved to the css namespace in Hugo 0.128.0.
func (ns *Namespace) PostCSS(args ...any) (resource.Resource, error) { func (ns *Namespace) PostCSS(args ...any) (resource.Resource, error) {
if len(args) > 2 { hugo.Deprecate("resources.PostCSS", "Use css.PostCSS.", "v0.128.0")
return nil, errors.New("must not provide more arguments than resource object and options") return ns.cssNs.PostCSS(args...)
}
r, m, err := resourcehelpers.ResolveArgs(args)
if err != nil {
return nil, err
}
return ns.postcssClient.Process(r, m)
} }
// PostProcess processes r after the build. // PostProcess processes r after the build.
@ -428,22 +327,8 @@ func (ns *Namespace) PostProcess(r resource.Resource) (postpub.PostPublishedReso
} }
// Babel processes the given Resource with Babel. // Babel processes the given Resource with Babel.
// Deprecated: Moved to the js namespace in Hugo 0.128.0.
func (ns *Namespace) Babel(args ...any) (resource.Resource, error) { func (ns *Namespace) Babel(args ...any) (resource.Resource, error) {
if len(args) > 2 { hugo.Deprecate("resources.Babel", "Use js.Babel.", "v0.128.0")
return nil, errors.New("must not provide more arguments than resource object and options") return ns.jsNs.Babel(args...)
}
r, m, err := resourcehelpers.ResolveArgs(args)
if err != nil {
return nil, err
}
var options babel.Options
if m != nil {
options, err = babel.DecodeOptions(m)
if err != nil {
return nil, err
}
}
return ns.babelClient.Process(r, options)
} }

View file

@ -252,6 +252,9 @@ func newTemplateExecuter(d *deps.Deps) (texttemplate.Executer, map[string]reflec
func createFuncMap(d *deps.Deps) map[string]any { func createFuncMap(d *deps.Deps) map[string]any {
funcMap := template.FuncMap{} funcMap := template.FuncMap{}
nsMap := make(map[string]any)
var onCreated []func(namespaces map[string]any)
// Merge the namespace funcs // Merge the namespace funcs
for _, nsf := range internal.TemplateFuncsNamespaceRegistry { for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
ns := nsf(d) ns := nsf(d)
@ -259,6 +262,11 @@ func createFuncMap(d *deps.Deps) map[string]any {
panic(ns.Name + " is a duplicate template func") panic(ns.Name + " is a duplicate template func")
} }
funcMap[ns.Name] = ns.Context funcMap[ns.Name] = ns.Context
contextV, err := ns.Context(context.Background())
if err != nil {
panic(err)
}
nsMap[ns.Name] = contextV
for _, mm := range ns.MethodMappings { for _, mm := range ns.MethodMappings {
for _, alias := range mm.Aliases { for _, alias := range mm.Aliases {
if _, exists := funcMap[alias]; exists { if _, exists := funcMap[alias]; exists {
@ -267,6 +275,14 @@ func createFuncMap(d *deps.Deps) map[string]any {
funcMap[alias] = mm.Method funcMap[alias] = mm.Method
} }
} }
if ns.OnCreated != nil {
onCreated = append(onCreated, ns.OnCreated)
}
}
for _, f := range onCreated {
f(nsMap)
} }
if d.OverloadedTemplateFuncs != nil { if d.OverloadedTemplateFuncs != nil {