js: Add Shims option
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Thu, 21 Jan 2021 15:52:32 +0000 (16:52 +0100)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Fri, 22 Jan 2021 08:03:24 +0000 (09:03 +0100)
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
hugolib/js_test.go
resources/resource_transformers/js/options.go

index a34454a93f80a77a289a2817e13983020962934d..d03fc1c6d328e3e0495f7842277852a15bae8118 100644 (file)
@@ -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.
-
-```go-html-template
-{{ $externals := slice "react" "react-dom" }}
-```
+: External dependencies. Use this to trim dependencies you know will never be executed. See https://esbuild.github.io/api/#external
 
-> 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:
 <script type="text/javascript" src="{{ $built.RelPermalink }}" defer></script>
 ```
 
-#### 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.
index 145a057c131b05d2dae45a14156665b062c29385..8ab0b970c04472a282dd39f598969ffe2cde5381 100644 (file)
@@ -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;
+`)
 }
index 5236fe126da3f019f6a30cba28580c2c9f21762f..a65e67ac0a9a2d8d58ef67c12ee25942063f9eb4 100644 (file)
@@ -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, ".") {