Skip to content

Commit

Permalink
pipes: Add external source map support to js.Build and Babel
Browse files Browse the repository at this point in the history
Fixes #8132
  • Loading branch information
richtera authored Jan 18, 2021
1 parent 0004a73 commit 2c8b5d9
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 4 deletions.
8 changes: 6 additions & 2 deletions hugolib/js_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,16 @@ document.body.textContent = greeter(user);`
JS: {{ template "print" $js }}
{{ $jsx := resources.Get "js/myjsx.jsx" | js.Build $options }}
JSX: {{ template "print" $jsx }}
{{ $ts := resources.Get "js/myts.ts" | js.Build }}
{{ $ts := resources.Get "js/myts.ts" | js.Build (dict "sourcemap" "inline")}}
TS: {{ template "print" $ts }}
{{ $ts2 := resources.Get "js/myts.ts" | js.Build (dict "sourcemap" "external" "TargetPath" "js/myts2.js")}}
TS2: {{ template "print" $ts2 }}
{{ define "print" }}RelPermalink: {{.RelPermalink}}|MIME: {{ .MediaType }}|Content: {{ .Content | safeJS }}{{ end }}
`)

jsDir := filepath.Join(workDir, "assets", "js")
fmt.Println(workDir)
b.Assert(os.MkdirAll(jsDir, 0777), qt.IsNil)
b.Assert(os.Chdir(workDir), qt.IsNil)
b.WithSourceFile("package.json", packageJSON)
Expand All @@ -133,6 +135,8 @@ TS: {{ template "print" $ts }}

b.Build(BuildCfg{})

