From e19a046c4be9b0654884259b9df94f41561e4fc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Thu, 21 Jan 2021 16:52:32 +0100 Subject: [PATCH] js: Add Shims option This commit adds a new `shims` option to `js.Build` that allows swapping out a component with another. Fixes #8165 --- docs/content/en/hugo-pipes/js.md | 60 +++++++++---------- hugolib/js_test.go | 7 ++- resources/resource_transformers/js/options.go | 14 ++++- 3 files changed, 46 insertions(+), 35 deletions(-) diff --git a/docs/content/en/hugo-pipes/js.md b/docs/content/en/hugo-pipes/js.md index a34454a93..d03fc1c6d 100644 --- a/docs/content/en/hugo-pipes/js.md +++ b/docs/content/en/hugo-pipes/js.md @@ -43,19 +43,42 @@ 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 +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: + +``` +{{ $shims := dict "react" "js/shims/react.js" "react-dom" "js/shims/react-dom.js" }} +{{ $js = $js | js.Build dict "shims" $shims }} +``` + +The _shim_ files may look like these: + +```js +// js/shims/react.js +module.exports = window.React; +``` + +```js +// js/shims/react-dom.js +module.exports = window.ReactDOM; +``` + + +With the above, these imports should work in both scenarios: + +```js +import * as React from 'react' +import * as ReactDOM from 'react-dom'; +``` + target [string] : The language target. One of: `es5`, `es2015`, `es2016`, `es2017`, `es2018`, `es2019`, `es2020` or `esnext`. Default is `esnext`. externals [slice] -: External dependencies. If a dependency should not be included in the bundle (Ex. library loaded from a CDN.), it should be listed here. +: External dependencies. Use this to trim dependencies you know will never be executed. See https://esbuild.github.io/api/#external -```go-html-template -{{ $externals := slice "react" "react-dom" }} -``` - -> Marking a package as external doesn't imply that the library can be loaded from a CDN. It simply tells Hugo not to expand/include the package in the JS file. defines [map] : Allow to define a set of string replacement to be performed when building. Should be a map where each key is to be replaced by its value. @@ -145,29 +168,4 @@ Or with options: ``` -#### Shimming a JS library -It's a common practice to load external libraries using a content delivery network (CDN) rather than importing all packages in a single JS file. To load scripts from a CDN with Hugo, you'll need to shim the libraries as follows. In this example, `react` and `react-dom` will be shimmed. - -First, add React and ReactDOM [CDN script tags](https://reactjs.org/docs/add-react-to-a-website.html#tip-minify-javascript-for-production) in your HTML template files. Then create `assets/js/shims/react.js` and `assets/js/shims/react-dom.js` with the following contents: -```js -// In assets/js/shims/react.js -module.exports = window.React; - -// In assets/js/shims/react-dom.js -module.exports = window.ReactDOM; -``` - -Finally, add the following to your project's `package.json`: -```json -{ - "browser": { - "react": "./assets/js/shims/react.js", - "react-dom": "./assets/js/shims/react-dom.js" - } -} -``` - -This tells Hugo's `js.Build` command to look for `react` and `react-dom` in the project's `assets/js/shims` folder. Note that the `browser` field in your `package.json` file will cause React and ReactDOM to be excluded from your JavaScript bundle. Therefore, **it is unnecessary to add them to the `js.Build` command's `externals` argument.** - -That's it! You should now have a browser-friendly JS which can use external JS libraries. diff --git a/hugolib/js_test.go b/hugolib/js_test.go index 145a057c1..8ab0b970c 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.5.0 // indirect +require github.com/gohugoio/hugoTestProjectJSModImports v0.8.0 // indirect `) @@ -215,4 +215,9 @@ Hello3 from mod2. Date from date-fns: ${today} Hello from lib in the main project Hello5 from mod2. var myparam = "Hugo Rocks!";`) + + // React JSX, verify the shimming. + b.AssertFileContent("public/js/like.js", `@v0.8.0/assets/js/shims/react.js +module.exports = window.ReactDOM; +`) } diff --git a/resources/resource_transformers/js/options.go b/resources/resource_transformers/js/options.go index 5236fe126..a65e67ac0 100644 --- a/resources/resource_transformers/js/options.go +++ b/resources/resource_transformers/js/options.go @@ -62,11 +62,14 @@ type Options struct { Format string // External dependencies, e.g. "react". - Externals []string `hash:"set"` + Externals []string // User defined symbols. Defines map[string]interface{} + // Maps a component import to another. + Shims map[string]string + // User defined params. Will be marshaled to JSON and available as "@params", e.g. // import * as params from '@params'; Params interface{} @@ -138,6 +141,13 @@ func createBuildPlugins(c *Client, opts Options) ([]api.Plugin, error) { fs := c.rs.Assets resolveImport := func(args api.OnResolveArgs) (api.OnResolveResult, error) { + impPath := args.Path + if opts.Shims != nil { + override, found := opts.Shims[impPath] + if found { + impPath = override + } + } isStdin := args.Importer == stdinImporter var relDir string if !isStdin { @@ -153,8 +163,6 @@ func createBuildPlugins(c *Client, opts Options) ([]api.Plugin, error) { relDir = filepath.Dir(opts.sourcefile) } - impPath := args.Path - // Imports not starting with a "." is assumed to live relative to /assets. // Hugo makes no assumptions about the directory structure below /assets. if relDir != "" && strings.HasPrefix(impPath, ".") {