aboutsummaryrefslogtreecommitdiffhomepage
path: root/resources/resource_transformers/js/options.go
diff options
context:
space:
mode:
Diffstat (limited to 'resources/resource_transformers/js/options.go')
-rw-r--r--resources/resource_transformers/js/options.go461
1 files changed, 0 insertions, 461 deletions
diff --git a/resources/resource_transformers/js/options.go b/resources/resource_transformers/js/options.go
deleted file mode 100644
index 8c271d032..000000000
--- a/resources/resource_transformers/js/options.go
+++ /dev/null
@@ -1,461 +0,0 @@
-// 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"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/gohugoio/hugo/common/maps"
- "github.com/gohugoio/hugo/common/paths"
- "github.com/gohugoio/hugo/identity"
- "github.com/spf13/afero"
-
- "github.com/evanw/esbuild/pkg/api"
-
- "github.com/gohugoio/hugo/hugofs"
- "github.com/gohugoio/hugo/media"
- "github.com/mitchellh/mapstructure"
-)
-
-const (
- nsImportHugo = "ns-hugo"
- nsParams = "ns-params"
-
- stdinImporter = "<stdin>"
-)
-
-// 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
-
- // This option allows you to automatically replace a global variable with an import from another file.
- // The filenames must be relative to /assets.
- // See https://esbuild.github.io/api/#inject
- Inject []string
-
- // User defined symbols.
- Defines map[string]any
-
- // Maps a component import to another.
- Shims map[string]string
-
- // User defined params. Will be marshaled to JSON and available as "@params", e.g.
- // import * as params from '@params';
- Params any
-
- // What to use instead of React.createElement.
- JSXFactory string
-
- // What to use instead of React.Fragment.
- JSXFragment string
-
- // What to do about JSX syntax.
- // See https://esbuild.github.io/api/#jsx
- JSX string
-
- // Which library to use to automatically import JSX helper functions from. Only works if JSX is set to automatic.
- // See https://esbuild.github.io/api/#jsx-import-source
- JSXImportSource string
-
- // There is/was a bug in WebKit with severe performance issue with the tracking
- // of TDZ checks in JavaScriptCore.
- //
- // Enabling this flag removes the TDZ and `const` assignment checks and
- // may improve performance of larger JS codebases until the WebKit fix
- // is in widespread use.
- //
- // See https://bugs.webkit.org/show_bug.cgi?id=199866
- // Deprecated: This no longer have any effect and will be removed.
- // TODO(bep) remove. See https://github.com/evanw/esbuild/commit/869e8117b499ca1dbfc5b3021938a53ffe934dba
- AvoidTDZ bool
-
- mediaType media.Type
- outDir string
- contents string
- sourceDir string
- resolveDir string
- tsConfig string
-}
-
-func decodeOptions(m map[string]any) (Options, error) {
- var opts Options
-
- if err := mapstructure.WeakDecode(m, &opts); err != nil {
- return opts, err
- }
-
- if opts.TargetPath != "" {
- opts.TargetPath = paths.ToSlashTrimLeading(opts.TargetPath)
- }
-
- opts.Target = strings.ToLower(opts.Target)
- opts.Format = strings.ToLower(opts.Format)
-
- return opts, nil
-}
-
-var extensionToLoaderMap = map[string]api.Loader{
- ".js": api.LoaderJS,
- ".mjs": api.LoaderJS,
- ".cjs": api.LoaderJS,
- ".jsx": api.LoaderJSX,
- ".ts": api.LoaderTS,
- ".tsx": api.LoaderTSX,
- ".css": api.LoaderCSS,
- ".json": api.LoaderJSON,
- ".txt": api.LoaderText,
-}
-
-func loaderFromFilename(filename string) api.Loader {
- l, found := extensionToLoaderMap[filepath.Ext(filename)]
- if found {
- return l
- }
- return api.LoaderJS
-}
-
-func resolveComponentInAssets(fs afero.Fs, impPath string) *hugofs.FileMeta {
- 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 strings.HasSuffix(impPath, ext) {
- // Import of foo.js.js need the full name.
- continue
- }
- if fi, err := fs.Stat(base + ext); err == nil {
- return fi.(hugofs.FileMetaInfo).Meta()
- }
- }
-
- // Not found.
- return nil
- }
-
- var m *hugofs.FileMeta
-
- // We need to check if this is a regular file imported without an extension.
- // There may be ambiguous situations where both foo.js and foo/index.js exists.
- // This import order is in line with both how Node and ESBuild's native
- // import resolver works.
-
- // It may be a regular file imported without an extension, e.g.
- // foo or foo/index.
- m = findFirst(impPath)
- if m != nil {
- return m
- }
-
- base := filepath.Base(impPath)
- if base == "index" {
- // try index.esm.js etc.
- m = findFirst(impPath + ".esm")
- if m != nil {
- return m
- }
- }
-
- // Check the path as is.
- fi, err := fs.Stat(impPath)
-
- if err == nil {
- if fi.IsDir() {
- m = findFirst(filepath.Join(impPath, "index"))
- if m == nil {
- m = findFirst(filepath.Join(impPath, "index.esm"))
- }
- } else {
- m = fi.(hugofs.FileMetaInfo).Meta()
- }
- } else if strings.HasSuffix(base, ".js") {
- m = findFirst(strings.TrimSuffix(impPath, ".js"))
- }
-
- return m
-}
-
-func createBuildPlugins(depsManager identity.Manager, c *Client, opts Options) ([]api.Plugin, error) {
- fs := c.rs.Assets
-
- resolveImport := func(args api.OnResolveArgs) (api.OnResolveResult, error) {
- impPath := args.Path
- if opts.Shims != nil {
- override, found := opts.Shims[impPath]
- if found {
- impPath = override
- }
- }
- isStdin := args.Importer == stdinImporter
- var relDir string
- if !isStdin {
- rel, found := fs.MakePathRelative(args.Importer, true)
- if !found {
- // Not in any of the /assets folders.
- // This is an import from a node_modules, let
- // ESBuild resolve this.
- return api.OnResolveResult{}, nil
- }
-
- relDir = filepath.Dir(rel)
- } else {
- relDir = opts.sourceDir
- }
-
- // Imports not starting with a "." is assumed to live relative to /assets.
- // Hugo makes no assumptions about the directory structure below /assets.
- if relDir != "" && strings.HasPrefix(impPath, ".") {
- impPath = filepath.Join(relDir, impPath)
- }
-
- m := resolveComponentInAssets(fs.Fs, impPath)
-
- if m != nil {
- depsManager.AddIdentity(m.PathInfo)
-
- // 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: nsImportHugo}, nil
- }
-
- // Fall back to ESBuild's resolve.
- 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) {
- return resolveImport(args)
- })
- build.OnLoad(api.OnLoadOptions{Filter: `.*`, Namespace: nsImportHugo},
- func(args api.OnLoadArgs) (api.OnLoadResult, error) {
- b, err := os.ReadFile(args.Path)
- if err != nil {
- return api.OnLoadResult{}, fmt.Errorf("failed to read %q: %w", args.Path, err)
- }
- c := string(b)
- return api.OnLoadResult{
- // See https://github.com/evanw/esbuild/issues/502
- // This allows all modules to resolve dependencies
- // in the main project's node_modules.
- ResolveDir: opts.resolveDir,
- Contents: &c,
- Loader: loaderFromFilename(args.Path),
- }, nil
- })
- },
- }
-
- params := opts.Params
- if params == nil {
- // This way @params will always resolve to something.
- params = make(map[string]any)
- }
-
- b, err := json.Marshal(params)
- if err != nil {
- return nil, fmt.Errorf("failed to marshal params: %w", err)
- }
- 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: nsParams,
- }, nil
- })
- build.OnLoad(api.OnLoadOptions{Filter: `.*`, Namespace: nsParams},
- 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
- case "es2021":
- target = api.ES2021
- case "es2022":
- target = api.ES2022
- case "es2023":
- target = api.ES2023
- default:
- err = fmt.Errorf("invalid target: %q", opts.Target)
- return
- }
-
- mediaType := opts.mediaType
- if mediaType.IsZero() {
- mediaType = media.Builtin.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.Builtin.JavascriptType.SubType:
- loader = api.LoaderJS
- case media.Builtin.TypeScriptType.SubType:
- loader = api.LoaderTS
- case media.Builtin.TSXType.SubType:
- loader = api.LoaderTSX
- case media.Builtin.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 jsx api.JSX
- switch opts.JSX {
- case "", "transform":
- jsx = api.JSXTransform
- case "preserve":
- jsx = api.JSXPreserve
- case "automatic":
- jsx = api.JSXAutomatic
- default:
- err = fmt.Errorf("unsupported jsx type: %q", opts.JSX)
- return
- }
-
- var defines map[string]string
- if opts.Defines != nil {
- defines = maps.ToStringMapString(opts.Defines)
- }
-
- // By default we only need to specify outDir and no outFile
- outDir := opts.outDir
- outFile := ""
- var sourceMap api.SourceMap
- switch opts.SourceMap {
- case "inline":
- sourceMap = api.SourceMapInline
- case "external":
- sourceMap = api.SourceMapExternal
- 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,
-
- JSX: jsx,
- JSXImportSource: opts.JSXImportSource,
-
- Tsconfig: opts.tsConfig,
-
- // Note: We're not passing Sourcefile to ESBuild.
- // This makes ESBuild pass `stdin` as the Importer to the import
- // resolver, which is what we need/expect.
- Stdin: &api.StdinOptions{
- Contents: opts.contents,
- ResolveDir: opts.resolveDir,
- Loader: loader,
- },
- }
- return
-}