Make js.Build fully support modules

Fixes #7816
Fixes #7777
Fixes #7916
This commit is contained in:
Bjørn Erik Pedersen 2020-10-05 13:34:14 +02:00
parent 3089fc0ba1
commit 85e4dd7370
22 changed files with 949 additions and 988 deletions

View file

@ -65,9 +65,8 @@ install:
script:
- go mod download
- go mod verify
- travis_wait 20 mage -v test
- >
if [ "$TRAVIS_ARCH" = "amd64" ]; then
- mage -v test
- if [ "$TRAVIS_ARCH" = "amd64" ]; then
mage -v check;
else
HUGO_TIMEOUT=30000 mage -v check;

View file

@ -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 {

View file

@ -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 {

4
deps/deps.go vendored
View file

@ -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

View file

@ -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" >}}

View file

@ -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.
@ -52,6 +66,50 @@ format [string] {{< new-in "0.74.3" >}}
One of: `iife`, `cjs`, `esm`.
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
```go-html-template
@ -70,6 +128,7 @@ Or with options:
```
#### 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`:

2
go.mod
View file

@ -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

100
go.sum
View file

@ -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=

View file

@ -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)
}

View file

@ -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

View file

@ -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)
@ -310,6 +324,7 @@ func newHugoSites(cfg deps.DepsCfg, sites ...*Site) (*HugoSites, error) {
Sites: sites,
workers: workers,
numWorkers: numWorkers,
skipRebuildForFilenames: make(map[string]bool),
init: &hugoSitesInit{
data: lazy.New(),
layouts: lazy.New(),

View file

@ -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
}

View file

@ -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")
workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-test-js-mod")
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())
config := fmt.Sprintf(`
baseURL = "https://example.org"
workingDir = %q
b.Fs = hugofs.NewDefault(v)
b.WithWorkingDir(workDir)
b.WithViper(v)
b.WithContent("p1.md", "")
disableKinds = ["page", "section", "term", "taxonomy"]
b.WithTemplates("index.html", `
{{ $js := resources.Get "js/main.js" | js.Build }}
JS: {{ template "print" $js }}
[module]
[[module.imports]]
path="github.com/gohugoio/hugoTestProjectJSModImports"
{{ 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")
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)
`, workDir)
b := newTestSitesBuilder(t)
b.Fs = fs
b.WithLogger(loggers.NewWarningLogger())
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
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)
}
go 1.15
realWrite("config.toml", `
baseURL="https://example.org"
require github.com/gohugoio/hugoTestProjectJSModImports v0.3.0 // indirect
[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"
`)
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") }}
{{- $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") }}
{{- 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!";`)
}

View file

@ -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)
}
}

View file

@ -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),
},
}
}

View file

@ -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)
}

View file

@ -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)
}
}
}

View file

@ -23,6 +23,8 @@ import (
"strings"
"sync"
"github.com/gohugoio/hugo/resources/jsconfig"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/config"
@ -86,7 +88,10 @@ func NewSpec(
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) {

View file

@ -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")
if err != nil {
return err
}
filesToDelete := make([]*os.File, 0)
defer func() {
for _, file := range filesToDelete {
os.Remove(file.Name())
}
}()
filesToDelete = append(filesToDelete, newTSConfig)
configDir, _ := filepath.Split(newTSConfig.Name())
// 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 err == nil {
innerTsConfig = tryTsConfig
baseURLAbs = tsDir
break
}
} 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)
}
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 {
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
}
buildOptions.Plugins, err = createBuildPlugins(t.c, opts)
if err != nil {
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)
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 {
m := fi.(hugofs.FileMetaInfo).Meta()
filename = m.Filename()
f, err = m.Open()
}
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)
}
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 {
if f == nil {
path = filepath.Join(t.c.rs.WorkingDir, path)
filename = path
f, err = t.c.rs.Fs.Os.Open(path)
}
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
}
ctx.PublishSourceMap(string(result.OutputFiles[0].Contents))
} else {
ctx.To.Write(result.OutputFiles[0].Contents)
return fmt.Errorf("%s", result.Errors[0].Text)
}
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
}

View file

@ -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{},
})
}

View file

@ -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
}

View file

@ -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,
},
})
}