ref: bf2837a314eaf70135791984a423b0b09f58741d
parent: cf6131dc18e5e833b021724a0d9bcdaa6827b8dd
author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
date: Wed Nov 4 11:13:37 EST 2020
js: Misc fixes * Fix resolve of package.json deps in submodules * Fix directory logic for writing assets/jsconfig.json Fixes #7924 Fixes #7923
--- a/go.mod
+++ b/go.mod
@@ -15,7 +15,7 @@
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
--- a/go.sum
+++ b/go.sum
@@ -170,6 +170,8 @@
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=
--- a/hugolib/hugo_sites_build.go
+++ b/hugolib/hugo_sites_build.go
@@ -354,26 +354,31 @@
// 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)
+ }
}
}
}
--- a/hugolib/js_test.go
+++ b/hugolib/js_test.go
@@ -176,12 +176,22 @@
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 @@
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!";`)
}
--- a/resources/resource_transformers/js/build.go
+++ b/resources/resource_transformers/js/build.go
@@ -18,14 +18,12 @@
"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 @@
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 @@
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
+ createErr := func(msg api.Message) error {
+ loc := msg.Location
+ path := loc.File
- if !strings.HasPrefix(path, "..") {
- // Try first in the assets fs
- var fi os.FileInfo
- fi, err = t.c.rs.BaseFs.Assets.Fs.Stat(path)
+ 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)
--- a/resources/resource_transformers/js/options.go
+++ b/resources/resource_transformers/js/options.go
@@ -16,6 +16,7 @@
import (
"encoding/json"
"fmt"
+ "io/ioutil"
"path/filepath"
"strings"
"sync"
@@ -31,6 +32,13 @@
"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 @@
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 @@
}
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 @@
// 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 @@
// 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 @@
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(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,