diff --git a/common/hugo/hugo.go b/common/hugo/hugo.go index 03788083698..6e07f69c3b8 100644 --- a/common/hugo/hugo.go +++ b/common/hugo/hugo.go @@ -16,6 +16,9 @@ package hugo import ( "fmt" "html/template" + "os" + + "github.com/gohugoio/hugo/config" ) const ( @@ -69,3 +72,9 @@ func NewInfo(environment string) Info { Environment: environment, } } + +func GetExecEnviron(cfg config.Provider) []string { + env := os.Environ() + config.SetEnvVars(&env, "HUGO_ENVIRONMENT", cfg.GetString("environment")) + return env +} diff --git a/docs/content/en/hugo-pipes/babel.md b/docs/content/en/hugo-pipes/babel.md new file mode 100755 index 00000000000..e34faec4399 --- /dev/null +++ b/docs/content/en/hugo-pipes/babel.md @@ -0,0 +1,55 @@ +--- +title: Babel +description: Hugo Pipes can process JS files with Babel. +date: 2019-03-21 +publishdate: 2019-03-21 +lastmod: 2019-03-21 +categories: [asset management] +keywords: [] +menu: + docs: + parent: "pipes" + weight: 49 +weight: 49 +sections_weight: 49 +draft: false +--- + +Any JavaScript resource file can be transpiled to another JavaScript version using `resources.Babel` which takes for argument the resource object and an optional dict of options listed below. Babel uses the [babel cli](https://babeljs.io/docs/en/babel-cli). + + +{{% note %}} +Hugo Pipe's Babel requires the `@babel/cli` and `@babel/core` JavaScript packages to be installed in the project or globally (`npm install -g @babel/cli @babel/core`) along with any Babel plugin(s) or preset(s) used (e.g., `npm install @babel/preset-env --save-dev`). + +If you are using the Hugo Snap package, Babel and plugin(s) need to be installed locally within your Hugo site directory, e.g., `npm install @babel/cli @babel/core --save-dev` without the `-g` flag. +{{% /note %}} + +### Options + +config [string] +: Path to the Babel configuration file. Hugo will, by default, look for a `babel.config.js` in your project. More information on these configuration files can be found here: [babel configuration](https://babeljs.io/docs/en/configuration). + +minified [bool] +: Save as much bytes as possible when printing + +noComments [bool] +: Write comments to generated output (true by default) + +compact [bool] +: Do not include superfluous whitespace characters and line terminators. Defaults to `auto` if not set. + +verbose [bool] +: Log everything + +### Examples + +```go-html-template +{{- $transpiled := resources.Get "scripts/main.js" | babel -}} +``` + +Or with options: + +```go-html-template +{{ $opts := dict "noComments" true }} +{{- $transpiled := resources.Get "scripts/main.js" | babel $opts -}} +``` diff --git a/docs/content/en/hugo-pipes/transformjs.md b/docs/content/en/hugo-pipes/transformjs.md deleted file mode 100755 index 2a25946115c..00000000000 --- a/docs/content/en/hugo-pipes/transformjs.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -title: TransformJS -description: Hugo Pipes can process JS files with Babel. -date: 2019-03-21 -publishdate: 2019-03-21 -lastmod: 2019-03-21 -categories: [asset management] -keywords: [] -menu: - docs: - parent: "pipes" - weight: 75 -weight: 75 -sections_weight: 75 -draft: false ---- - -Any JavaScript resource file can be transpiled to another JavaScript version using `resources.TransformJS` which takes for argument the resource object and a slice of options listed below. TransformJS uses the [babel cli](https://babeljs.io/docs/en/babel-cli). - - -{{% note %}} -Hugo Pipe's TranspileJS requires the `@babel/cli` and `@babel/core` JavaScript packages to be installed in the environment (`npm install -g @babel/cli @babel/core`) along with any Babel plugin(s) or preset(s) used (e.g., `npm install -g @babel/preset-env`). - -If you are using the Hugo Snap package, Babel and plugin(s) need to be installed locally within your Hugo site directory, e.g., `npm install @babel/cli @babel/core` without the `-g` flag. -{{% /note %}} -### Options - -config [string] -: Path to the Babel configuration file - -_If no configuration file is used:_ - -plugins [string] -: Comma seperated string of Babel plugins to use - -presets [string] -: Comma seperated string of Babel presets to use - -minified [bool] -: Save as much bytes as possible when printing - -noComments [bool] -: Write comments to generated output (true by default) - -compact [string] -: Do not include superfluous whitespace characters and line terminators (true/false/auto) - -verbose [bool] -: Log everything - -### Examples -Without a `.babelrc` file, you can simply pass the options like so: -```go-html-template -{{- $transpileOpts := (dict "presets" "@babel/preset-env" "minified" true "noComments" true "compact" "true" ) -}} -{{- $transpiled := resources.Get "scripts/main.js" | transpileJS $transpileOpts -}} -``` - -If you rather want to use a config file, you can leave out the options in the template. -```go-html-template -{{- $transpiled := resources.Get "scripts/main.js" | transpileJS $transpileOpts -}} -``` -Then, you can either create a `.babelrc` in the root of your project, or your can create a `.babel.config.js`. -More information on these configuration files can be found here: [babel configuration](https://babeljs.io/docs/en/configuration) - -Finally, you can also pass a custom file path to a config file like so: -```go-html-template -{{- $transpileOpts := (dict "config" "config/my-babel-config.js" ) -}} -{{- $transpiled := resources.Get "scripts/main.js" | transpileJS $transpileOpts -}} -``` \ No newline at end of file diff --git a/hugolib/resource_chain_babel_test.go b/hugolib/resource_chain_babel_test.go new file mode 100644 index 00000000000..c9ddb2140b1 --- /dev/null +++ b/hugolib/resource_chain_babel_test.go @@ -0,0 +1,127 @@ +// 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 hugolib + +import ( + "os" + "os/exec" + "path/filepath" + "runtime" + "testing" + + "github.com/gohugoio/hugo/htesting" + + "github.com/spf13/viper" + + qt "github.com/frankban/quicktest" + + "github.com/gohugoio/hugo/hugofs" + + "github.com/gohugoio/hugo/common/loggers" +) + +func TestResourceChainBabel(t *testing.T) { + if !isCI() { + t.Skip("skip (relative) long running modules test when running locally") + } + + if runtime.GOOS == "windows" { + t.Skip("skip npm test on Windows") + } + + wd, _ := os.Getwd() + defer func() { + os.Chdir(wd) + }() + + c := qt.New(t) + + packageJSON := `{ + "scripts": {}, + + "devDependencies": { + "@babel/cli": "7.8.4", + "@babel/core": "7.9.0", + "@babel/preset-env": "7.9.5" + } +} +` + + babelConfig := ` +console.error("Hugo Environment:", process.env.HUGO_ENVIRONMENT ); + +module.exports = { + presets: ["@babel/preset-env"], +}; + +` + + js := ` +/* A Car */ +class Car { + constructor(brand) { + this.carname = brand; + } +} +` + + workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-test-babel") + c.Assert(err, qt.IsNil) + defer clean() + + v := viper.New() + v.Set("workingDir", workDir) + v.Set("disableKinds", []string{"taxonomyTerm", "taxonomy", "page"}) + b := newTestSitesBuilder(t).WithLogger(loggers.NewWarningLogger()) + + // Need to use OS fs for this. + b.Fs = hugofs.NewDefault(v) + b.WithWorkingDir(workDir) + b.WithViper(v) + b.WithContent("p1.md", "") + + b.WithTemplates("index.html", ` +{{ $options := dict "noComments" true }} +{{ $transpiled := resources.Get "js/main.js" | babel -}} +Transpiled: {{ $transpiled.Content | safeJS }} + +`) + + jsDir := filepath.Join(workDir, "assets", "js") + b.Assert(os.MkdirAll(jsDir, 0777), qt.IsNil) + b.WithSourceFile("assets/js/main.js", js) + b.WithSourceFile("package.json", packageJSON) + b.WithSourceFile("babel.config.js", babelConfig) + + b.Assert(os.Chdir(workDir), qt.IsNil) + _, err = exec.Command("npm", "install").CombinedOutput() + b.Assert(err, qt.IsNil) + + out, err := captureStderr(func() error { + return b.BuildE(BuildCfg{}) + + }) + // Make sure Node sees this. + b.Assert(out, qt.Contains, "Hugo Environment: production") + b.Assert(err, qt.IsNil) + + b.AssertFileContent("public/index.html", ` +var Car = function Car(brand) { + _classCallCheck(this, Car); + + this.carname = brand; +}; +`) + +} diff --git a/resources/resource_transformers/transpilejs/transpilejs.go b/resources/resource_transformers/babel/babel.go similarity index 87% rename from resources/resource_transformers/transpilejs/transpilejs.go rename to resources/resource_transformers/babel/babel.go index b832f436b71..fc9650aa061 100644 --- a/resources/resource_transformers/transpilejs/transpilejs.go +++ b/resources/resource_transformers/babel/babel.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Hugo Authors. All rights reserved. +// 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. @@ -11,14 +11,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -package transpilejs +package babel import ( "io" "os" "os/exec" "path/filepath" + "strconv" + "github.com/gohugoio/hugo/common/hugo" "github.com/gohugoio/hugo/resources/internal" "github.com/mitchellh/mapstructure" @@ -32,14 +34,13 @@ import ( // Options from https://babeljs.io/docs/en/options type Options struct { - Config string //Custom path to config file - Plugins string //Comma seperated string of plugins - Presets string //Comma seperated string of presets - Minified bool //true/false - NoComments bool //true/false - Compact string //true/false/auto - Verbose bool //true/false - NoBabelrc bool //true/false + Config string // Custom path to config file + + Minified bool + NoComments bool + Compact *bool + Verbose bool + NoBabelrc bool } func DecodeOptions(m map[string]interface{}) (opts Options, err error) { @@ -52,20 +53,14 @@ func DecodeOptions(m map[string]interface{}) (opts Options, err error) { func (opts Options) toArgs() []string { var args []string - if opts.Plugins != "" { - args = append(args, "--plugins="+opts.Plugins) - } - if opts.Presets != "" { - args = append(args, "--presets="+opts.Presets) - } if opts.Minified { args = append(args, "--minified") } if opts.NoComments { args = append(args, "--no-comments") } - if opts.Compact != "" { - args = append(args, "--compact="+opts.Compact) + if opts.Compact != nil { + args = append(args, "--compact="+strconv.FormatBool(*opts.Compact)) } if opts.Verbose { args = append(args, "--verbose") @@ -103,7 +98,6 @@ func (t *babelTransformation) Key() internal.ResourceTransformationKey { // npm install -g @babel/preset-env // Instead of installing globally, you can also install everything as a dev-dependency (--save-dev instead of -g) func (t *babelTransformation) Transform(ctx *resources.ResourceTransformationCtx) error { - const localBabelPath = "node_modules/@babel/cli/bin/" const binaryName = "babel.js" @@ -164,6 +158,7 @@ func (t *babelTransformation) Transform(ctx *resources.ResourceTransformationCtx cmd.Stdout = ctx.To cmd.Stderr = os.Stderr + cmd.Env = hugo.GetExecEnviron(t.rs.Cfg) stdin, err := cmd.StdinPipe() if err != nil { diff --git a/resources/resource_transformers/postcss/postcss.go b/resources/resource_transformers/postcss/postcss.go index 15cda898cd7..1f2e51fd8d8 100644 --- a/resources/resource_transformers/postcss/postcss.go +++ b/resources/resource_transformers/postcss/postcss.go @@ -25,9 +25,9 @@ import ( "strconv" "strings" - "github.com/gohugoio/hugo/common/loggers" + "github.com/gohugoio/hugo/common/hugo" - "github.com/gohugoio/hugo/config" + "github.com/gohugoio/hugo/common/loggers" "github.com/gohugoio/hugo/resources/internal" "github.com/spf13/afero" @@ -202,10 +202,7 @@ func (t *postcssTransformation) Transform(ctx *resources.ResourceTransformationC cmd.Stdout = ctx.To cmd.Stderr = io.MultiWriter(os.Stderr, &errBuf) - // TODO(bep) somehow generalize this to other external helpers that may need this. - env := os.Environ() - config.SetEnvVars(&env, "HUGO_ENVIRONMENT", t.rs.Cfg.GetString("environment")) - cmd.Env = env + cmd.Env = hugo.GetExecEnviron(t.rs.Cfg) stdin, err := cmd.StdinPipe() if err != nil { diff --git a/tpl/resources/init.go b/tpl/resources/init.go index 10e8e531957..df83cb3bbfb 100644 --- a/tpl/resources/init.go +++ b/tpl/resources/init.go @@ -60,8 +60,8 @@ func init() { [][2]string{}, ) - ns.AddMethodMapping(ctx.TranspileJS, - []string{"transpileJS"}, + ns.AddMethodMapping(ctx.Babel, + []string{"babel"}, [][2]string{}, ) diff --git a/tpl/resources/resources.go b/tpl/resources/resources.go index c256fa903d7..6625702ab26 100644 --- a/tpl/resources/resources.go +++ b/tpl/resources/resources.go @@ -29,12 +29,12 @@ import ( "github.com/gohugoio/hugo/resources/resource_factories/bundler" "github.com/gohugoio/hugo/resources/resource_factories/create" + "github.com/gohugoio/hugo/resources/resource_transformers/babel" "github.com/gohugoio/hugo/resources/resource_transformers/integrity" "github.com/gohugoio/hugo/resources/resource_transformers/minifier" "github.com/gohugoio/hugo/resources/resource_transformers/postcss" "github.com/gohugoio/hugo/resources/resource_transformers/templates" "github.com/gohugoio/hugo/resources/resource_transformers/tocss/scss" - "github.com/gohugoio/hugo/resources/resource_transformers/transpilejs" "github.com/spf13/cast" ) @@ -55,15 +55,15 @@ func New(deps *deps.Deps) (*Namespace, error) { } return &Namespace{ - deps: deps, - scssClient: scssClient, - createClient: create.New(deps.ResourceSpec), - bundlerClient: bundler.New(deps.ResourceSpec), - integrityClient: integrity.New(deps.ResourceSpec), - minifyClient: minifyClient, - postcssClient: postcss.New(deps.ResourceSpec), - templatesClient: templates.New(deps.ResourceSpec, deps), - transpileJSClient: transpilejs.New(deps.ResourceSpec), + deps: deps, + scssClient: scssClient, + createClient: create.New(deps.ResourceSpec), + bundlerClient: bundler.New(deps.ResourceSpec), + integrityClient: integrity.New(deps.ResourceSpec), + minifyClient: minifyClient, + postcssClient: postcss.New(deps.ResourceSpec), + templatesClient: templates.New(deps.ResourceSpec, deps), + babelClient: babel.New(deps.ResourceSpec), }, nil } @@ -71,14 +71,14 @@ func New(deps *deps.Deps) (*Namespace, error) { type Namespace struct { deps *deps.Deps - createClient *create.Client - bundlerClient *bundler.Client - scssClient *scss.Client - integrityClient *integrity.Client - minifyClient *minifier.Client - postcssClient *postcss.Client - transpileJSClient *transpilejs.Client - templatesClient *templates.Client + createClient *create.Client + bundlerClient *bundler.Client + scssClient *scss.Client + integrityClient *integrity.Client + minifyClient *minifier.Client + postcssClient *postcss.Client + babelClient *babel.Client + templatesClient *templates.Client } // Get locates the filename given in Hugo's assets filesystem @@ -283,22 +283,22 @@ func (ns *Namespace) PostProcess(r resource.Resource) (postpub.PostPublishedReso } -// TranspileJS processes the given Resource with Babel. -func (ns *Namespace) TranspileJS(args ...interface{}) (resource.Resource, error) { +// Babel processes the given Resource with Babel. +func (ns *Namespace) Babel(args ...interface{}) (resource.Resource, error) { r, m, err := ns.resolveArgs(args) if err != nil { return nil, err } - var options transpilejs.Options + var options babel.Options if m != nil { - options, err = transpilejs.DecodeOptions(m) + options, err = babel.DecodeOptions(m) if err != nil { return nil, err } } - return ns.transpileJSClient.Process(r, options) + return ns.babelClient.Process(r, options) }