b.AssertFileContent("public/js/myts.js", `//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJz`)
b.AssertFileContent("public/js/myts2.js.map", `"version": 3,`)
b.AssertFileContent("public/index.html", `
console.log("included");
if (hasSpace.test(string))
Expand Down
33 changes: 33 additions & 0 deletions hugolib/resource_chain_babel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ class Car {
this.carname = brand;
}
}
`

js2 := `
/* A Car2 */
class Car2 {
constructor(brand) {
this.carname = brand;
}
}
`

workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-test-babel")
Expand All @@ -103,11 +112,18 @@ class Car {
{{ $transpiled := resources.Get "js/main.js" | babel -}}
Transpiled: {{ $transpiled.Content | safeJS }}
{{ $transpiled := resources.Get "js/main2.js" | babel (dict "sourceMap" "inline") -}}
Transpiled2: {{ $transpiled.Content | safeJS }}
{{ $transpiled := resources.Get "js/main2.js" | babel (dict "sourceMap" "external") -}}
Transpiled3: {{ $transpiled.Permalink }}
`)

jsDir := filepath.Join(workDir, "assets", "js")
b.Assert(os.MkdirAll(jsDir, 0777), qt.IsNil)
b.WithSourceFile("assets/js/main.js", js)
b.WithSourceFile("assets/js/main2.js", js2)
b.WithSourceFile("package.json", packageJSON)
b.WithSourceFile("babel.config.js", babelConfig)

Expand All @@ -129,4 +145,21 @@ var Car = function Car(brand) {
this.carname = brand;
};
`)
b.AssertFileContent("public/index.html", `
var Car2 = function Car2(brand) {
_classCallCheck(this, Car2);
this.carname = brand;
};
`)
b.AssertFileContent("public/js/main2.js", `
var Car2 = function Car2(brand) {
_classCallCheck(this, Car2);
this.carname = brand;
};
`)
b.AssertFileContent("public/js/main2.js.map", `{"version":3,`)
b.AssertFileContent("public/index.html", `
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozL`)
}
51 changes: 50 additions & 1 deletion resources/resource_transformers/babel/babel.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ package babel
import (
"bytes"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
"regexp"
"strconv"

"github.com/cli/safeexec"
Expand All @@ -43,8 +47,10 @@ type Options struct {
Compact *bool
Verbose bool
NoBabelrc bool
SourceMap string
}

// DecodeOptions decodes options to and generates command flags
func DecodeOptions(m map[string]interface{}) (opts Options, err error) {
if m == nil {
return
Expand All @@ -56,6 +62,14 @@ func DecodeOptions(m map[string]interface{}) (opts Options, err error) {
func (opts Options) toArgs() []string {
var args []string

// external is not a known constant on the babel command line
// .sourceMaps must be a boolean, "inline", "both", or undefined
switch opts.SourceMap {
case "external":
args = append(args, "--source-maps")
case "inline":
args = append(args, "--source-maps=inline")
}
if opts.Minified {
args = append(args, "--minified")
}
Expand Down Expand Up @@ -141,6 +155,8 @@ func (t *babelTransformation) Transform(ctx *resources.ResourceTransformationCtx
}
}

ctx.ReplaceOutPathExtension(".js")

var cmdArgs []string

if configFile != "" {
Expand All @@ -153,13 +169,24 @@ func (t *babelTransformation) Transform(ctx *resources.ResourceTransformationCtx
}
cmdArgs = append(cmdArgs, "--filename="+ctx.SourcePath)

// Create compile into a real temp file:
// 1. separate stdout/stderr messages from babel (https://github.com/gohugoio/hugo/issues/8136)
// 2. allow generation and retrieval of external source map.
compileOutput, err := ioutil.TempFile("", "compileOut-*.js")
if err != nil {
return err
}

cmdArgs = append(cmdArgs, "--out-file="+compileOutput.Name())
defer os.Remove(compileOutput.Name())

cmd, err := hexec.SafeCommand(binary, cmdArgs...)
if err != nil {
return err
}

cmd.Stdout = ctx.To
cmd.Stderr = io.MultiWriter(infoW, &errBuf)
cmd.Stdout = cmd.Stderr
cmd.Env = hugo.GetExecEnviron(t.rs.WorkingDir, t.rs.Cfg, t.rs.BaseFs.Assets.Fs)

stdin, err := cmd.StdinPipe()
Expand All @@ -177,6 +204,28 @@ func (t *babelTransformation) Transform(ctx *resources.ResourceTransformationCtx
return errors.Wrap(err, errBuf.String())
}

content, err := ioutil.ReadAll(compileOutput)
if err != nil {
return err
}

mapFile := compileOutput.Name() + ".map"
if _, err := os.Stat(mapFile); err == nil {
defer os.Remove(mapFile)
sourceMap, err := ioutil.ReadFile(mapFile)
if err != nil {
return err
}
if err = ctx.PublishSourceMap(string(sourceMap)); err != nil {
return err
}
targetPath := path.Base(ctx.OutPath) + ".map"
re := regexp.MustCompile(`//# sourceMappingURL=.*\n?`)
content = []byte(re.ReplaceAllString(string(content), "//# sourceMappingURL="+targetPath+"\n"))
}

ctx.To.Write(content)

return nil
}

Expand Down
30 changes: 29 additions & 1 deletion resources/resource_transformers/js/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"fmt"
"io/ioutil"
"os"
"path"
"regexp"
"strings"

"github.com/spf13/afero"
Expand Down Expand Up @@ -92,6 +94,14 @@ func (t *buildTransformation) Transform(ctx *resources.ResourceTransformationCtx
return err
}

if buildOptions.Sourcemap == api.SourceMapExternal && buildOptions.Outdir == "" {
buildOptions.Outdir, err = ioutil.TempDir(os.TempDir(), "compileOutput")
if err != nil {
return err
}
defer os.Remove(buildOptions.Outdir)
}

result := api.Build(buildOptions)

if len(result.Errors) > 0 {
Expand Down Expand Up @@ -145,7 +155,25 @@ func (t *buildTransformation) Transform(ctx *resources.ResourceTransformationCtx
return errors[0]
}

ctx.To.Write(result.OutputFiles[0].Contents)
if buildOptions.Sourcemap == api.SourceMapExternal {
content := string(result.OutputFiles[1].Contents)
symPath := path.Base(ctx.OutPath) + ".map"
re := regexp.MustCompile(`//# sourceMappingURL=.*\n?`)
content = re.ReplaceAllString(content, "//# sourceMappingURL="+symPath+"\n")

if err = ctx.PublishSourceMap(string(result.OutputFiles[0].Contents)); err != nil {
return err
}
_, err := ctx.To.Write([]byte(content))
if err != nil {
return err
}
} else {
_, err := ctx.To.Write(result.OutputFiles[0].Contents)
if err != nil {
return err
}
}
return nil
}

Expand Down
2 changes: 2 additions & 0 deletions resources/resource_transformers/js/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,8 @@ func toBuildOptions(opts Options) (buildOptions api.BuildOptions, err error) {
switch opts.SourceMap {
case "inline":
sourceMap = api.SourceMapInline
case "external":
sourceMap = api.SourceMapExternal
case "":
sourceMap = api.SourceMapNone
default:
Expand Down
18 changes: 18 additions & 0 deletions resources/resource_transformers/js/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,22 @@ func TestToBuildOptions(t *testing.T) {
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,
},
})
}

0 comments on commit 2c8b5d9

Please sign in to comment.