summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <[email protected]>2020-10-05 13:34:14 +0200
committerBjørn Erik Pedersen <[email protected]>2020-11-03 13:04:37 +0100
commit85e4dd7370eae97ae367e596aa6a10ba42fd4b7c (patch)
tree23e739edbed24a62f842c1a3ebc1d9cb706ea8b7
parent3089fc0ba171be14670b19439bc2eab6b077b6c3 (diff)
downloadhugo-85e4dd7370eae97ae367e596aa6a10ba42fd4b7c.tar.gz
hugo-85e4dd7370eae97ae367e596aa6a10ba42fd4b7c.zip
Make js.Build fully support modules
Fixes #7816 Fixes #7777 Fixes #7916
-rw-r--r--.travis.yml15
-rw-r--r--commands/hugo.go4
-rw-r--r--config/commonConfig.go4
-rw-r--r--deps/deps.go4
-rw-r--r--docs/content/en/getting-started/configuration.md4
-rw-r--r--docs/content/en/hugo-pipes/js.md63
-rw-r--r--go.mod2
-rw-r--r--go.sum100
-rw-r--r--hugofs/fileinfo.go5
-rw-r--r--hugofs/rootmapping_fs.go1
-rw-r--r--hugolib/hugo_sites.go27
-rw-r--r--hugolib/hugo_sites_build.go41
-rw-r--r--hugolib/js_test.go343
-rw-r--r--hugolib/site.go34
-rw-r--r--resources/jsconfig/jsconfig.go93
-rw-r--r--resources/jsconfig/jsconfig_test.go35
-rw-r--r--resources/resource_cache.go13
-rw-r--r--resources/resource_spec.go34
-rw-r--r--resources/resource_transformers/js/build.go565
-rw-r--r--resources/resource_transformers/js/build_test.go82
-rw-r--r--resources/resource_transformers/js/options.go353
-rw-r--r--resources/resource_transformers/js/options_test.go105
22 files changed, 944 insertions, 983 deletions
diff --git a/.travis.yml b/.travis.yml
index bff54e4dd..9ff6029b1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -47,10 +47,10 @@ cache:
before_install:
- df -h
- # https://travis-ci.community/t/go-cant-find-gcc-with-go1-11-1-on-windows/293/5
+ # https://travis-ci.community/t/go-cant-find-gcc-with-go1-11-1-on-windows/293/5
- if [ "$TRAVIS_OS_NAME" = "windows" ]; then
- choco install mingw -y;
- export PATH=/c/tools/mingw64/bin:"$PATH";
+ choco install mingw -y;
+ export PATH=/c/tools/mingw64/bin:"$PATH";
fi
- gem install asciidoctor
- type asciidoctor
@@ -65,12 +65,11 @@ install:
script:
- go mod download
- go mod verify
- - travis_wait 20 mage -v test
- - >
- if [ "$TRAVIS_ARCH" = "amd64" ]; then
- mage -v check;
+ - mage -v test
+ - if [ "$TRAVIS_ARCH" = "amd64" ]; then
+ mage -v check;
else
- HUGO_TIMEOUT=30000 mage -v check;
+ HUGO_TIMEOUT=30000 mage -v check;
fi
- mage -v hugo
- HUGO_IGNOREERRORS=error-remote-getjson ./hugo -s docs/
diff --git a/commands/hugo.go b/commands/hugo.go
index 058f1ec7c..58f33b775 100644
--- a/commands/hugo.go
+++ b/commands/hugo.go
@@ -984,9 +984,11 @@ func (c *commandeer) handleEvents(watcher *watcher.Batcher,
staticEvents := []fsnotify.Event{}
dynamicEvents := []fsnotify.Event{}
- // Special handling for symbolic links inside /content.
filtered := []fsnotify.Event{}
for _, ev := range evs {
+ if c.hugo().ShouldSkipFileChangeEvent(ev) {
+ continue
+ }
// Check the most specific first, i.e. files.
contentMapped := c.hugo().ContentChanges.GetSymbolicLinkMappings(ev.Name)
if len(contentMapped) > 0 {
diff --git a/config/commonConfig.go b/config/commonConfig.go
index 522ced854..9b4edfd90 100644
--- a/config/commonConfig.go
+++ b/config/commonConfig.go
@@ -41,6 +41,10 @@ type Build struct {
// When enabled, will collect and write a hugo_stats.json with some build
// related aggregated data (e.g. CSS class names).
WriteStats bool
+
+ // Can be used to toggle off writing of the intellinsense /assets/jsconfig.js
+ // file.
+ NoJSConfigInAssets bool
}
func (b Build) UseResourceCache(err error) bool {
diff --git a/deps/deps.go b/deps/deps.go
index b70dcb5a0..f6b64c279 100644
--- a/deps/deps.go
+++ b/deps/deps.go
@@ -316,14 +316,16 @@ func (d Deps) ForLanguage(cfg DepsCfg, onCreated func(d *Deps) error) (*Deps, er
d.Site = cfg.Site
- // The resource cache is global so reuse.
+ // These are common for all sites, so reuse.
// TODO(bep) clean up these inits.
resourceCache := d.ResourceSpec.ResourceCache
+ postBuildAssets := d.ResourceSpec.PostBuildAssets
d.ResourceSpec, err = resources.NewSpec(d.PathSpec, d.ResourceSpec.FileCaches, d.BuildState, d.Log, d.globalErrHandler, cfg.OutputFormats, cfg.MediaTypes)
if err != nil {
return nil, err
}
d.ResourceSpec.ResourceCache = resourceCache
+ d.ResourceSpec.PostBuildAssets = postBuildAssets
d.Cfg = l
d.Language = l
diff --git a/docs/content/en/getting-started/configuration.md b/docs/content/en/getting-started/configuration.md
index 392f71a66..fda7e2327 100644
--- a/docs/content/en/getting-started/configuration.md
+++ b/docs/content/en/getting-started/configuration.md
@@ -304,6 +304,7 @@ The `build` configuration section contains global build-related configuration op
[build]
useResourceCacheWhen="fallback"
writeStats = false
+noJSConfigInAssets = false
{{< /code-toggle >}}
@@ -313,6 +314,9 @@ useResourceCacheWhen
writeStats {{< new-in "0.69.0" >}}
: When enabled, a file named `hugo_stats.json` will be written to your project root with some aggregated data about the build, e.g. list of HTML entities published to be used to do [CSS pruning](/hugo-pipes/postprocess/#css-purging-with-postcss). If you're only using this for the production build, you should consider placing it below [config/production](/getting-started/configuration/#configuration-directory). It's also worth mentioning that, due to the nature of the partial server builds, new HTML entities will be added when you add or change them while the server is running, but the old values will not be removed until you restart the server or run a regular `hugo` build.
+noJSConfigInAssets {{< new-in "0.78.0" >}}
+: Turn off writing a `jsconfig.js` into your `/assets` folder with mapping of imports from running [js.Build](https://gohugo.io/hugo-pipes/js). This file is intended to help with intellisense/navigation inside code editors such as [VS Code](https://code.visualstudio.com/). Note that if you do not use `js.Build`, no file will be written.
+
## Configure Server
{{< new-in "0.67.0" >}}
diff --git a/docs/content/en/hugo-pipes/js.md b/docs/content/en/hugo-pipes/js.md
index e7a0e9007..5e9c027d5 100644
--- a/docs/content/en/hugo-pipes/js.md
+++ b/docs/content/en/hugo-pipes/js.md
@@ -23,6 +23,20 @@ targetPath [string]
: If not set, the source path will be used as the base target path.
Note that the target path's extension may change if the target MIME type is different, e.g. when the source is TypeScript.
+params [map or slice] {{< new-in "0.78.0" >}}
+: Params that can be imported as JSON in your JS files, e.g.:
+
+```go-html-template
+{{ $js := resources.Get "js/main.js" | js.Build (dict "params" (dict "api" "https://example.org/api" ) }}
+```
+And then in your JS file:
+
+```js
+import * as params from '@params';
+```
+
+Note that this is meant for small data sets, e.g. config settings. For larger data, please put/mount the files into `/assets` and import them directly.
+
minify [bool]
: Let `js.Build` handle the minification.
@@ -50,7 +64,51 @@ defines [map]
format [string] {{< new-in "0.74.3" >}}
: The output format.
One of: `iife`, `cjs`, `esm`.
- Default is `iife`, a self-executing function, suitable for inclusion as a <script> tag.
+ Default is `iife`, a self-executing function, suitable for inclusion as a <script> tag.
+
+
+### Import JS code from /assets
+
+{{< new-in "0.78.0" >}}
+
+Since Hugo `v0.78.0` `js.Build` has full support for the virtual union file system in [Hugo Modules](/hugo-modules/). You can see some simple examples in this [test project](https://github.com/gohugoio/hugoTestProjectJSModImports), but in short this means that you can do this:
+
+```js
+import { hello } from 'my/module';
+```
+
+And it will respolve to the top-most `index.{js,ts,tsx,jsx}` inside `assets/my/module` in the layered file system.
+
+```js
+import { hello3 } from 'my/module/hello3';
+```
+
+Wil resolve to `hello3.{js,ts,tsx,jsx}` inside `assets/my/module`.
+
+Any imports starting with `.` is resolved relative to the current file:
+
+```js
+import { hello4 } from './lib';
+```
+
+For other files (e.g. `JSON`, `CSS`) you need to use the relative path including any extension, e.g:
+
+```js
+import * as data from 'my/module/data.json';
+```
+
+Also note the new `params` option that can be passed from template to your JS files, e.g.:
+
+```go-html-template
+{{ $js := resources.Get "js/main.js" | js.Build (dict "params" (dict "api" "https://example.org/api" ) }}
+```
+And then in your JS file:
+
+```js
+import * as params from '@params';
+```
+
+Hugo will, by default, generate a `assets/jsconfig.js` file that maps the imports. This is useful for navigation/intellisense help inside code editors, but if you don't need/want it, you can [turn it off](/getting-started/configuration/#configure-build).
### Examples
@@ -69,7 +127,8 @@ Or with options:
<script type="text/javascript" src="{{ $built.RelPermalink }}" defer></script>
```
-#### Shimming a JS library
+#### Shimming a JS library
+
It's a very common practice to load external libraries using CDN rather than importing all packages in a single JS file, making it bulky. To do the same with Hugo, you'll need to shim the libraries as follows. In this example, `algoliasearch` and `instantsearch.js` will be shimmed.
Firstly, add the following to your project's `package.json`:
diff --git a/go.mod b/go.mod
index d349c8a6b..f48a2619c 100644
--- a/go.mod
+++ b/go.mod
@@ -15,7 +15,7 @@ require (
github.com/bep/tmc v0.5.1
github.com/disintegration/gift v1.2.1
github.com/dustin/go-humanize v1.0.0
- github.com/evanw/esbuild v0.7.18
+ github.com/evanw/esbuild v0.8.2
github.com/fortytw2/leaktest v1.3.0
github.com/frankban/quicktest v1.11.1
github.com/fsnotify/fsnotify v1.4.9
diff --git a/go.sum b/go.sum
index 178b2d277..b240a8a77 100644
--- a/go.sum
+++ b/go.sum
@@ -9,12 +9,9 @@ cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxK
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3 h1:AVXDdKsrtX33oR9fbCMu/+c1o8Ofjq6Ku/MInaLVg5Y=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
-cloud.google.com/go/bigquery v1.0.1 h1:hL+ycaJpVE9M7nLoiXb/Pn10ENE2u+oddxbD8uu0ZVU=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
-cloud.google.com/go/datastore v1.0.0 h1:Kt+gOPPp2LEPWp8CSfxhsM8ik9CcyE/gYu+0r+RnZvM=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
-cloud.google.com/go/pubsub v1.0.1 h1:W9tAK3E57P75u0XLLR82LZyw8VpAnhmyTOxW9qzmyj8=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0 h1:VV2nUM3wwLLGh9lSABFgZMjInyUbJeaRSE64WuAIQ+4=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
@@ -54,10 +51,14 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
+github.com/achiku/varfmt v0.0.0-20160708124000-f820e1efecee h1:IfTwtLm+DUeY8kZ8NKSxGRr2kaCe8qqIpJz4Uwh1efU=
+github.com/achiku/varfmt v0.0.0-20160708124000-f820e1efecee/go.mod h1:RKS7P4TSY/jV2QjH/ZxoAE2l4EEXZRPwQ/tIzXiFrk0=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
github.com/alecthomas/chroma v0.7.2-0.20200305040604-4f3623dce67a/go.mod h1:fv5SzZPFJbwp2NXJWpFIX7DZS4HgV1K4ew4Pc2OZD9s=
+github.com/alecthomas/chroma v0.8.0 h1:HS+HE97sgcqjQGu5uVr8jIE55Mmh5UeQ7kckAhHg2pY=
+github.com/alecthomas/chroma v0.8.0/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
github.com/alecthomas/chroma v0.8.1 h1:ym20sbvyC6RXz45u4qDglcgr8E313oPROshcuCHqiEE=
github.com/alecthomas/chroma v0.8.1/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
@@ -75,6 +76,7 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
@@ -82,6 +84,20 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI
github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
github.com/aws/aws-sdk-go v1.18.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.19.16/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
+github.com/aws/aws-sdk-go v1.34.20 h1:D9otznteZZyN5pRyFETqveYia/85Xzk7+RaPGB1I9fE=
+github.com/aws/aws-sdk-go v1.34.20/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
+github.com/aws/aws-sdk-go v1.34.21 h1:M97FXuiJgDHwD4mXhrIZ7RJ4xXV6uZVPvIC2qb+HfYE=
+github.com/aws/aws-sdk-go v1.34.21/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
+github.com/aws/aws-sdk-go v1.34.22 h1:7V2sKilVVgHqdjbW+O/xaVWYfnmuLwZdF/+6JuUh6Cw=
+github.com/aws/aws-sdk-go v1.34.22/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
+github.com/aws/aws-sdk-go v1.34.26 h1:tw4nsSfGvCDnXt2xPe8NkxIrDui+asAWinMknPLEf80=
+github.com/aws/aws-sdk-go v1.34.26/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
+github.com/aws/aws-sdk-go v1.34.27 h1:qBqccUrlz43Zermh0U1O502bHYZsgMlBm+LUVabzBPA=
+github.com/aws/aws-sdk-go v1.34.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
+github.com/aws/aws-sdk-go v1.34.33 h1:ymkFm0rNPEOlgjyX3ojEd4zqzW6kGICBkqWs7LqgHtU=
+github.com/aws/aws-sdk-go v1.34.33/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
+github.com/aws/aws-sdk-go v1.34.34 h1:5dC0ZU0xy25+UavGNEkQ/5MOQwxXDA2YXtjCL1HfYKI=
+github.com/aws/aws-sdk-go v1.34.34/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
github.com/aws/aws-sdk-go v1.35.0 h1:Pxqn1MWNfBCNcX7jrXCCTfsKpg5ms2IMUMmmcGtYJuo=
github.com/aws/aws-sdk-go v1.35.0/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -104,7 +120,9 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
+github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
@@ -132,8 +150,26 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
-github.com/evanw/esbuild v0.7.18 h1:HNMBF6AbyXOhocM4X0WuEQdbfh+/c1URzN0TbihicAA=
-github.com/evanw/esbuild v0.7.18/go.mod h1:mptxmSXIzBIKKCe4jo9A5SToEd1G+AKZ9JmY85dYRJ0=
+github.com/evanw/esbuild v0.6.32 h1:hVuqC+IgEENPWnr0gic01EFgGCmyW8dUPnr78zC7K5k=
+github.com/evanw/esbuild v0.6.32/go.mod h1:mptxmSXIzBIKKCe4jo9A5SToEd1G+AKZ9JmY85dYRJ0=
+github.com/evanw/esbuild v0.7.1 h1:bkC9MpDxHPCLESOf3AQzK1QiyaxbnxFa3XLPnyARLSI=
+github.com/evanw/esbuild v0.7.1/go.mod h1:mptxmSXIzBIKKCe4jo9A5SToEd1G+AKZ9JmY85dYRJ0=
+github.com/evanw/esbuild v0.7.2 h1:LBY35Gw3fKs7jVpsbQwOmw7pJLDHdpliI1Mc/DqP0Hs=
+github.com/evanw/esbuild v0.7.2/go.mod h1:mptxmSXIzBIKKCe4jo9A5SToEd1G+AKZ9JmY85dYRJ0=
+github.com/evanw/esbuild v0.7.4 h1:mLb2tQ9315u23ulh/5Gg8xejOfgqHs2zm7bDNtNnNcM=
+github.com/evanw/esbuild v0.7.4/go.mod h1:mptxmSXIzBIKKCe4jo9A5SToEd1G+AKZ9JmY85dYRJ0=
+github.com/evanw/esbuild v0.7.7 h1:l/M5wHuU738LEX8RyGDP7Zkdrw84j3bpCPrJbKX33Ks=
+github.com/evanw/esbuild v0.7.7/go.mod h1:mptxmSXIzBIKKCe4jo9A5SToEd1G+AKZ9JmY85dYRJ0=
+github.com/evanw/esbuild v0.7.8 h1:DyCpTDLRAtjqRixfXFslGSsYaoKRQfYi+gwGkzW1FHI=
+github.com/evanw/esbuild v0.7.8/go.mod h1:mptxmSXIzBIKKCe4jo9A5SToEd1G+AKZ9JmY85dYRJ0=
+github.com/evanw/esbuild v0.7.9 h1:jXSoYpNpGkOK1VNx3tvd/KnbVbn5ULRYzvkumXaSkxo=
+github.com/evanw/esbuild v0.7.9/go.mod h1:mptxmSXIzBIKKCe4jo9A5SToEd1G+AKZ9JmY85dYRJ0=
+github.com/evanw/esbuild v0.7.15-0.20201011185726-43c0bbcbf178 h1:vFq5Tq6bGzkP8FHlP5LHninOaqOJuwhFi5BMQeXsCf0=
+github.com/evanw/esbuild v0.7.15-0.20201011185726-43c0bbcbf178/go.mod h1:mptxmSXIzBIKKCe4jo9A5SToEd1G+AKZ9JmY85dYRJ0=
+github.com/evanw/esbuild v0.8.1 h1:AqGawd1vAh0l88ZzAyuG9/w4B3Hswt0wM5s05AYHYXo=
+github.com/evanw/esbuild v0.8.1/go.mod h1:mptxmSXIzBIKKCe4jo9A5SToEd1G+AKZ9JmY85dYRJ0=
+github.com/evanw/esbuild v0.8.2 h1:pwvPPsU8dqwBLdPwBmETdp1ccpefC1l+8RKZD1PafcA=
+github.com/evanw/esbuild v0.8.2/go.mod h1:mptxmSXIzBIKKCe4jo9A5SToEd1G+AKZ9JmY85dYRJ0=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fortytw2/leaktest v1.2.0 h1:cj6GCiwJDH7l3tMHLjZDo0QqPtrXJiWSI9JgpeQKw+Q=
github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
@@ -143,12 +179,20 @@ github.com/frankban/quicktest v1.4.1 h1:Wv2VwvNn73pAdFIVUQRXYDFp31lXKbqblIXo/Q5G
github.com/frankban/quicktest v1.4.1/go.mod h1:36zfPVQyHxymz4cH7wlDmVwDrJuljRB60qkgn7rorfQ=
github.com/frankban/quicktest v1.7.2 h1:2QxQoC1TS09S7fhCPsrvqYdvP1H5M1P1ih5ABm3BTYk=
github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
+github.com/frankban/quicktest v1.10.2 h1:19ARM85nVi4xH7xPXuc5eM/udya5ieh7b/Sv+d844Tk=
+github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
+github.com/frankban/quicktest v1.11.0 h1:Yyrghcw93e1jKo4DTZkRFTTFvBsVhzbblBUPNU1vW6Q=
+github.com/frankban/quicktest v1.11.0/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
github.com/frankban/quicktest v1.11.1 h1:stwUsXhUGliQs9t0ZS39BWCltFdOHgABiIlihop8AD4=
github.com/frankban/quicktest v1.11.1/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/getkin/kin-openapi v0.14.0 h1:hqwQL7kze/adt0wB+0UJR2nJm+gfUHqM0Gu4D8nByVc=
+github.com/getkin/kin-openapi v0.14.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw=
+github.com/getkin/kin-openapi v0.22.0 h1:J5IFyKd/5yuB6AZAgwK0CMBKnabWcmkowtsl6bRkz4s=
+github.com/getkin/kin-openapi v0.22.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw=
github.com/getkin/kin-openapi v0.22.1 h1:ODA1olTp175o//NfHko/uCAAhwUSfm5P4+K52XvTg4w=
github.com/getkin/kin-openapi v0.22.1/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s+pcEVXFuAjw=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
@@ -217,6 +261,8 @@ github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
+github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
+github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
@@ -249,20 +295,22 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/jdkato/prose v1.1.1 h1:r6CwY09U97IZNgNQEHoeCh2nvg2e8WCOGjPH/b7lowI=
+github.com/jdkato/prose v1.1.1/go.mod h1:jkF0lkxaX5PFSlk9l4Gh9Y+T57TqUZziWT7uZbW5ADg=
github.com/jdkato/prose v1.2.0 h1:t/R3H6xOrVuIgNevWiOSJf1kEoeF2VWlrN6w76Tkzow=
github.com/jdkato/prose v1.2.0/go.mod h1:WC4YKHtBdAMgBdmfdqBmEuVbBD0U5c9HQ6l1U8Cq0ts=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
+github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
+github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
-github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 h1:rBMNdlhTLzJjJSDIjNEXX1Pz3Hmwmz91v+zycvx9PJc=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
@@ -286,6 +334,8 @@ github.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2px
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/magefile/mage v1.10.0 h1:3HiXzCUY12kh9bIuyXShaVe529fJfyqoVM42o/uom2g=
github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
+github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
+github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/markbates/inflect v0.0.0-20171215194931-a12c3aec81a6 h1:LZhVjIISSbj8qLf2qDPP0D8z0uvOWAW5C85ly5mJW6c=
@@ -327,6 +377,8 @@ github.com/muesli/smartcrop v0.3.0/go.mod h1:i2fCI/UorTfgEpPPLWiFBv4pye+YAG78Rwc
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
+github.com/nicksnyder/go-i18n v1.10.1 h1:isfg77E/aCD7+0lD/D00ebR2MV5vgeQ276WYyDaCRQc=
+github.com/nicksnyder/go-i18n v1.10.1/go.mod h1:e4Di5xjP9oTVrC6y3C7C0HoSYXjSbhh/dU0eUV32nB4=
github.com/nicksnyder/go-i18n/v2 v2.1.1 h1:ATCOanRDlrfKVB4WHAdJnLEqZtDmKYsweqsOUYflnBU=
github.com/nicksnyder/go-i18n/v2 v2.1.1/go.mod h1:d++QJC9ZVf7pa48qrsRWhMJ5pSHIPmS3OLqK1niyLxs=
github.com/niklasfasching/go-org v1.3.2 h1:ZKTSd+GdJYkoZl1pBXLR/k7DRiRXnmB96TRiHmHdzwI=
@@ -343,6 +395,8 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
+github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
@@ -373,6 +427,8 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rogpeppe/go-internal v1.5.1 h1:asQ0uD7BN9RU5Im41SEEZTwCi/zAXdMOLS3npYaos2g=
+github.com/rogpeppe/go-internal v1.5.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.6.2 h1:aIihoIOHCiLZHxyoNQ+ABL4NKhFTgKLBdMLyEAh98m0=
github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/russross/blackfriday v1.5.3-0.20200218234912-41c5fccfd6f6 h1:tlXG832s5pa9x9Gs3Rp2rTvEqjiDEuETUOSfBEiTcns=
@@ -399,12 +455,16 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
-github.com/spf13/afero v1.4.1 h1:asw9sl74539yqavKaglDM5hFpdJVK0Y5Dr/JOgQ89nQ=
-github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
+github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
+github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
+github.com/spf13/afero v1.4.0 h1:jsLTaI1zwYO3vjrzHalkVcIHXTNmdQFepW4OI8H3+x8=
+github.com/spf13/afero v1.4.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v0.0.7 h1:FfTH+vuMXOas8jmfb5/M7dzEYx7LpcLb7a0LPe34uOU=
+github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4=
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
github.com/spf13/fsync v0.9.0 h1:f9CEt3DOB2mnHxZaftmEOFWjABEvKM/xpf3cUwJrGOY=
@@ -417,6 +477,9 @@ github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
+github.com/spf13/viper v1.6.1 h1:VPZzIkznI1YhVMRi6vNFLHSwhnhReBfgTxIPccpfdZk=
+github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
@@ -431,7 +494,6 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
-github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
@@ -446,12 +508,14 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1
github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
github.com/uber/jaeger-client-go v2.15.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
github.com/uber/jaeger-lib v1.5.0/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
+github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.1.22 h1:0e0f6Zee9SAQ5yOZGNMWaOxqVvcc/9/kUWu/Kl91Jk8=
github.com/yuin/goldmark v1.1.22/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM=
@@ -486,7 +550,6 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
-golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136 h1:A1gGSx58LAGVHUUsOf7IiR0u8Xb6W51gRwfDBhkdcaw=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@@ -498,7 +561,6 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
@@ -523,6 +585,8 @@ golang.org/x/net v0.0.0-20190420063019-afa5a82059c6/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco=
+golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
@@ -532,6 +596,8 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFM
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190523182746-aaccbc9213b0 h1:xFEXbcD0oa/xhqQmMXztdZ0bWvexAWds+8c1gRN8nu0=
+golang.org/x/oauth2 v0.0.0-20190523182746-aaccbc9213b0/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
@@ -600,7 +666,6 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -620,7 +685,8 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
+google.golang.org/appengine v1.6.0 h1:Tfd7cKwKbFRsI8RMAD3oqqw7JPFRrvFlOsfbgVkjOOw=
+google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19 h1:Lj2SnHtxkRGJDqnGaSjo+CCdIieEnwVazbOXILwQemk=
@@ -630,6 +696,8 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190508193815-b515fa19cec8/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69 h1:4rNOqY4ULrKzS6twXa619uQgI7h9PaVd4ZhjFQ7C5zs=
+google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
@@ -640,6 +708,8 @@ google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0=
+google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
@@ -667,13 +737,11 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
pack.ag/amqp v0.8.0/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4=
pack.ag/amqp v0.11.0/go.mod h1:4/cbmt4EJXSKlG6LCfWHoqmN0uFdy5i/+YFz+fTfhV4=
diff --git a/hugofs/fileinfo.go b/hugofs/fileinfo.go
index 79d89a88b..5bfb1ff32 100644
--- a/hugofs/fileinfo.go
+++ b/hugofs/fileinfo.go
@@ -37,6 +37,7 @@ import (
const (
metaKeyFilename = "filename"
+ metaKeySourceRoot = "sourceRoot"
metaKeyBaseDir = "baseDir" // Abs base directory of source file.
metaKeyMountRoot = "mountRoot"
metaKeyModule = "module"
@@ -128,6 +129,10 @@ func (f FileMeta) PathFile() string {
return strings.TrimPrefix(strings.TrimPrefix(f.Filename(), base), filepathSeparator)
}
+func (f FileMeta) SourceRoot() string {
+ return f.stringV(metaKeySourceRoot)
+}
+
func (f FileMeta) MountRoot() string {
return f.stringV(metaKeyMountRoot)
}
diff --git a/hugofs/rootmapping_fs.go b/hugofs/rootmapping_fs.go
index 2c4f0df52..a38560d0a 100644
--- a/hugofs/rootmapping_fs.go
+++ b/hugofs/rootmapping_fs.go
@@ -60,6 +60,7 @@ func NewRootMappingFs(fs afero.Fs, rms ...RootMapping) (*RootMappingFs, error) {
rm.Meta = make(FileMeta)
}
+ rm.Meta[metaKeySourceRoot] = rm.To
rm.Meta[metaKeyBaseDir] = rm.ToBasedir
rm.Meta[metaKeyMountRoot] = rm.path
rm.Meta[metaKeyModule] = rm.Module
diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go
index 1a6a07b03..25ae3dd19 100644
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -22,6 +22,8 @@ import (
"sync"
"sync/atomic"
+ "github.com/fsnotify/fsnotify"
+
"github.com/gohugoio/hugo/identity"
radix "github.com/armon/go-radix"
@@ -85,6 +87,10 @@ type HugoSites struct {
// Keeps track of bundle directories and symlinks to enable partial rebuilding.
ContentChanges *contentChangeMap
+ // File change events with filename stored in this map will be skipped.
+ skipRebuildForFilenamesMu sync.Mutex
+ skipRebuildForFilenames map[string]bool
+
init *hugoSitesInit
workers *para.Workers
@@ -94,6 +100,14 @@ type HugoSites struct {
*testCounters
}
+// ShouldSkipFileChangeEvent allows skipping filesystem event early before
+// the build is started.
+func (h *HugoSites) ShouldSkipFileChangeEvent(ev fsnotify.Event) bool {
+ h.skipRebuildForFilenamesMu.Lock()
+ defer h.skipRebuildForFilenamesMu.Unlock()
+ return h.skipRebuildForFilenames[ev.Name]
+}
+
func (h *HugoSites) getContentMaps() *pageMaps {
h.contentInit.Do(func() {
h.content = newPageMaps(h)
@@ -304,12 +318,13 @@ func newHugoSites(cfg deps.DepsCfg, sites ...*Site) (*HugoSites, error) {
}
h := &HugoSites{
- running: cfg.Running,
- multilingual: langConfig,
- multihost: cfg.Cfg.GetBool("multihost"),
- Sites: sites,
- workers: workers,
- numWorkers: numWorkers,
+ running: cfg.Running,
+ multilingual: langConfig,
+ multihost: cfg.Cfg.GetBool("multihost"),
+ Sites: sites,
+ workers: workers,
+ numWorkers: numWorkers,
+ skipRebuildForFilenames: make(map[string]bool),
init: &hugoSitesInit{
data: lazy.New(),
layouts: lazy.New(),
diff --git a/hugolib/hugo_sites_build.go b/hugolib/hugo_sites_build.go
index 3c0440a97..603772afd 100644
--- a/hugolib/hugo_sites_build.go
+++ b/hugolib/hugo_sites_build.go
@@ -33,8 +33,6 @@ import (
"github.com/spf13/afero"
- "github.com/gohugoio/hugo/resources/resource"
-
"github.com/gohugoio/hugo/output"
"github.com/pkg/errors"
@@ -351,14 +349,45 @@ func (h *HugoSites) postProcess() error {
return err
}
- var toPostProcess []resource.OriginProvider
- for _, s := range h.Sites {
- for _, v := range s.ResourceSpec.PostProcessResources {
- toPostProcess = append(toPostProcess, v)
+ // This will only be set when js.Build have been triggered with
+ // imports that resolves to the project or a module.
+ // Write a jsconfig.json file to the project's /asset directory
+ // to help JS intellisense in VS Code etc.
+ if !h.ResourceSpec.BuildConfig.NoJSConfigInAssets && h.BaseFs.Assets.Dirs != nil {
+ m := h.BaseFs.Assets.Dirs[0].Meta()
+ assetsDir := m.Filename()
+ if strings.HasPrefix(assetsDir, h.ResourceSpec.WorkingDir) {
+ if jsConfig := h.ResourceSpec.JSConfigBuilder.Build(assetsDir); jsConfig != nil {
+
+ b, err := json.MarshalIndent(jsConfig, "", " ")
+ if err != nil {
+ h.Log.Warnf("Failed to create jsconfig.json: %s", err)
+
+ } else {
+ filename := filepath.Join(assetsDir, "jsconfig.json")
+ if h.running {
+ h.skipRebuildForFilenamesMu.Lock()
+ h.skipRebuildForFilenames[filename] = true
+ h.skipRebuildForFilenamesMu.Unlock()
+ }
+ // Make sure it's written to the OS fs as this is used by
+ // editors.
+ if err := afero.WriteFile(hugofs.Os, filename, b, 0666); err != nil {
+ h.Log.Warnf("Failed to write jsconfig.json: %s", err)
+ }
+ }
+ }
+
}
}
+ var toPostProcess []postpub.PostPublishedResource
+ for _, r := range h.ResourceSpec.PostProcessResources {
+ toPostProcess = append(toPostProcess, r)
+ }
+
if len(toPostProcess) == 0 {
+ // Nothing more to do.
return nil
}
diff --git a/hugolib/js_test.go b/hugolib/js_test.go
index e34ce3867..6c27219f3 100644
--- a/hugolib/js_test.go
+++ b/hugolib/js_test.go
@@ -14,6 +14,7 @@
package hugolib
import (
+ "fmt"
"os"
"os/exec"
"path/filepath"
@@ -22,7 +23,6 @@ import (
"github.com/gohugoio/hugo/htesting"
- "github.com/spf13/afero"
"github.com/spf13/viper"
qt "github.com/frankban/quicktest"
@@ -82,9 +82,7 @@ document.body.textContent = greeter(user);`
"scripts": {},
"dependencies": {
- "to-camel-case": "1.0.0",
- "react": "^16",
- "react-dom": "^16"
+ "to-camel-case": "1.0.0"
}
}
`
@@ -153,333 +151,46 @@ func TestJSBuild(t *testing.T) {
c := qt.New(t)
- mainJS := `
- import "./included";
-
- console.log("main");
-
-`
- includedJS := `
- console.log("included");
-
- `
-
- workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-test-js")
- c.Assert(err, qt.IsNil)
- defer clean()
-
- v := viper.New()
- v.Set("workingDir", workDir)
- v.Set("disableKinds", []string{"taxonomy", "term", "page"})
- b := newTestSitesBuilder(t).WithLogger(loggers.NewWarningLogger())
-
- b.Fs = hugofs.NewDefault(v)
- b.WithWorkingDir(workDir)
- b.WithViper(v)
- b.WithContent("p1.md", "")
-
- b.WithTemplates("index.html", `
-{{ $js := resources.Get "js/main.js" | js.Build }}
-JS: {{ template "print" $js }}
-
-
-{{ define "print" }}RelPermalink: {{.RelPermalink}}|MIME: {{ .MediaType }}|Content: {{ .Content | safeJS }}{{ end }}
-
-`)
-
- jsDir := filepath.Join(workDir, "assets", "js")
- b.Assert(os.MkdirAll(jsDir, 0777), qt.IsNil)
- b.Assert(os.Chdir(workDir), qt.IsNil)
- b.WithSourceFile("assets/js/main.js", mainJS)
- b.WithSourceFile("assets/js/included.js", includedJS)
-
- b.Build(BuildCfg{})
-
- b.AssertFileContent("public/index.html", `
-console.log(&#34;included&#34;);
-
-`)
-
-}
-
-func TestJSBuildGlobals(t *testing.T) {
- if !isCI() {
- t.Skip("skip (relative) long running modules test when running locally")
- }
-
- wd, _ := os.Getwd()
- defer func() {
- os.Chdir(wd)
- }()
-
- c := qt.New(t)
-
- workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-test-js")
- c.Assert(err, qt.IsNil)
- defer clean()
-
- v := viper.New()
- v.Set("workingDir", workDir)
- v.Set("disableKinds", []string{"taxonomy", "term", "page"})
- b := newTestSitesBuilder(t).WithLogger(loggers.NewWarningLogger())
-
- b.Fs = hugofs.NewDefault(v)
- b.WithWorkingDir(workDir)
- b.WithViper(v)
- b.WithContent("p1.md", "")
-
- jsDir := filepath.Join(workDir, "assets", "js")
- b.Assert(os.MkdirAll(jsDir, 0777), qt.IsNil)
- b.Assert(os.Chdir(workDir), qt.IsNil)
-
- b.WithTemplates("index.html", `
-{{- $js := resources.Get "js/main-project.js" | js.Build -}}
-{{ template "print" (dict "js" $js "name" "root") }}
-
-{{- define "print" -}}
-{{ printf "rellink-%s-%s" .name .js.RelPermalink | safeHTML }}
-{{ printf "mime-%s-%s" .name .js.MediaType | safeHTML }}
-{{ printf "content-%s-%s" .name .js.Content | safeHTML }}
-{{- end -}}
-`)
-
- b.WithSourceFile("assets/js/normal.js", `
-const name = "root-normal";
-export default name;
-`)
- b.WithSourceFile("assets/js/main-project.js", `
-import normal from "@js/normal";
-window.normal = normal; // make sure not to tree-shake
-`)
-
- b.Build(BuildCfg{})
-
- b.AssertFileContent("public/index.html", `
-const name = "root-normal";
-`)
-}
-
-func TestJSBuildOverride(t *testing.T) {
- if !isCI() {
- t.Skip("skip (relative) long running modules test when running locally")
- }
-
- wd, _ := os.Getwd()
- defer func() {
- os.Chdir(wd)
- }()
-
- c := qt.New(t)
-
- workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-test-js2")
+ workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-test-js-mod")
c.Assert(err, qt.IsNil)
defer clean()
- // workDir := "/tmp/hugo-test-js2"
- c.Assert(os.Chdir(workDir), qt.IsNil)
-
- cfg := viper.New()
- cfg.Set("workingDir", workDir)
- fs := hugofs.NewFrom(afero.NewOsFs(), cfg)
- b := newTestSitesBuilder(t)
- b.Fs = fs
- b.WithLogger(loggers.NewWarningLogger())
-
- realWrite := func(name string, content string) {
- realLocation := filepath.Join(workDir, name)
- realDir := filepath.Dir(realLocation)
- if _, err := os.Stat(realDir); err != nil {
- os.MkdirAll(realDir, 0777)
- }
- bytesContent := []byte(content)
- // c.Assert(ioutil.WriteFile(realLocation, bytesContent, 0777), qt.IsNil)
- c.Assert(afero.WriteFile(b.Fs.Source, realLocation, bytesContent, 0777), qt.IsNil)
- }
+ config := fmt.Sprintf(`
+baseURL = "https://example.org"
+workingDir = %q
- realWrite("config.toml", `
-baseURL="https://example.org"
+disableKinds = ["page", "section", "term", "taxonomy"]
[module]
[[module.imports]]
-path="mod2"
-[[module.imports.mounts]]
-source="assets"
-target="assets"
-[[module.imports.mounts]]
-source="layouts"
-target="layouts"
-[[module.imports]]
-path="mod1"
-[[module.imports.mounts]]
-source="assets"
-target="assets"
-[[module.imports.mounts]]
-source="layouts"
-target="layouts"
-`)
+path="github.com/gohugoio/hugoTestProjectJSModImports"
- realWrite("content/p1.md", `---
-layout: sample
----
-`)
- realWrite("themes/mod1/layouts/_default/sample.html", `
-{{- $js := resources.Get "js/main-project.js" | js.Build -}}
-{{ template "print" (dict "js" $js "name" "root") }}
-{{- $js = resources.Get "js/main-mod1.js" | js.Build -}}
-{{ template "print" (dict "js" $js "name" "mod1") }}
-{{- $js = resources.Get "js/main-mod2.js" | js.Build (dict "data" .Site.Params) -}}
-{{ template "print" (dict "js" $js "name" "mod2") }}
+`, workDir)
-{{- $js = resources.Get "js/main-mod2.js" | js.Build (dict "data" .Site.Params "sourceMap" "inline" "targetPath" "js/main-mod2-inline.js") -}}
-{{ template "print" (dict "js" $js "name" "mod2") }}
-
-{{- $js = resources.Get "js/main-mod2.js" | js.Build (dict "data" .Site.Params "sourceMap" "external" "targetPath" "js/main-mod2-external.js") -}}
-{{ template "print" (dict "js" $js "name" "mod2") }}
+ b := newTestSitesBuilder(t)
+ b.Fs = hugofs.NewDefault(viper.New())
+ b.WithWorkingDir(workDir).WithConfigFile("toml", config).WithLogger(loggers.NewInfoLogger())
+ b.WithSourceFile("go.mod", `module github.com/gohugoio/tests/testHugoModules
+
+go 1.15
+
+require github.com/gohugoio/hugoTestProjectJSModImports v0.3.0 // indirect
-{{- define "print" -}}
-{{ printf "rellink-%s-%s" .name .js.RelPermalink | safeHTML }}
-{{ printf "mime-%s-%s" .name .js.MediaType | safeHTML }}
-{{ printf "content-%s-%s" .name .js.Content | safeHTML }}
-{{- end -}}
`)
- // Override project included file
- // This file will override the one in mod1 and mod2
- realWrite("assets/js/override.js", `
-const name = "root-override";
-export default name;
-`)
-
- // Add empty theme mod config files
- realWrite("themes/mod1/config.yml", ``)
- realWrite("themes/mod2/config.yml", ``)
-
- // This is the main project js file.
- // try to include @js/override which is overridden inside of project
- // try to include @js/override-mod which is overridden in mod2
- realWrite("assets/js/main-project.js", `
-import override from "@js/override";
-import overrideMod from "@js/override-mod";
-window.override = override; // make sure to prevent tree-shake
-window.overrideMod = overrideMod; // make sure to prevent tree-shake
-`)
- // This is the mod1 js file
- // try to include @js/override which is overridden inside of the project
- // try to include @js/override-mod which is overridden in mod2
- realWrite("themes/mod1/assets/js/main-mod1.js", `
-import override from "@js/override";
-import overrideMod from "@js/override-mod";
-window.mod = "mod1";
-window.override = override; // make sure to prevent tree-shake
-window.overrideMod = overrideMod; // make sure to prevent tree-shake
-`)
- // This is the mod1 js file (overridden in mod2)
- // try to include @js/override which is overridden inside of the project
- // try to include @js/override-mod which is overridden in mod2
- realWrite("themes/mod2/assets/js/main-mod1.js", `
-import override from "@js/override";
-import overrideMod from "@js/override-mod";
-window.mod = "mod2";
-window.override = override; // make sure to prevent tree-shake
-window.overrideMod = overrideMod; // make sure to prevent tree-shake
-`)
- // This is mod2 js file
- // try to include @js/override which is overridden inside of the project
- // try to include @js/override-mod which is overridden in mod2
- // try to include @config which is declared in a local jsconfig.json file
- // try to include @data which was passed as "data" into js.Build
- realWrite("themes/mod2/assets/js/main-mod2.js", `
-import override from "@js/override";
-import overrideMod from "@js/override-mod";
-import config from "@config";
-import data from "@data";
-window.data = data;
-window.override = override; // make sure to prevent tree-shake
-window.overrideMod = overrideMod; // make sure to prevent tree-shake
-window.config = config;
-`)
- realWrite("themes/mod2/assets/js/jsconfig.json", `
-{
- "compilerOptions": {
- "baseUrl": ".",
- "paths": {
- "@config": ["./config.json"]
- }
- }
-}
-`)
- realWrite("themes/mod2/assets/js/config.json", `
-{
- "data": {
- "sample": "sample"
- }
-}
-`)
- realWrite("themes/mod1/assets/js/override.js", `
-const name = "mod1-override";
-export default name;
-`)
- realWrite("themes/mod2/assets/js/override.js", `
-const name = "mod2-override";
-export default name;
-`)
- realWrite("themes/mod1/assets/js/override-mod.js", `
-const nameMod = "mod1-override";
-export default nameMod;
-`)
- realWrite("themes/mod2/assets/js/override-mod.js", `
-const nameMod = "mod2-override";
-export default nameMod;
-`)
- b.WithConfigFile("toml", `
-baseURL="https://example.org"
-themesDir="./themes"
-[module]
-[[module.imports]]
-path="mod2"
-[[module.imports.mounts]]
-source="assets"
-target="assets"
-[[module.imports.mounts]]
-source="layouts"
-target="layouts"
-[[module.imports]]
-path="mod1"
-[[module.imports.mounts]]
-source="assets"
-target="assets"
-[[module.imports.mounts]]
-source="layouts"
-target="layouts"
-`)
-
- b.WithWorkingDir(workDir)
- b.LoadConfig()
+ b.WithContent("p1.md", "").WithNothingAdded()
b.Build(BuildCfg{})
- b.AssertFileContent("public/js/main-mod1.js", `
-name = "root-override";
-nameMod = "mod2-override";
-window.mod = "mod2";
-`)
- b.AssertFileContent("public/js/main-mod2.js", `
-name = "root-override";
-nameMod = "mod2-override";
-sample: "sample"
-"sect"
-`)
- b.AssertFileContent("public/js/main-project.js", `
-name = "root-override";
-nameMod = "mod2-override";
-`)
- b.AssertFileContent("public/js/main-mod2-external.js.map", `
-const nameMod = \"mod2-override\";\nexport default nameMod;\n
-"\nimport override from \"@js/override\";\nimport overrideMod from \"@js/override-mod\";\nimport config from \"@config\";\nimport data from \"@data\";\nwindow.data = data;\nwindow.override = override; // make sure to prevent tree-shake\nwindow.overrideMod = overrideMod; // make sure to prevent tree-shake\nwindow.config = config;\n"
-`)
- b.AssertFileContent("public/js/main-mod2-inline.js", `
- sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiYXNzZXRzL2pzL292ZXJyaWRlLmpzIiwgInRoZW
-`)
+ b.AssertFileContent("public/js/main.js", `
+greeting: "greeting configured in mod2"
+Hello1 from mod1: $
+return "Hello2 from mod1";
+var Hugo = "Rocks!";
+return "Hello3 from mod2";
+return "Hello from lib in the main project";
+var myparam = "Hugo Rocks!";`)
+
}
diff --git a/hugolib/site.go b/hugolib/site.go
index ec2939530..3679e354c 100644
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -997,6 +997,16 @@ func (s *Site) translateFileEvents(events []fsnotify.Event) []fsnotify.Event {
return filtered
}
+var (
+ // These are only used for cache busting, so false positives are fine.
+ // We also deliberately do not match for file suffixes to also catch
+ // directory names.
+ // TODO(bep) consider this when completing the relevant PR rewrite on this.
+ cssFileRe = regexp.MustCompile("(css|sass|scss)")
+ cssConfigRe = regexp.MustCompile(`(postcss|tailwind)\.config\.js`)
+ jsFileRe = regexp.MustCompile("(js|ts|jsx|tsx)")
+)
+
// reBuild partially rebuilds a site given the filesystem events.
// It returns whetever the content source was changed.
// TODO(bep) clean up/rewrite this method.
@@ -1028,19 +1038,24 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
logger = helpers.NewDistinctFeedbackLogger()
)
- var isCSSConfigRe = regexp.MustCompile(`(postcss|tailwind)\.config\.js`)
- var isCSSFileRe = regexp.MustCompile(`\.(css|scss|sass)`)
-
var cachePartitions []string
// Special case
// TODO(bep) I have a ongoing branch where I have redone the cache. Consider this there.
- var isCSSChange bool
+ var (
+ evictCSSRe *regexp.Regexp
+ evictJSRe *regexp.Regexp
+ )
for _, ev := range events {
if assetsFilename := s.BaseFs.Assets.MakePathRelative(ev.Name); assetsFilename != "" {
cachePartitions = append(cachePartitions, resources.ResourceKeyPartitions(assetsFilename)...)
- if !isCSSChange {
- isCSSChange = isCSSFileRe.MatchString(assetsFilename) || isCSSConfigRe.MatchString(assetsFilename)
+ if evictCSSRe == nil {
+ if cssFileRe.MatchString(assetsFilename) || cssConfigRe.MatchString(assetsFilename) {
+ evictCSSRe = cssFileRe
+ }
+ }
+ if evictJSRe == nil && jsFileRe.MatchString(assetsFilename) {
+ evictJSRe = jsFileRe
}
}
@@ -1088,8 +1103,11 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
// These in memory resource caches will be rebuilt on demand.
for _, s := range s.h.Sites {
s.ResourceSpec.ResourceCache.DeletePartitions(cachePartitions...)
- if isCSSChange {
- s.ResourceSpec.ResourceCache.DeleteContains("css", "scss", "sass")
+ if evictCSSRe != nil {
+ s.ResourceSpec.ResourceCache.DeleteMatches(evictCSSRe)
+ }
+ if evictJSRe != nil {
+ s.ResourceSpec.ResourceCache.DeleteMatches(evictJSRe)
}
}
diff --git a/resources/jsconfig/jsconfig.go b/resources/jsconfig/jsconfig.go
new file mode 100644
index 000000000..9b399bfe7
--- /dev/null
+++ b/resources/jsconfig/jsconfig.go
@@ -0,0 +1,93 @@
+// Copyright 2020 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package jsconfig
+
+import (
+ "path/filepath"
+ "sort"
+ "sync"
+)
+
+// Builder builds a jsconfig.json file that, currently, is used only to assist
+// intellinsense in editors.
+type Builder struct {
+ sourceRootsMu sync.RWMutex
+ sourceRoots map[string]bool
+}
+
+// NewBuilder creates a new Builder.
+func NewBuilder() *Builder {
+ return &Builder{sourceRoots: make(map[string]bool)}
+}
+
+// Build builds a new Config with paths relative to dir.
+// This method is thread safe.
+func (b *Builder) Build(dir string) *Config {
+ b.sourceRootsMu.RLock()
+ defer b.sourceRootsMu.RUnlock()
+
+ if len(b.sourceRoots) == 0 {
+ return nil
+ }
+ conf := newJSConfig()
+
+ var roots []string
+ for root := range b.sourceRoots {
+ rel, err := filepath.Rel(dir, filepath.Join(root, "*"))
+ if err == nil {
+ roots = append(roots, rel)
+ }
+ }
+ sort.Strings(roots)
+ conf.CompilerOptions.Paths["*"] = roots
+
+ return conf
+}
+
+// AddSourceRoot adds a new source root.
+// This method is thread safe.
+func (b *Builder) AddSourceRoot(root string) {
+ b.sourceRootsMu.RLock()
+ found := b.sourceRoots[root]
+ b.sourceRootsMu.RUnlock()
+
+ if found {
+ return
+ }
+
+ b.sourceRootsMu.Lock()
+ b.sourceRoots[root] = true
+ b.sourceRootsMu.Unlock()
+
+}
+
+// CompilerOptions holds compilerOptions for jsonconfig.json.
+type CompilerOptions struct {
+ BaseURL string `json:"baseUrl"`
+ Paths map[string][]string `json:"paths"`
+}
+
+// Config holds the data for jsconfig.json.
+type Config struct {
+ CompilerOptions CompilerOptions `json:"compilerOptions"`
+}
+
+func newJSConfig() *Config {
+ return &Config{
+ CompilerOptions: CompilerOptions{
+ BaseURL: ".",
+ Paths: make(map[string][]string),
+ },
+ }
+}
diff --git a/resources/jsconfig/jsconfig_test.go b/resources/jsconfig/jsconfig_test.go
new file mode 100644
index 000000000..9a9657843
--- /dev/null
+++ b/resources/jsconfig/jsconfig_test.go
@@ -0,0 +1,35 @@
+// Copyright 2020 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package jsconfig
+
+import (
+ "path/filepath"
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestJsConfigBuilder(t *testing.T) {
+ c := qt.New(t)
+
+ b := NewBuilder()
+ b.AddSourceRoot("/c/assets")
+ b.AddSourceRoot("/d/assets")
+
+ conf := b.Build("/a/b")
+ c.Assert(conf.CompilerOptions.BaseURL, qt.Equals, ".")
+ c.Assert(conf.CompilerOptions.Paths["*"], qt.DeepEquals, []string{filepath.FromSlash("../../c/assets/*"), filepath.FromSlash("../../d/assets/*")})
+
+ c.Assert(NewBuilder().Build("/a/b"), qt.IsNil)
+}
diff --git a/resources/resource_cache.go b/resources/resource_cache.go
index feaa94f5c..6c4ba951b 100644
--- a/resources/resource_cache.go
+++ b/resources/resource_cache.go
@@ -18,6 +18,7 @@ import (
"io"
"path"
"path/filepath"
+ "regexp"
"strings"
"sync"
@@ -296,21 +297,15 @@ func (c *ResourceCache) DeletePartitions(partitions ...string) {
}
-func (c *ResourceCache) DeleteContains(parts ...string) {
+func (c *ResourceCache) DeleteMatches(re *regexp.Regexp) {
c.Lock()
defer c.Unlock()
for k := range c.cache {
- clear := false
- for _, part := range parts {
- if strings.Contains(k, part) {
- clear = true
- break
- }
- }
- if clear {
+ if re.MatchString(k) {
delete(c.cache, k)
}
+
}
}
diff --git a/resources/resource_spec.go b/resources/resource_spec.go
index 17225e3f5..0ca60fe31 100644
--- a/resources/resource_spec.go
+++ b/resources/resource_spec.go
@@ -23,6 +23,8 @@ import (
"strings"
"sync"
+ "github.com/gohugoio/hugo/resources/jsconfig"
+
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/config"
@@ -76,17 +78,20 @@ func NewSpec(
}
rs := &Spec{
- PathSpec: s,
- Logger: logger,
- ErrorSender: errorHandler,
- imaging: imaging,
- incr: incr,
- MediaTypes: mimeTypes,
- OutputFormats: outputFormats,
- Permalinks: permalinks,
- BuildConfig: config.DecodeBuild(s.Cfg),
- FileCaches: fileCaches,
- PostProcessResources: make(map[string]postpub.PostPublishedResource),
+ PathSpec: s,
+ Logger: logger,
+ ErrorSender: errorHandler,
+ imaging: imaging,
+ incr: incr,
+ MediaTypes: mimeTypes,
+ OutputFormats: outputFormats,
+ Permalinks: permalinks,
+ BuildConfig: config.DecodeBuild(s.Cfg),
+ FileCaches: fileCaches,
+ PostBuildAssets: &PostBuildAssets{
+ PostProcessResources: make(map[string]postpub.PostPublishedResource),
+ JSConfigBuilder: jsconfig.NewBuilder(),
+ },
imageCache: newImageCache(
fileCaches.ImageCache(),
@@ -121,8 +126,15 @@ type Spec struct {
ResourceCache *ResourceCache
FileCaches filecache.Caches
+ // Assets used after the build is done.
+ // This is shared between all sites.
+ *PostBuildAssets
+}
+
+type PostBuildAssets struct {
postProcessMu sync.RWMutex
PostProcessResources map[string]postpub.PostPublishedResource
+ JSConfigBuilder *jsconfig.Builder
}
func (r *Spec) New(fd ResourceSourceDescriptor) (resource.Resource, error) {
diff --git a/resources/resource_transformers/js/build.go b/resources/resource_transformers/js/build.go
index d316bc85b..8a7c21592 100644
--- a/resources/resource_transformers/js/build.go
+++ b/resources/resource_transformers/js/build.go
@@ -14,122 +14,52 @@
package js
import (
- "encoding/json"
+ "errors"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
- "reflect"
"strings"
- "github.com/achiku/varfmt"
- "github.com/spf13/cast"
+ "github.com/gohugoio/hugo/hugofs"
+
+ "github.com/spf13/afero"
+
+ "github.com/gohugoio/hugo/common/herrors"
- "github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugolib/filesystems"
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/resources/internal"
- "github.com/mitchellh/mapstructure"
-
"github.com/evanw/esbuild/pkg/api"
"github.com/gohugoio/hugo/resources"
"github.com/gohugoio/hugo/resources/resource"
)
-// Options esbuild configuration
-type Options struct {
- // If not set, the source path will be used as the base target path.
- // Note that the target path's extension may change if the target MIME type
- // is different, e.g. when the source is TypeScript.
- TargetPath string
-
- // Whether to minify to output.
- Minify bool
-
- // Whether to write mapfiles
- SourceMap string
-
- // The language target.
- // One of: es2015, es2016, es2017, es2018, es2019, es2020 or esnext.
- // Default is esnext.
- Target string
-
- // The output format.
- // One of: iife, cjs, esm
- // Default is to esm.
- Format string
-
- // External dependencies, e.g. "react".
- Externals []string `hash:"set"`
-
- // User defined symbols.
- Defines map[string]interface{}
-
- // User defined data (must be JSON marshall'able)
- Data interface{}
-
- // What to use instead of React.createElement.
- JSXFactory string
-
- // What to use instead of React.Fragment.
- JSXFragment string
-
- mediaType media.Type
- outDir string
- contents string
- sourcefile string
- resolveDir string
- workDir string
- tsConfig string
-}
-
-func decodeOptions(m map[string]interface{}) (Options, error) {
- var opts Options
-
- if err := mapstructure.WeakDecode(m, &opts); err != nil {
- return opts, err
- }
-
- if opts.TargetPath != "" {
- opts.TargetPath = helpers.ToSlashTrimLeading(opts.TargetPath)
- }
-
- opts.Target = strings.ToLower(opts.Target)
- opts.Format = strings.ToLower(opts.Format)
-
- return opts, nil
-}
-
-// Client context for esbuild
+// Client context for ESBuild.
type Client struct {
rs *resources.Spec
sfs *filesystems.SourceFilesystem
}
-// New create new client context
+// New creates a new client context.
func New(fs *filesystems.SourceFilesystem, rs *resources.Spec) *Client {
- return &Client{rs: rs, sfs: fs}
+ return &Client{
+ rs: rs,
+ sfs: fs,
+ }
}
type buildTransformation struct {
optsm map[string]interface{}
- rs *resources.Spec
- sfs *filesystems.SourceFilesystem
+ c *Client
}
func (t *buildTransformation) Key() internal.ResourceTransformationKey {
return internal.NewResourceTransformationKey("jsbuild", t.optsm)
}
-func appendExts(list []string, rel string) []string {
- for _, ext := range []string{".tsx", ".ts", ".jsx", ".mjs", ".cjs", ".js", ".json"} {
- list = append(list, fmt.Sprintf("%s/index%s", rel, ext))
- }
- return list
-}
-
func (t *buildTransformation) Transform(ctx *resources.ResourceTransformationCtx) error {
ctx.OutMediaType = media.JavascriptType
@@ -149,465 +79,68 @@ func (t *buildTransformation) Transform(ctx *resources.ResourceTransformationCtx
return err
}
- sdir, sfile := filepath.Split(t.sfs.RealFilename(ctx.SourcePath))
- opts.workDir, err = filepath.Abs(t.rs.WorkingDir)
- if err != nil {
- return err
- }
-
- opts.sourcefile = sfile
- opts.resolveDir = sdir
+ sdir, _ := path.Split(ctx.SourcePath)
+ opts.sourcefile = ctx.SourcePath
+ opts.resolveDir = t.c.sfs.RealFilename(sdir)
+ opts.workDir = t.c.rs.WorkingDir
opts.contents = string(src)
opts.mediaType = ctx.InMediaType
- // Create new temporary tsconfig file
- newTSConfig, err := ioutil.TempFile("", "tsconfig.*.json")
+ buildOptions, err := toBuildOptions(opts)
if err != nil {
return err
}
- filesToDelete := make([]*os.File, 0)
-
- defer func() {
- for _, file := range filesToDelete {
- os.Remove(file.Name())
- }
- }()
+ buildOptions.Plugins, err = createBuildPlugins(t.c, opts)
+ if err != nil {
+ return err
+ }
- filesToDelete = append(filesToDelete, newTSConfig)
- configDir, _ := filepath.Split(newTSConfig.Name())
+ result := api.Build(buildOptions)
- // Search for the innerMost tsconfig or jsconfig
- innerTsConfig := ""
- tsDir := opts.resolveDir
- baseURLAbs := configDir
- baseURL := "."
- for tsDir != "." {
- tryTsConfig := path.Join(tsDir, "tsconfig.json")
- _, err := os.Stat(tryTsConfig)
- if err != nil {
- tryTsConfig := path.Join(tsDir, "jsconfig.json")
- _, err = os.Stat(tryTsConfig)
+ if len(result.Errors) > 0 {
+ first := result.Errors[0]
+ loc := first.Location
+ path := loc.File
+
+ var err error
+ var f afero.File
+ var filename string
+
+ if !strings.HasPrefix(path, "..") {
+ // Try first in the assets fs
+ var fi os.FileInfo
+ fi, err = t.c.rs.BaseFs.Assets.Fs.Stat(path)
if err == nil {
- innerTsConfig = tryTsConfig
- baseURLAbs = tsDir
- break
+ m := fi.(hugofs.FileMetaInfo).Meta()
+ filename = m.Filename()
+ f, err = m.Open()
}
- } else {
- innerTsConfig = tryTsConfig
- baseURLAbs = tsDir
- break
}
- if tsDir == opts.workDir {
- break
- }
- tsDir = path.Dir(tsDir)
- }
- // Resolve paths for @assets and @js (@js is just an alias for assets/js)
- dirs := make([]string, 0)
- rootPaths := make([]string, 0)
- for _, dir := range t.sfs.RealDirs(".") {
- rootDir := dir
- if !strings.HasSuffix(dir, "package.json") {
- dirs = append(dirs, dir)
- } else {
- rootDir, _ = path.Split(dir)
+ if f == nil {
+ path = filepath.Join(t.c.rs.WorkingDir, path)
+ filename = path
+ f, err = t.c.rs.Fs.Os.Open(path)
}
- nodeModules := path.Join(rootDir, "node_modules")
- if _, err := os.Stat(nodeModules); err == nil {
- rootPaths = append(rootPaths, nodeModules)
- }
- }
- // Construct new temporary tsconfig file content
- config := make(map[string]interface{})
- if innerTsConfig != "" {
- oldConfig, err := ioutil.ReadFile(innerTsConfig)
if err == nil {
- // If there is an error, it just means there is no config file here.
- // Since we're also using the tsConfig file path to detect where
- // to put the temp file, this is ok.
- err = json.Unmarshal(oldConfig, &config)
- if err != nil {
- return err
- }
- }
- }
-
- if config["compilerOptions"] == nil {
- config["compilerOptions"] = map[string]interface{}{}
- }
-
- // Assign new global paths to the config file while reading existing ones.
- compilerOptions := config["compilerOptions"].(map[string]interface{})
-
- // Handle original baseUrl if it's there
- if compilerOptions["baseUrl"] != nil {
- baseURL = compilerOptions["baseUrl"].(string)
- oldBaseURLAbs := path.Join(tsDir, baseURL)
- rel, _ := filepath.Rel(configDir, oldBaseURLAbs)
- configDir = oldBaseURLAbs
- baseURLAbs = configDir
- if "/" != helpers.FilePathSeparator {
- // On windows we need to use slashes instead of backslash
- rel = strings.ReplaceAll(rel, helpers.FilePathSeparator, "/")
- }
- if rel != "" {
- if strings.HasPrefix(rel, ".") {
- baseURL = rel
- } else {
- baseURL = fmt.Sprintf("./%s", rel)
- }
- }
- compilerOptions["baseUrl"] = baseURL
- } else {
- compilerOptions["baseUrl"] = baseURL
- }
-
- jsRel := func(refPath string) string {
- rel, _ := filepath.Rel(configDir, refPath)
- if "/" != helpers.FilePathSeparator {
- // On windows we need to use slashes instead of backslash
- rel = strings.ReplaceAll(rel, helpers.FilePathSeparator, "/")
- }
- if rel != "" {
- if !strings.HasPrefix(rel, ".") {
- rel = fmt.Sprintf("./%s", rel)
- }
- } else {
- rel = "."
- }
- return rel
- }
-
- // Handle possible extends
- if config["extends"] != nil {
- extends := config["extends"].(string)
- extendsAbs := path.Join(tsDir, extends)
- rel := jsRel(extendsAbs)
- config["extends"] = rel
- }
-
- var optionsPaths map[string]interface{}
- // Get original paths if they exist
- if compilerOptions["paths"] != nil {
- optionsPaths = compilerOptions["paths"].(map[string]interface{})
- } else {
- optionsPaths = make(map[string]interface{})
- }
- compilerOptions["paths"] = optionsPaths
-
- assets := make([]string, 0)
- assetsExact := make([]string, 0)
- js := make([]string, 0)
- jsExact := make([]string, 0)
- for _, dir := range dirs {
- rel := jsRel(dir)
- assets = append(assets, fmt.Sprintf("%s/*", rel))
- assetsExact = appendExts(assetsExact, rel)
-
- rel = jsRel(filepath.Join(dir, "js"))
- js = append(js, fmt.Sprintf("%s/*", rel))
- jsExact = appendExts(jsExact, rel)
- }
-
- optionsPaths["@assets/*"] = assets
- optionsPaths["@js/*"] = js
-
- // Make @js and @assets absolue matches search for index files
- // to get around the problem in ESBuild resolving folders as index files.
- optionsPaths["@assets"] = assetsExact
- optionsPaths["@js"] = jsExact
-
- var newDataFile *os.File
- if opts.Data != nil {
- // Create a data file
- lines := make([]string, 0)
- lines = append(lines, "// auto generated data import")
- exports := make([]string, 0)
- keys := make(map[string]bool)
-
- var bytes []byte
-
- conv := reflect.ValueOf(opts.Data)
- convType := conv.Kind()
- if convType == reflect.Interface {
- if conv.IsNil() {
- conv = reflect.Value{}
- }
- }
-
- if conv.Kind() != reflect.Map {
- // Write out as single JSON file
- newDataFile, err = ioutil.TempFile("", "data.*.json")
- // Output the data
- bytes, err = json.MarshalIndent(conv.InterfaceData(), "", " ")
- if err != nil {
- return err
- }
- } else {
- // Try to allow tree shaking at the root
- newDataFile, err = ioutil.TempFile(configDir, "data.*.js")
- for _, key := range conv.MapKeys() {
- strKey := key.Interface().(string)
- if keys[strKey] {
- continue
- }
- keys[strKey] = true
-
- value := conv.MapIndex(key)
-
- keyVar := varfmt.PublicVarName(strKey)
-
- // Output the data
- bytes, err := json.MarshalIndent(value.Interface(), "", " ")
- if err != nil {
- return err
- }
- jsonValue := string(bytes)
-
- lines = append(lines, fmt.Sprintf("export const %s = %s;", keyVar, jsonValue))
- exports = append(exports, fmt.Sprintf(" %s,", keyVar))
- if strKey != keyVar {
- exports = append(exports, fmt.Sprintf(" [\"%s\"]: %s,", strKey, keyVar))
- }
- }
-
- lines = append(lines, "const all = {")
- for _, line := range exports {
- lines = append(lines, line)
- }
- lines = append(lines, "};")
- lines = append(lines, "export default all;")
-
- bytes = []byte(strings.Join(lines, "\n"))
- }
-
- // Write tsconfig file
- _, err = newDataFile.Write(bytes)
- if err != nil {
+ fe := herrors.NewFileError("js", 0, loc.Line, loc.Column, errors.New(first.Text))
+ err, _ := herrors.WithFileContext(fe, filename, f, herrors.SimpleLineMatcher)
+ f.Close()
return err
}
- err = newDataFile.Close()
- if err != nil {
- return err
- }
-
- // Link this file into `import data from "@data"`
- dataFiles := make([]string, 1)
- rel, _ := filepath.Rel(baseURLAbs, newDataFile.Name())
- dataFiles[0] = rel
- optionsPaths["@data"] = dataFiles
-
- filesToDelete = append(filesToDelete, newDataFile)
- }
-
- if len(rootPaths) > 0 {
- // This will allow import "react" to resolve a react module that's
- // either in the root node_modules or in one of the hugo mods.
- optionsPaths["*"] = rootPaths
- }
-
- // Output the new config file
- bytes, err := json.MarshalIndent(config, "", " ")
- if err != nil {
- return err
- }
-
- // Write tsconfig file
- _, err = newTSConfig.Write(bytes)
- if err != nil {
- return err
- }
- err = newTSConfig.Close()
- if err != nil {
- return err
- }
-
- // Tell ESBuild about this new config file to use
- opts.tsConfig = newTSConfig.Name()
-
- buildOptions, err := toBuildOptions(opts)
- if err != nil {
- os.Remove(opts.tsConfig)
- return err
- }
-
- result := api.Build(buildOptions)
- if len(result.Warnings) > 0 {
- for _, value := range result.Warnings {
- if value.Location != nil {
- t.rs.Logger.WARN.Println(fmt.Sprintf("%s:%d: WARN: %s",
- filepath.Join(sdir, value.Location.File),
- value.Location.Line, value.Text))
- t.rs.Logger.WARN.Println(" ", value.Location.LineText)
- } else {
- t.rs.Logger.WARN.Println(fmt.Sprintf("%s: WARN: %s",
- sdir,
- value.Text))
- }
- }
- }
- if len(result.Errors) > 0 {
- output := result.Errors[0].Text
- for _, value := range result.Errors {
- var line string
- if value.Location != nil {
- line = fmt.Sprintf("%s:%d ERROR: %s",
- filepath.Join(sdir, value.Location.File),
- value.Location.Line, value.Text)
- } else {
- line = fmt.Sprintf("%s ERROR: %s",
- sdir,
- value.Text)
- }
- t.rs.Logger.ERROR.Println(line)
- output = fmt.Sprintf("%s\n%s", output, line)
- if value.Location != nil {
- t.rs.Logger.ERROR.Println(" ", value.Location.LineText)
- }
- }
- return fmt.Errorf("%s", output)
+ return fmt.Errorf("%s", result.Errors[0].Text)
}
- if buildOptions.Outfile != "" {
- _, tfile := path.Split(opts.TargetPath)
- output := fmt.Sprintf("%s//# sourceMappingURL=%s\n",
- string(result.OutputFiles[1].Contents), tfile+".map")
- _, err := ctx.To.Write([]byte(output))
- if err != nil {
- return err
- }
- ctx.PublishSourceMap(string(result.OutputFiles[0].Contents))
- } else {
- ctx.To.Write(result.OutputFiles[0].Contents)
- }
+ ctx.To.Write(result.OutputFiles[0].Contents)
return nil
}
// Process process esbuild transform
func (c *Client) Process(res resources.ResourceTransformer, opts map[string]interface{}) (resource.Resource, error) {
return res.Transform(
- &buildTransformation{rs: c.rs, sfs: c.sfs, optsm: opts},
+ &buildTransformation{c: c, optsm: opts},
)
}
-
-func toBuildOptions(opts Options) (buildOptions api.BuildOptions, err error) {
- var target api.Target
- switch opts.Target {
- case "", "esnext":
- target = api.ESNext
- case "es5":
- target = api.ES5
- case "es6", "es2015":
- target = api.ES2015
- case "es2016":
- target = api.ES2016
- case "es2017":
- target = api.ES2017
- case "es2018":
- target = api.ES2018
- case "es2019":
- target = api.ES2019
- case "es2020":
- target = api.ES2020
- default:
- err = fmt.Errorf("invalid target: %q", opts.Target)
- return
- }
-
- mediaType := opts.mediaType
- if mediaType.IsZero() {
- mediaType = media.JavascriptType
- }
-
- var loader api.Loader
- switch mediaType.SubType {
- // TODO(bep) ESBuild support a set of other loaders, but I currently fail
- // to see the relevance. That may change as we start using this.
- case media.JavascriptType.SubType:
- loader = api.LoaderJS
- case media.TypeScriptType.SubType:
- loader = api.LoaderTS
- case media.TSXType.SubType:
- loader = api.LoaderTSX
- case media.JSXType.SubType:
- loader = api.LoaderJSX
- default:
- err = fmt.Errorf("unsupported Media Type: %q", opts.mediaType)
- return
- }
-
- var format api.Format
- // One of: iife, cjs, esm
- switch opts.Format {
- case "", "iife":
- format = api.FormatIIFE
- case "esm":
- format = api.FormatESModule
- case "cjs":
- format = api.FormatCommonJS
- default:
- err = fmt.Errorf("unsupported script output format: %q", opts.Format)
- return
- }
-
- var defines map[string]string
- if opts.Defines != nil {
- defines = cast.ToStringMapString(opts.Defines)
- }
-
- // By default we only need to specify outDir and no outFile
- var outDir = opts.outDir
- var outFile = ""
- var sourceMap api.SourceMap
- switch opts.SourceMap {
- case "inline":
- sourceMap = api.SourceMapInline
- case "external":
- // When doing external sourcemaps we should specify
- // out file and no out dir
- sourceMap = api.SourceMapExternal
- outFile = filepath.Join(opts.workDir, opts.TargetPath)
- outDir = ""
- case "":
- sourceMap = api.SourceMapNone
- default:
- err = fmt.Errorf("unsupported sourcemap type: %q", opts.SourceMap)
- return
- }
-
- buildOptions = api.BuildOptions{
- Outfile: outFile,
- Bundle: true,
-
- Target: target,
- Format: format,
- Sourcemap: sourceMap,
-
- MinifyWhitespace: opts.Minify,
- MinifyIdentifiers: opts.Minify,
- MinifySyntax: opts.Minify,
-
- Outdir: outDir,
- Defines: defines,
-
- Externals: opts.Externals,
-
- JSXFactory: opts.JSXFactory,
- JSXFragment: opts.JSXFragment,
-
- Tsconfig: opts.tsConfig,
-
- Stdin: &api.StdinOptions{
- Contents: opts.contents,
- Sourcefile: opts.sourcefile,
- ResolveDir: opts.resolveDir,
- Loader: loader,
- },
- }
- return
-
-}
diff --git a/resources/resource_transformers/js/build_test.go b/resources/resource_transformers/js/build_test.go
index 8839c646e..30a4490ed 100644
--- a/resources/resource_transformers/js/build_test.go
+++ b/resources/resource_transformers/js/build_test.go
@@ -12,85 +12,3 @@
// limitations under the License.
package js
-
-import (
- "testing"
-
- "github.com/gohugoio/hugo/media"
-
- "github.com/evanw/esbuild/pkg/api"
-
- qt "github.com/frankban/quicktest"
-)
-
-// This test is added to test/warn against breaking the "stability" of the
-// cache key. It's sometimes needed to break this, but should be avoided if possible.
-func TestOptionKey(t *testing.T) {
- c := qt.New(t)
-
- opts := map[string]interface{}{
- "TargetPath": "foo",
- "Target": "es2018",
- }
-
- key := (&buildTransformation{optsm: opts}).Key()
-
- c.Assert(key.Value(), qt.Equals, "jsbuild_7891849149754191852")
-}
-
-func TestToBuildOptions(t *testing.T) {
- c := qt.New(t)
-
- opts, err := toBuildOptions(Options{mediaType: media.JavascriptType})
- c.Assert(err, qt.IsNil)
- c.Assert(opts, qt.DeepEquals, api.BuildOptions{
- Bundle: true,
- Target: api.ESNext,
- Format: api.FormatIIFE,
- Stdin: &api.StdinOptions{},
- })
-
- opts, err = toBuildOptions(Options{
- Target: "es2018", Format: "cjs", Minify: true, mediaType: media.JavascriptType})
- c.Assert(err, qt.IsNil)
- c.Assert(opts, qt.DeepEquals, api.BuildOptions{
- Bundle: true,
- Target: api.ES2018,
- Format: api.FormatCommonJS,
- MinifyIdentifiers: true,
- MinifySyntax: true,
- MinifyWhitespace: true,
- Stdin: &api.StdinOptions{},
- })
-
- opts, err = toBuildOptions(Options{
- Target: "es2018", Format: "cjs", Minify: true, mediaType: media.JavascriptType,
- SourceMap: "inline"})
- c.Assert(err, qt.IsNil)
- c.Assert(opts, qt.DeepEquals, api.BuildOptions{
- Bundle: true,
- Target: api.ES2018,
- Format: api.FormatCommonJS,
- MinifyIdentifiers: true,
- MinifySyntax: true,
- MinifyWhitespace: true,
- Sourcemap: api.SourceMapInline,
- Stdin: &api.StdinOptions{},
- })
-
- opts, err = toBuildOptions(Options{
- Target: "es2018", Format: "cjs", Minify: true, mediaType: media.JavascriptType,
- SourceMap: "external"})
- c.Assert(err, qt.IsNil)
- c.Assert(opts, qt.DeepEquals, api.BuildOptions{
- Bundle: true,
- Target: api.ES2018,
- Format: api.FormatCommonJS,
- MinifyIdentifiers: true,
- MinifySyntax: true,
- MinifyWhitespace: true,
- Sourcemap: api.SourceMapExternal,
- Stdin: &api.StdinOptions{},
- })
-
-}
diff --git a/resources/resource_transformers/js/options.go b/resources/resource_transformers/js/options.go
new file mode 100644
index 000000000..5e74982d3
--- /dev/null
+++ b/resources/resource_transformers/js/options.go
@@ -0,0 +1,353 @@
+// Copyright 2020 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package js
+
+import (
+ "encoding/json"
+ "fmt"
+ "path/filepath"
+ "strings"
+ "sync"
+
+ "github.com/pkg/errors"
+
+ "github.com/evanw/esbuild/pkg/api"
+
+ "github.com/gohugoio/hugo/helpers"
+ "github.com/gohugoio/hugo/hugofs"
+ "github.com/gohugoio/hugo/media"
+ "github.com/mitchellh/mapstructure"
+ "github.com/spf13/cast"
+)
+
+// Options esbuild configuration
+type Options struct {
+ // If not set, the source path will be used as the base target path.
+ // Note that the target path's extension may change if the target MIME type
+ // is different, e.g. when the source is TypeScript.
+ TargetPath string
+
+ // Whether to minify to output.
+ Minify bool
+
+ // Whether to write mapfiles
+ SourceMap string
+
+ // The language target.
+ // One of: es2015, es2016, es2017, es2018, es2019, es2020 or esnext.
+ // Default is esnext.
+ Target string
+
+ // The output format.
+ // One of: iife, cjs, esm
+ // Default is to esm.
+ Format string
+
+ // External dependencies, e.g. "react".
+ Externals []string `hash:"set"`
+
+ // User defined symbols.
+ Defines map[string]interface{}
+
+ // User defined params. Will be marshaled to JSON and available as "@params", e.g.
+ // import * as params from '@params';
+ Params interface{}
+
+ // What to use instead of React.createElement.
+ JSXFactory string
+
+ // What to use instead of React.Fragment.
+ JSXFragment string
+
+ mediaType media.Type
+ outDir string
+ contents string
+ sourcefile string
+ resolveDir string
+ workDir string
+ tsConfig string
+}
+
+func decodeOptions(m map[string]interface{}) (Options, error) {
+ var opts Options
+
+ if err := mapstructure.WeakDecode(m, &opts); err != nil {
+ return opts, err
+ }
+
+ if opts.TargetPath != "" {
+ opts.TargetPath = helpers.ToSlashTrimLeading(opts.TargetPath)
+ }
+
+ opts.Target = strings.ToLower(opts.Target)
+ opts.Format = strings.ToLower(opts.Format)
+
+ return opts, nil
+}
+
+type importCache struct {
+ sync.RWMutex
+ m map[string]api.OnResolveResult
+}
+
+func createBuildPlugins(c *Client, opts Options) ([]api.Plugin, error) {
+ fs := c.rs.Assets
+
+ cache := importCache{
+ m: make(map[string]api.OnResolveResult),
+ }
+
+ resolveImport := func(args api.OnResolveArgs) (api.OnResolveResult, error) {
+ relDir := fs.MakePathRelative(args.ResolveDir)
+
+ if relDir == "" {
+ // Not in a Hugo Module, probably in node_modules.
+ return api.OnResolveResult{}, nil
+ }
+
+ impPath := args.Path
+
+ // stdin is the main entry file which already is at the relative root.
+ // Imports not starting with a "." is assumed to live relative to /assets.
+ // Hugo makes no assumptions about the directory structure below /assets.
+ if args.Importer != "<stdin>" && strings.HasPrefix(impPath, ".") {
+ impPath = filepath.Join(relDir, args.Path)
+ }
+
+ 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)
+ }
+
+ if m != nil {
+ // Store the source root so we can create a jsconfig.json
+ // to help intellisense when the build is done.
+ // This should be a small number of elements, and when
+ // in server mode, we may get stale entries on renames etc.,
+ // but that shouldn't matter too much.
+ c.rs.JSConfigBuilder.AddSourceRoot(m.SourceRoot())
+ return api.OnResolveResult{Path: m.Filename(), Namespace: ""}, nil
+ }
+
+ return api.OnResolveResult{}, nil
+ }
+
+ importResolver := api.Plugin{
+ Name: "hugo-import-resolver",
+ Setup: func(build api.PluginBuild) {
+ build.OnResolve(api.OnResolveOptions{Filter: `.*`},
+ func(args api.OnResolveArgs) (api.OnResolveResult, error) {
+ // Try cache first.
+ cache.RLock()
+ v, found := cache.m[args.Path]
+ cache.RUnlock()
+
+ if found {
+ return v, nil
+ }
+
+ imp, err := resolveImport(args)
+ if err != nil {
+ return imp, err
+ }
+
+ cache.Lock()
+ defer cache.Unlock()
+
+ cache.m[args.Path] = imp
+
+ return imp, nil
+
+ })
+ },
+ }
+
+ params := opts.Params
+ if params == nil {
+ // This way @params will always resolve to something.
+ params = make(map[string]interface{})
+ }
+
+ b, err := json.Marshal(params)
+ if err != nil {
+ return nil, errors.Wrap(err, "failed to marshal params")
+ }
+ bs := string(b)
+ paramsPlugin := api.Plugin{
+ Name: "hugo-params-plugin",
+ Setup: func(build api.PluginBuild) {
+ build.OnResolve(api.OnResolveOptions{Filter: `^@params$`},
+ func(args api.OnResolveArgs) (api.OnResolveResult, error) {
+ return api.OnResolveResult{
+ Path: args.Path,
+ Namespace: "params",
+ }, nil
+ })
+ build.OnLoad(api.OnLoadOptions{Filter: `.*`, Namespace: "params"},
+ func(args api.OnLoadArgs) (api.OnLoadResult, error) {
+ return api.OnLoadResult{
+ Contents: &bs,
+ Loader: api.LoaderJSON,
+ }, nil
+ })
+ },
+ }
+
+ return []api.Plugin{importResolver, paramsPlugin}, nil
+
+}
+
+func toBuildOptions(opts Options) (buildOptions api.BuildOptions, err error) {
+
+ var target api.Target
+ switch opts.Target {
+ case "", "esnext":
+ target = api.ESNext
+ case "es5":
+ target = api.ES5
+ case "es6", "es2015":
+ target = api.ES2015
+ case "es2016":
+ target = api.ES2016
+ case "es2017":
+ target = api.ES2017
+ case "es2018":
+ target = api.ES2018
+ case "es2019":
+ target = api.ES2019
+ case "es2020":
+ target = api.ES2020
+ default:
+ err = fmt.Errorf("invalid target: %q", opts.Target)
+ return
+ }
+
+ mediaType := opts.mediaType
+ if mediaType.IsZero() {
+ mediaType = media.JavascriptType
+ }
+
+ var loader api.Loader
+ switch mediaType.SubType {
+ // TODO(bep) ESBuild support a set of other loaders, but I currently fail
+ // to see the relevance. That may change as we start using this.
+ case media.JavascriptType.SubType:
+ loader = api.LoaderJS
+ case media.TypeScriptType.SubType:
+ loader = api.LoaderTS
+ case media.TSXType.SubType:
+ loader = api.LoaderTSX
+ case media.JSXType.SubType:
+ loader = api.LoaderJSX
+ default:
+ err = fmt.Errorf("unsupported Media Type: %q", opts.mediaType)
+ return
+ }
+
+ var format api.Format
+ // One of: iife, cjs, esm
+ switch opts.Format {
+ case "", "iife":
+ format = api.FormatIIFE
+ case "esm":
+ format = api.FormatESModule
+ case "cjs":
+ format = api.FormatCommonJS
+ default:
+ err = fmt.Errorf("unsupported script output format: %q", opts.Format)
+ return
+ }
+
+ var defines map[string]string
+ if opts.Defines != nil {
+ defines = cast.ToStringMapString(opts.Defines)
+ }
+
+ // By default we only need to specify outDir and no outFile
+ var outDir = opts.outDir
+ var outFile = ""
+ var sourceMap api.SourceMap
+ switch opts.SourceMap {
+ case "inline":
+ sourceMap = api.SourceMapInline
+ case "external":
+ // When doing external sourcemaps we should specify
+ // out file and no out dir
+ sourceMap = api.SourceMapExternal
+ outFile = filepath.Join(opts.workDir, opts.TargetPath)
+ outDir = ""
+ case "":
+ sourceMap = api.SourceMapNone
+ default:
+ err = fmt.Errorf("unsupported sourcemap type: %q", opts.SourceMap)
+ return
+ }
+
+ buildOptions = api.BuildOptions{
+ Outfile: outFile,
+ Bundle: true,
+
+ Target: target,
+ Format: format,
+ Sourcemap: sourceMap,
+
+ MinifyWhitespace: opts.Minify,
+ MinifyIdentifiers: opts.Minify,
+ MinifySyntax: opts.Minify,
+
+ Outdir: outDir,
+ Define: defines,
+
+ External: opts.Externals,
+
+ JSXFactory: opts.JSXFactory,
+ JSXFragment: opts.JSXFragment,
+
+ Tsconfig: opts.tsConfig,
+
+ Stdin: &api.StdinOptions{
+ Contents: opts.contents,
+ Sourcefile: opts.sourcefile,
+ ResolveDir: opts.resolveDir,
+ Loader: loader,
+ },
+ }
+ return
+
+}
diff --git a/resources/resource_transformers/js/options_test.go b/resources/resource_transformers/js/options_test.go
new file mode 100644
index 000000000..89d362ab9
--- /dev/null
+++ b/resources/resource_transformers/js/options_test.go
@@ -0,0 +1,105 @@
+// Copyright 2020 The Hugo Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package js
+
+import (
+ "testing"
+
+ "github.com/gohugoio/hugo/media"
+
+ "github.com/evanw/esbuild/pkg/api"
+
+ qt "github.com/frankban/quicktest"
+)
+
+// This test is added to test/warn against breaking the "stability" of the
+// cache key. It's sometimes needed to break this, but should be avoided if possible.
+func TestOptionKey(t *testing.T) {
+ c := qt.New(t)
+
+ opts := map[string]interface{}{
+ "TargetPath": "foo",
+ "Target": "es2018",
+ }
+
+ key := (&buildTransformation{optsm: opts}).Key()
+
+ c.Assert(key.Value(), qt.Equals, "jsbuild_7891849149754191852")
+}
+
+func TestToBuildOptions(t *testing.T) {
+ c := qt.New(t)
+
+ opts, err := toBuildOptions(Options{mediaType: media.JavascriptType})
+
+ c.Assert(err, qt.IsNil)
+ c.Assert(opts, qt.DeepEquals, api.BuildOptions{
+ Bundle: true,
+ Target: api.ESNext,
+ Format: api.FormatIIFE,
+ Stdin: &api.StdinOptions{
+ Loader: api.LoaderJS,
+ },
+ })
+
+ opts, err = toBuildOptions(Options{
+ Target: "es2018", Format: "cjs", Minify: true, mediaType: media.JavascriptType})
+ c.Assert(err, qt.IsNil)
+ c.Assert(opts, qt.DeepEquals, api.BuildOptions{
+ Bundle: true,
+ Target: api.ES2018,
+ Format: api.FormatCommonJS,
+ MinifyIdentifiers: true,
+ MinifySyntax: true,
+ MinifyWhitespace: true,
+ Stdin: &api.StdinOptions{
+ Loader: api.LoaderJS,
+ },
+ })
+
+ opts, err = toBuildOptions(Options{
+ Target: "es2018", Format: "cjs", Minify: true, mediaType: media.JavascriptType,
+ SourceMap: "inline"})
+ c.Assert(err, qt.IsNil)
+ c.Assert(opts, qt.DeepEquals, api.BuildOptions{
+ Bundle: true,
+ Target: api.ES2018,
+ Format: api.FormatCommonJS,
+ MinifyIdentifiers: true,
+ MinifySyntax: true,
+ MinifyWhitespace: true,
+ Sourcemap: api.SourceMapInline,
+ Stdin: &api.StdinOptions{
+ Loader: api.LoaderJS,
+ },
+ })
+
+ opts, err = toBuildOptions(Options{
+ Target: "es2018", Format: "cjs", Minify: true, mediaType: media.JavascriptType,
+ SourceMap: "external"})
+ c.Assert(err, qt.IsNil)
+ c.Assert(opts, qt.DeepEquals, api.BuildOptions{
+ Bundle: true,
+ Target: api.ES2018,
+ Format: api.FormatCommonJS,
+ MinifyIdentifiers: true,
+ MinifySyntax: true,
+ MinifyWhitespace: true,
+ Sourcemap: api.SourceMapExternal,
+ Stdin: &api.StdinOptions{
+ Loader: api.LoaderJS,
+ },
+ })
+
+}