diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2020-07-21 17:59:03 +0200 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2020-07-22 10:13:30 +0200 |
commit | 0256959a358bb26b983c9d9496862b0fdf387621 (patch) | |
tree | 35578577001fb8bf330cae72195251cd73ce777d | |
parent | eded9ac2a05b9a7244c25c70ca8f761b69b33385 (diff) |
resources/js: Add option for setting bundle format
Fixes #7503
-rw-r--r-- | docs/content/en/hugo-pipes/js.md | 5 | ||||
-rw-r--r-- | media/mediaType.go | 5 | ||||
-rw-r--r-- | resources/postpub/fields_test.go | 1 | ||||
-rw-r--r-- | resources/resource_transformers/js/build.go | 126 | ||||
-rw-r--r-- | resources/resource_transformers/js/build_test.go | 34 |
5 files changed, 129 insertions, 42 deletions
diff --git a/docs/content/en/hugo-pipes/js.md b/docs/content/en/hugo-pipes/js.md index f361adc45..465854f3a 100644 --- a/docs/content/en/hugo-pipes/js.md +++ b/docs/content/en/hugo-pipes/js.md @@ -45,6 +45,11 @@ defines [map] {{ $defines := dict "process.env.NODE_ENV" `"development"` }} ``` +format [string] {{< new-in "0.75.0" >}} +: The output format. + One of: `iife`, `cjs`, `esm`. + Default is `iife`, a self-executing function, suitable for inclusion as a <script> tag. + ### Examples ```go-html-template diff --git a/media/mediaType.go b/media/mediaType.go index 8a2efc4a4..21d4ddca5 100644 --- a/media/mediaType.go +++ b/media/mediaType.go @@ -378,6 +378,11 @@ func DecodeTypes(mms ...map[string]interface{}) (Types, error) { return m, nil } +// IsZero reports whether this Type represents a zero value. +func (m Type) IsZero() bool { + return m.SubType == "" +} + // MarshalJSON returns the JSON encoding of m. func (m Type) MarshalJSON() ([]byte, error) { type Alias Type diff --git a/resources/postpub/fields_test.go b/resources/postpub/fields_test.go index fa0c9190a..d67c7c9a9 100644 --- a/resources/postpub/fields_test.go +++ b/resources/postpub/fields_test.go @@ -32,6 +32,7 @@ func TestCreatePlaceholders(t *testing.T) { c.Assert(m, qt.DeepEquals, map[string]interface{}{ "FullSuffix": "pre_foo.FullSuffix_post", + "IsZero": "pre_foo.IsZero_post", "Type": "pre_foo.Type_post", "MainType": "pre_foo.MainType_post", "Delimiter": "pre_foo.Delimiter_post", diff --git a/resources/resource_transformers/js/build.go b/resources/resource_transformers/js/build.go index 488c6d1a4..8e0c7c130 100644 --- a/resources/resource_transformers/js/build.go +++ b/resources/resource_transformers/js/build.go @@ -33,8 +33,6 @@ import ( "github.com/gohugoio/hugo/resources/resource" ) -const defaultTarget = "esnext" - 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 @@ -49,6 +47,11 @@ type Options struct { // 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"` @@ -60,25 +63,29 @@ type Options struct { // What to use instead of React.Fragment. JSXFragment string + + mediaType media.Type + outDir string + contents string + sourcefile string + resolveDir string } -func decodeOptions(m map[string]interface{}) (opts Options, err error) { - err = mapstructure.WeakDecode(m, &opts) - if err != nil { - return +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) } - if opts.Target == "" { - opts.Target = defaultTarget - } - opts.Target = strings.ToLower(opts.Target) + opts.Format = strings.ToLower(opts.Format) - return + return opts, nil } type Client struct { @@ -114,9 +121,40 @@ func (t *buildTransformation) Transform(ctx *resources.ResourceTransformationCtx ctx.ReplaceOutPathExtension(".js") } + src, err := ioutil.ReadAll(ctx.From) + if err != nil { + return err + } + + sdir, sfile := path.Split(ctx.SourcePath) + opts.sourcefile = sfile + opts.resolveDir = t.sfs.RealFilename(sdir) + opts.contents = string(src) + opts.mediaType = ctx.InMediaType + + buildOptions, err := toBuildOptions(opts) + if err != nil { + return err + } + + result := api.Build(buildOptions) + if len(result.Errors) > 0 { + return fmt.Errorf("%s", result.Errors[0].Text) + } + ctx.To.Write(result.OutputFiles[0].Contents) + return nil +} + +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}, + ) +} + +func toBuildOptions(opts Options) (buildOptions api.BuildOptions, err error) { var target api.Target switch opts.Target { - case defaultTarget: + case "", "esnext": target = api.ESNext case "es5": target = api.ES5 @@ -133,11 +171,17 @@ func (t *buildTransformation) Transform(ctx *resources.ResourceTransformationCtx case "es2020": target = api.ES2020 default: - return fmt.Errorf("invalid target: %q", opts.Target) + err = fmt.Errorf("invalid target: %q", opts.Target) + return + } + + mediaType := opts.mediaType + if mediaType.IsZero() { + mediaType = media.JavascriptType } var loader api.Loader - switch ctx.InMediaType.SubType { + 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: @@ -149,29 +193,43 @@ func (t *buildTransformation) Transform(ctx *resources.ResourceTransformationCtx case media.JSXType.SubType: loader = api.LoaderJSX default: - return fmt.Errorf("unsupported Media Type: %q", ctx.InMediaType) - + err = fmt.Errorf("unsupported Media Type: %q", opts.mediaType) + return } - src, err := ioutil.ReadAll(ctx.From) - if err != nil { - return err + 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 + } - sdir, sfile := path.Split(ctx.SourcePath) - sdir = t.sfs.RealFilename(sdir) + var defines map[string]string + if opts.Defines != nil { + defines = cast.ToStringMapString(opts.Defines) + } - buildOptions := api.BuildOptions{ + buildOptions = api.BuildOptions{ Outfile: "", Bundle: true, Target: target, + Format: format, MinifyWhitespace: opts.Minify, MinifyIdentifiers: opts.Minify, MinifySyntax: opts.Minify, - Defines: cast.ToStringMapString(opts.Defines), + Outdir: opts.outDir, + Defines: defines, Externals: opts.Externals, @@ -181,26 +239,12 @@ func (t *buildTransformation) Transform(ctx *resources.ResourceTransformationCtx //Tsconfig: opts.TSConfig, Stdin: &api.StdinOptions{ - Contents: string(src), - Sourcefile: sfile, - ResolveDir: sdir, + Contents: opts.contents, + Sourcefile: opts.sourcefile, + ResolveDir: opts.resolveDir, Loader: loader, }, } - result := api.Build(buildOptions) - if len(result.Errors) > 0 { - return fmt.Errorf("%s", result.Errors[0].Text) - } - if len(result.OutputFiles) != 1 { - return fmt.Errorf("unexpected output count: %d", len(result.OutputFiles)) - } - - ctx.To.Write(result.OutputFiles[0].Contents) - return nil -} + return -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}, - ) } diff --git a/resources/resource_transformers/js/build_test.go b/resources/resource_transformers/js/build_test.go index 2e4c174f7..ee97dede5 100644 --- a/resources/resource_transformers/js/build_test.go +++ b/resources/resource_transformers/js/build_test.go @@ -16,6 +16,10 @@ package js import ( "testing" + "github.com/gohugoio/hugo/media" + + "github.com/evanw/esbuild/pkg/api" + qt "github.com/frankban/quicktest" ) @@ -26,9 +30,37 @@ func TestOptionKey(t *testing.T) { opts := map[string]interface{}{ "TargetPath": "foo", + "Target": "es2018", } key := (&buildTransformation{optsm: opts}).Key() - c.Assert(key.Value(), qt.Equals, "jsbuild_15565843046704064284") + 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{}, + }) + } |