diff --git a/docs/content/en/hugo-pipes/js.md b/docs/content/en/hugo-pipes/js.md index d03fc1c6d32..b57497d862e 100644 --- a/docs/content/en/hugo-pipes/js.md +++ b/docs/content/en/hugo-pipes/js.md @@ -43,6 +43,9 @@ minify [bool] avoidTDZ {{< new-in "0.78.0" >}} : There is/was a bug in WebKit with severe performance issue with the tracking of TDZ checks in JavaScriptCore. Enabling this flag removes the TDZ and `const` assignment checks and may improve performance of larger JS codebases until the WebKit fix is in widespread use. See https://bugs.webkit.org/show_bug.cgi?id=199866 +inject [slice] {{< new-in "0.81.0" >}} +: This option allows you to automatically replace a global variable with an import from another file. The path names must be relative to `assets`. See https://esbuild.github.io/api/#inject + shims {{< new-in "0.81.0" >}} : This option allows swapping out a component with another. A common use case is to load dependencies like React from a CDN (with _shims_) when in production, but running with the full bundled `node_modules` dependency during development: diff --git a/hugolib/js_test.go b/hugolib/js_test.go index 8ab0b970c04..4bb8bf1778b 100644 --- a/hugolib/js_test.go +++ b/hugolib/js_test.go @@ -187,7 +187,7 @@ path="github.com/gohugoio/hugoTestProjectJSModImports" go 1.15 -require github.com/gohugoio/hugoTestProjectJSModImports v0.8.0 // indirect +require github.com/gohugoio/hugoTestProjectJSModImports v0.9.0 // indirect `) @@ -214,10 +214,12 @@ var Hugo = "Rocks!"; Hello3 from mod2. Date from date-fns: ${today} Hello from lib in the main project Hello5 from mod2. -var myparam = "Hugo Rocks!";`) +var myparam = "Hugo Rocks!"; +shim cwd +`) // React JSX, verify the shimming. - b.AssertFileContent("public/js/like.js", `@v0.8.0/assets/js/shims/react.js + b.AssertFileContent("public/js/like.js", `@v0.9.0/assets/js/shims/react.js module.exports = window.ReactDOM; `) } diff --git a/resources/resource_transformers/js/build.go b/resources/resource_transformers/js/build.go index ee60aa502ec..bd126efdabf 100644 --- a/resources/resource_transformers/js/build.go +++ b/resources/resource_transformers/js/build.go @@ -18,6 +18,7 @@ import ( "io/ioutil" "os" "path" + "path/filepath" "regexp" "strings" @@ -103,6 +104,28 @@ func (t *buildTransformation) Transform(ctx *resources.ResourceTransformationCtx defer os.Remove(buildOptions.Outdir) } + if opts.Inject != nil { + // Resolve the absolute filenames. + for i, ext := range opts.Inject { + impPath := filepath.FromSlash(ext) + if filepath.IsAbs(impPath) { + return errors.Errorf("inject: absolute paths not supported, must be relative to /assets") + } + + m := resolveComponentInAssets(t.c.rs.Assets.Fs, impPath) + + if m == nil { + return errors.Errorf("inject: file %q not found", ext) + } + + opts.Inject[i] = m.Filename() + + } + + buildOptions.Inject = opts.Inject + + } + result := api.Build(buildOptions) if len(result.Errors) > 0 { diff --git a/resources/resource_transformers/js/options.go b/resources/resource_transformers/js/options.go index a65e67ac0a9..921e944d402 100644 --- a/resources/resource_transformers/js/options.go +++ b/resources/resource_transformers/js/options.go @@ -20,6 +20,8 @@ import ( "path/filepath" "strings" + "github.com/spf13/afero" + "github.com/pkg/errors" "github.com/evanw/esbuild/pkg/api" @@ -64,6 +66,11 @@ type Options struct { // External dependencies, e.g. "react". Externals []string + // This option allows you to automatically replace a global variable with an import from another file. + // The filenames must be relative to /assets. + // See https://esbuild.github.io/api/#inject + Inject []string + // User defined symbols. Defines map[string]interface{} @@ -137,6 +144,40 @@ func loaderFromFilename(filename string) api.Loader { return api.LoaderJS } +func resolveComponentInAssets(fs afero.Fs, impPath string) hugofs.FileMeta { + findFirst := func(base string) hugofs.FileMeta { + // This is the most common sub-set of ESBuild's default extensions. + // We assume that imports of JSON, CSS etc. will be using their full + // name with extension. + for _, ext := range []string{".js", ".ts", ".tsx", ".jsx"} { + if fi, err := fs.Stat(base + ext); err == nil { + return fi.(hugofs.FileMetaInfo).Meta() + } + } + + // Not found. + return nil + } + + var m hugofs.FileMeta + + // First the path as is. + fi, err := fs.Stat(impPath) + + if err == nil { + if fi.IsDir() { + m = findFirst(filepath.Join(impPath, "index")) + } else { + m = fi.(hugofs.FileMetaInfo).Meta() + } + } else { + // It may be a regular file imported without an extension. + m = findFirst(impPath) + } + + return m +} + func createBuildPlugins(c *Client, opts Options) ([]api.Plugin, error) { fs := c.rs.Assets @@ -169,36 +210,7 @@ func createBuildPlugins(c *Client, opts Options) ([]api.Plugin, error) { impPath = filepath.Join(relDir, impPath) } - findFirst := func(base string) hugofs.FileMeta { - // This is the most common sub-set of ESBuild's default extensions. - // We assume that imports of JSON, CSS etc. will be using their full - // name with extension. - for _, ext := range []string{".js", ".ts", ".tsx", ".jsx"} { - if fi, err := fs.Fs.Stat(base + ext); err == nil { - return fi.(hugofs.FileMetaInfo).Meta() - } - } - - // Not found. - return nil - } - - var m hugofs.FileMeta - - // First the path as is. - fi, err := fs.Fs.Stat(impPath) - - if err == nil { - if fi.IsDir() { - m = findFirst(filepath.Join(impPath, "index")) - } else { - m = fi.(hugofs.FileMetaInfo).Meta() - } - } else { - // It may be a regular file imported without an extension. - m = findFirst(impPath) - } - // + m := resolveComponentInAssets(fs.Fs, impPath) if m != nil { // Store the source root so we can create a jsconfig.json