URI: 
       js: Misc fixes - hugo - [fork] hugo port for 9front
  HTML git clone git@git.drkhsh.at/hugo.git
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
   DIR README
   DIR LICENSE
       ---
   DIR commit bf2837a314eaf70135791984a423b0b09f58741d
   DIR parent cf6131dc18e5e833b021724a0d9bcdaa6827b8dd
  HTML Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
       Date:   Wed,  4 Nov 2020 16:13:37 +0100
       
       js: Misc fixes
       
       * Fix resolve of package.json deps in submodules
       * Fix directory logic for writing assets/jsconfig.json
       
       Fixes #7924
       Fixes #7923
       
       Diffstat:
         M go.mod                              |       2 +-
         M go.sum                              |       2 ++
         M hugolib/hugo_sites_build.go         |      41 +++++++++++++++++--------------
         M hugolib/js_test.go                  |      17 ++++++++++++++---
         M resources/resource_transformers/js… |      74 ++++++++++++++++++-------------
         M resources/resource_transformers/js… |      69 ++++++++++++++++++++++++++-----
       
       6 files changed, 142 insertions(+), 63 deletions(-)
       ---
   DIR diff --git a/go.mod b/go.mod
       @@ -15,7 +15,7 @@ require (
                github.com/bep/tmc v0.5.1
                github.com/disintegration/gift v1.2.1
                github.com/dustin/go-humanize v1.0.0
       -        github.com/evanw/esbuild v0.8.2
       +        github.com/evanw/esbuild v0.8.3
                github.com/fortytw2/leaktest v1.3.0
                github.com/frankban/quicktest v1.11.1
                github.com/fsnotify/fsnotify v1.4.9
   DIR diff --git a/go.sum b/go.sum
       @@ -170,6 +170,8 @@ 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/evanw/esbuild v0.8.3 h1:uPgAFhcGcNyMDrBnfUDcimt0N9AC9UsxeROkC8C27os=
       +github.com/evanw/esbuild v0.8.3/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=
   DIR diff --git a/hugolib/hugo_sites_build.go b/hugolib/hugo_sites_build.go
       @@ -354,26 +354,31 @@ func (h *HugoSites) postProcess() error {
                // 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 {
       +                fi, err := h.BaseFs.Assets.Fs.Stat("")
       +                if err != nil {
       +                        h.Log.Warnf("Failed to resolve jsconfig.json dir: %s", err)
       +                } else {
       +                        m := fi.(hugofs.FileMetaInfo).Meta()
       +                        assetsDir := m.SourceRoot()
       +                        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)
       +                                        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)
       +                                        } 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)
       +                                                }
                                                }
                                        }
                                }
   DIR diff --git a/hugolib/js_test.go b/hugolib/js_test.go
       @@ -176,12 +176,22 @@ path="github.com/gohugoio/hugoTestProjectJSModImports"
                
        go 1.15
                
       -require github.com/gohugoio/hugoTestProjectJSModImports v0.3.0 // indirect
       +require github.com/gohugoio/hugoTestProjectJSModImports v0.5.0 // indirect
        
        `)
        
                b.WithContent("p1.md", "").WithNothingAdded()
        
       +        b.WithSourceFile("package.json", `{
       + "dependencies": {
       +  "date-fns": "^2.16.1"
       + }
       +}`)
       +
       +        b.Assert(os.Chdir(workDir), qt.IsNil)
       +        _, err = exec.Command("npm", "install").CombinedOutput()
       +        b.Assert(err, qt.IsNil)
       +
                b.Build(BuildCfg{})
        
                b.AssertFileContent("public/js/main.js", `
       @@ -189,8 +199,9 @@ 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";
       +Hello3 from mod2. Date from date-fns: ${today}
       +Hello from lib in the main project
       +Hello5 from mod2.
        var myparam = "Hugo Rocks!";`)
        
        }
   DIR diff --git a/resources/resource_transformers/js/build.go b/resources/resource_transformers/js/build.go
       @@ -18,14 +18,12 @@ import (
                "fmt"
                "io/ioutil"
                "os"
       -        "path"
       -        "path/filepath"
                "strings"
        
       -        "github.com/gohugoio/hugo/hugofs"
       -
                "github.com/spf13/afero"
        
       +        "github.com/gohugoio/hugo/hugofs"
       +
                "github.com/gohugoio/hugo/common/herrors"
        
                "github.com/gohugoio/hugo/hugolib/filesystems"
       @@ -79,10 +77,9 @@ func (t *buildTransformation) Transform(ctx *resources.ResourceTransformationCtx
                        return err
                }
        
       -        sdir, _ := path.Split(ctx.SourcePath)
                opts.sourcefile = ctx.SourcePath
       -        opts.resolveDir = t.c.sfs.RealFilename(sdir)
                opts.workDir = t.c.rs.WorkingDir
       +        opts.resolveDir = opts.workDir
                opts.contents = string(src)
                opts.mediaType = ctx.InMediaType
        
       @@ -99,39 +96,54 @@ func (t *buildTransformation) Transform(ctx *resources.ResourceTransformationCtx
                result := api.Build(buildOptions)
        
                if len(result.Errors) > 0 {
       -                first := result.Errors[0]
       -                loc := first.Location
       -                path := loc.File
       -
       -                var err error
       -                var f afero.File
       -                var filename string
       -
       -                if !strings.HasPrefix(path, "..") {
       -                        // Try first in the assets fs
       -                        var fi os.FileInfo
       -                        fi, err = t.c.rs.BaseFs.Assets.Fs.Stat(path)
       +
       +                createErr := func(msg api.Message) error {
       +                        loc := msg.Location
       +                        path := loc.File
       +
       +                        var (
       +                                f   afero.File
       +                                err error
       +                        )
       +
       +                        if strings.HasPrefix(path, nsImportHugo) {
       +                                path = strings.TrimPrefix(path, nsImportHugo+":")
       +                                f, err = hugofs.Os.Open(path)
       +                        } else {
       +                                var fi os.FileInfo
       +                                fi, err = t.c.sfs.Fs.Stat(path)
       +                                if err == nil {
       +                                        m := fi.(hugofs.FileMetaInfo).Meta()
       +                                        path = m.Filename()
       +                                        f, err = m.Open()
       +                                }
       +
       +                        }
       +
                                if err == nil {
       -                                m := fi.(hugofs.FileMetaInfo).Meta()
       -                                filename = m.Filename()
       -                                f, err = m.Open()
       +                                fe := herrors.NewFileError("js", 0, loc.Line, loc.Column, errors.New(msg.Text))
       +                                err, _ := herrors.WithFileContext(fe, path, f, herrors.SimpleLineMatcher)
       +                                f.Close()
       +                                return err
                                }
       +
       +                        return fmt.Errorf("%s", msg.Text)
                        }
        
       -                if f == nil {
       -                        path = filepath.Join(t.c.rs.WorkingDir, path)
       -                        filename = path
       -                        f, err = t.c.rs.Fs.Os.Open(path)
       +                var errors []error
       +
       +                for _, msg := range result.Errors {
       +                        errors = append(errors, createErr(msg))
                        }
        
       -                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
       +                // Return 1, log the rest.
       +                for i, err := range errors {
       +                        if i > 0 {
       +                                t.c.rs.Logger.Errorf("js.Build failed: %s", err)
       +                        }
                        }
        
       -                return fmt.Errorf("%s", result.Errors[0].Text)
       +                return errors[0]
                }
        
                ctx.To.Write(result.OutputFiles[0].Contents)
   DIR diff --git a/resources/resource_transformers/js/options.go b/resources/resource_transformers/js/options.go
       @@ -16,6 +16,7 @@ package js
        import (
                "encoding/json"
                "fmt"
       +        "io/ioutil"
                "path/filepath"
                "strings"
                "sync"
       @@ -31,6 +32,13 @@ import (
                "github.com/spf13/cast"
        )
        
       +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.
       @@ -111,6 +119,26 @@ type importCache struct {
                m map[string]api.OnResolveResult
        }
        
       +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 createBuildPlugins(c *Client, opts Options) ([]api.Plugin, error) {
                fs := c.rs.Assets
        
       @@ -119,20 +147,21 @@ func createBuildPlugins(c *Client, opts Options) ([]api.Plugin, error) {
                }
        
                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
       +                isStdin := args.Importer == stdinImporter
       +                var relDir string
       +                if !isStdin {
       +                        relDir = filepath.Dir(fs.MakePathRelative(args.Importer))
       +                } else {
       +                        relDir = filepath.Dir(opts.sourcefile)
                        }
        
                        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)
       +                if relDir != "" && strings.HasPrefix(impPath, ".") {
       +                        impPath = filepath.Join(relDir, impPath)
                        }
        
                        findFirst := func(base string) hugofs.FileMeta {
       @@ -164,6 +193,7 @@ func createBuildPlugins(c *Client, opts Options) ([]api.Plugin, error) {
                                // 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
       @@ -172,9 +202,11 @@ func createBuildPlugins(c *Client, opts Options) ([]api.Plugin, error) {
                                // 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{Path: m.Filename(), Namespace: nsImportHugo}, nil
                        }
        
       +                // Not found in /assets. Probably in node_modules. ESBuild will handle that
       +                // rather complex logic.
                        return api.OnResolveResult{}, nil
                }
        
       @@ -205,6 +237,23 @@ func createBuildPlugins(c *Client, opts Options) ([]api.Plugin, error) {
                                                return imp, nil
        
                                        })
       +                        build.OnLoad(api.OnLoadOptions{Filter: `.*`, Namespace: nsImportHugo},
       +                                func(args api.OnLoadArgs) (api.OnLoadResult, error) {
       +                                        b, err := ioutil.ReadFile(args.Path)
       +
       +                                        if err != nil {
       +                                                return api.OnLoadResult{}, errors.Wrapf(err, "failed to read %q", args.Path)
       +                                        }
       +                                        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
       +                                })
                        },
                }
        
       @@ -226,10 +275,10 @@ func createBuildPlugins(c *Client, opts Options) ([]api.Plugin, error) {
                                        func(args api.OnResolveArgs) (api.OnResolveResult, error) {
                                                return api.OnResolveResult{
                                                        Path:      args.Path,
       -                                                Namespace: "params",
       +                                                Namespace: nsParams,
                                                }, nil
                                        })
       -                        build.OnLoad(api.OnLoadOptions{Filter: `.*`, Namespace: "params"},
       +                        build.OnLoad(api.OnLoadOptions{Filter: `.*`, Namespace: nsParams},
                                        func(args api.OnLoadArgs) (api.OnLoadResult, error) {
                                                return api.OnLoadResult{
                                                        Contents: &bs,