js: Add Inject config option

Fixes #8164
This commit is contained in:
Bjørn Erik Pedersen 2021-01-22 17:07:23 +01:00
parent 241b7483ea
commit 32b86076ee
4 changed files with 73 additions and 33 deletions

View file

@ -43,6 +43,9 @@ minify [bool]
avoidTDZ {{< new-in "0.78.0" >}} 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 : 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" >}} 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: : 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:

View file

@ -187,7 +187,7 @@ path="github.com/gohugoio/hugoTestProjectJSModImports"
go 1.15 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} Hello3 from mod2. Date from date-fns: ${today}
Hello from lib in the main project Hello from lib in the main project
Hello5 from mod2. Hello5 from mod2.
var myparam = "Hugo Rocks!";`) var myparam = "Hugo Rocks!";
shim cwd
`)
// React JSX, verify the shimming. // 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; module.exports = window.ReactDOM;
`) `)
} }

View file

@ -18,6 +18,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path" "path"
"path/filepath"
"regexp" "regexp"
"strings" "strings"
@ -103,6 +104,28 @@ func (t *buildTransformation) Transform(ctx *resources.ResourceTransformationCtx
defer os.Remove(buildOptions.Outdir) 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) result := api.Build(buildOptions)
if len(result.Errors) > 0 { if len(result.Errors) > 0 {

View file

@ -20,6 +20,8 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/spf13/afero"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/evanw/esbuild/pkg/api" "github.com/evanw/esbuild/pkg/api"
@ -64,6 +66,11 @@ type Options struct {
// External dependencies, e.g. "react". // External dependencies, e.g. "react".
Externals []string 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. // User defined symbols.
Defines map[string]interface{} Defines map[string]interface{}
@ -137,6 +144,40 @@ func loaderFromFilename(filename string) api.Loader {
return api.LoaderJS 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) { func createBuildPlugins(c *Client, opts Options) ([]api.Plugin, error) {
fs := c.rs.Assets fs := c.rs.Assets
@ -169,36 +210,7 @@ func createBuildPlugins(c *Client, opts Options) ([]api.Plugin, error) {
impPath = filepath.Join(relDir, impPath) impPath = filepath.Join(relDir, impPath)
} }
findFirst := func(base string) hugofs.FileMeta { m := resolveComponentInAssets(fs.Fs, impPath)
// 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)
}
//
if m != nil { if m != nil {
// Store the source root so we can create a jsconfig.json // Store the source root so we can create a jsconfig.json