diff --git a/tpl/css/css.go b/tpl/css/css.go index 51fa1d51854..145cb3aadac 100644 --- a/tpl/css/css.go +++ b/tpl/css/css.go @@ -2,17 +2,40 @@ package css import ( "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/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/resourcehelpers" "github.com/spf13/cast" ) const name = "css" // 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. func (ns *Namespace) Quoted(v any) css.QuotedString { @@ -26,17 +49,135 @@ func (ns *Namespace) Unquoted(v any) css.UnquotedString { 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() { 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{ Name: name, 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 } 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 +} diff --git a/tpl/internal/templatefuncsRegistry.go b/tpl/internal/templatefuncsRegistry.go index fc02a6ef902..1b74bf443a1 100644 --- a/tpl/internal/templatefuncsRegistry.go +++ b/tpl/internal/templatefuncsRegistry.go @@ -51,6 +51,9 @@ type TemplateFuncsNamespace struct { // This is the method receiver. 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. MethodMappings map[string]TemplateFuncMethodMapping } diff --git a/tpl/js/init.go b/tpl/js/init.go index 16e6c7efad1..69d7c7275d6 100644 --- a/tpl/js/init.go +++ b/tpl/js/init.go @@ -31,6 +31,11 @@ func init() { Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil }, } + ns.AddMethodMapping(ctx.Babel, + []string{"babel"}, + [][2]string{}, + ) + return ns } diff --git a/tpl/js/js.go b/tpl/js/js.go index 63a6765328f..c68e0af9272 100644 --- a/tpl/js/js.go +++ b/tpl/js/js.go @@ -15,9 +15,12 @@ package js import ( + "errors" + "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/js" "github.com/gohugoio/hugo/tpl/internal/resourcehelpers" ) @@ -28,13 +31,15 @@ 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. type Namespace struct { - client *js.Client + client *js.Client + babelClient *babel.Client } // 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) } + +// 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) +} diff --git a/tpl/resources/init.go b/tpl/resources/init.go index db51b0287e5..5cea482acc3 100644 --- a/tpl/resources/init.go +++ b/tpl/resources/init.go @@ -17,7 +17,9 @@ import ( "context" "github.com/gohugoio/hugo/deps" + "github.com/gohugoio/hugo/tpl/css" "github.com/gohugoio/hugo/tpl/internal" + "github.com/gohugoio/hugo/tpl/js" ) const name = "resources" @@ -33,6 +35,22 @@ func init() { ns := &internal.TemplateFuncsNamespace{ Name: name, 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, @@ -57,21 +75,6 @@ func init() { [][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 } diff --git a/tpl/resources/resources.go b/tpl/resources/resources.go index 34b4464be27..78340b1ff2b 100644 --- a/tpl/resources/resources.go +++ b/tpl/resources/resources.go @@ -18,12 +18,12 @@ import ( "context" "errors" "fmt" - "sync" + "github.com/gohugoio/hugo/common/hugo" "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" @@ -33,13 +33,9 @@ import ( "github.com/gohugoio/hugo/resources/resource_factories/bundler" "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/minifier" - "github.com/gohugoio/hugo/resources/resource_transformers/postcss" "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" ) @@ -50,26 +46,18 @@ func New(deps *deps.Deps) (*Namespace, error) { return &Namespace{}, nil } - scssClient, err := scss.New(deps.BaseFs.Assets, deps.ResourceSpec) - if err != nil { - return nil, err - } - minifyClient, err := minifier.New(deps.ResourceSpec) if err != nil { return nil, err } return &Namespace{ - deps: deps, - scssClientLibSass: scssClient, - createClient: create.New(deps.ResourceSpec), - bundlerClient: bundler.New(deps.ResourceSpec), - integrityClient: integrity.New(deps.ResourceSpec), - minifyClient: minifyClient, - postcssClient: postcss.New(deps.ResourceSpec), - templatesClient: templates.New(deps.ResourceSpec, deps), - babelClient: babel.New(deps.ResourceSpec), + deps: deps, + createClient: create.New(deps.ResourceSpec), + bundlerClient: bundler.New(deps.ResourceSpec), + integrityClient: integrity.New(deps.ResourceSpec), + minifyClient: minifyClient, + templatesClient: templates.New(deps.ResourceSpec, deps), }, nil } @@ -79,33 +67,16 @@ var _ resource.ResourceFinder = (*Namespace)(nil) type Namespace struct { deps *deps.Deps - createClient *create.Client - bundlerClient *bundler.Client - scssClientLibSass *scss.Client - integrityClient *integrity.Client - minifyClient *minifier.Client - postcssClient *postcss.Client - babelClient *babel.Client - templatesClient *templates.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 -} - -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) - }) + createClient *create.Client + bundlerClient *bundler.Client + integrityClient *integrity.Client + minifyClient *minifier.Client + templatesClient *templates.Client - return ns.scssClientDartSass, err + // We moved some CSS and JS related functions to the css and js package in Hugo 0.128.0. + // Keep this here until the deprecation period is over. + cssNs *css.Namespace + jsNs *js.Namespace } // 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 // as second argument. As an option, you can e.g. specify e.g. the target path (string) // 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) { - 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) + hugo.Deprecate("resources.ToCSS", "Use css.SASS.", "v0.128.0") + return ns.cssNs.Sass(args...) } -// 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) { - 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) + hugo.Deprecate("resources.PostCSS", "Use css.PostCSS.", "v0.128.0") + return ns.cssNs.PostCSS(args...) } // 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. +// Deprecated: Moved to the js namespace in Hugo 0.128.0. 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) + hugo.Deprecate("resources.Babel", "Use js.Babel.", "v0.128.0") + return ns.jsNs.Babel(args...) } diff --git a/tpl/tplimpl/template_funcs.go b/tpl/tplimpl/template_funcs.go index e5ee4d54f80..81527063149 100644 --- a/tpl/tplimpl/template_funcs.go +++ b/tpl/tplimpl/template_funcs.go @@ -252,6 +252,9 @@ func newTemplateExecuter(d *deps.Deps) (texttemplate.Executer, map[string]reflec func createFuncMap(d *deps.Deps) map[string]any { funcMap := template.FuncMap{} + nsMap := make(map[string]any) + var onCreated []func(namespaces map[string]any) + // Merge the namespace funcs for _, nsf := range internal.TemplateFuncsNamespaceRegistry { ns := nsf(d) @@ -259,6 +262,11 @@ func createFuncMap(d *deps.Deps) map[string]any { panic(ns.Name + " is a duplicate template func") } 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 _, alias := range mm.Aliases { if _, exists := funcMap[alias]; exists { @@ -267,6 +275,14 @@ func createFuncMap(d *deps.Deps) map[string]any { funcMap[alias] = mm.Method } } + + if ns.OnCreated != nil { + onCreated = append(onCreated, ns.OnCreated) + } + } + + for _, f := range onCreated { + f(nsMap) } if d.OverloadedTemplateFuncs != nil {