Skip to content

Commit

Permalink
Add "hugo mod npm pack"
Browse files Browse the repository at this point in the history
This commit also introduces a convention where these common JS config files, including `package.hugo.json`, gets mounted into:

```
assets/_jsconfig
´``

These files mapped to their real filename will be added to the environment when running PostCSS, Babel etc., so you can do `process.env.HUGO_FILE_TAILWIND_CONFIG_JS` to resolve the real filename.

But do note that `assets` is a composite/union filesystem, so if your config file is not meant to be overridden, name them something specific.

This commit also adds adds `workDir/node_modules` to `NODE_PATH` and `HUGO_WORKDIR` to the env when running the JS tools above.

Fixes gohugoio#7644
Fixes gohugoio#7656
Fixes gohugoio#7675
  • Loading branch information
bep committed Sep 13, 2020
1 parent d4611c4 commit 569e0fb
Show file tree
Hide file tree
Showing 16 changed files with 720 additions and 47 deletions.
15 changes: 14 additions & 1 deletion commands/mod.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2019 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.
Expand All @@ -20,6 +20,8 @@ import (
"path/filepath"
"regexp"

"github.com/gohugoio/hugo/hugolib"

"github.com/gohugoio/hugo/modules"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -114,6 +116,8 @@ This is not needed if you only operate on modules inside /themes or if you have
RunE: nil,
}

cmd.AddCommand(newModNPMCmd(c))

cmd.AddCommand(
&cobra.Command{
Use: "get",
Expand Down Expand Up @@ -272,6 +276,15 @@ func (c *modCmd) withModsClient(failOnMissingConfig bool, f func(*modules.Client
return f(com.hugo().ModulesClient)
}

func (c *modCmd) withHugo(f func(*hugolib.HugoSites) error) error {
com, err := c.initConfig(true)
if err != nil {
return err
}

return f(com.hugo())
}

func (c *modCmd) initConfig(failOnNoConfig bool) (*commandeer, error) {
com, err := initializeConfig(failOnNoConfig, false, &c.hugoBuilderCommon, c, nil)
if err != nil {
Expand Down
56 changes: 56 additions & 0 deletions commands/mod_npm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// 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 commands

import (
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/modules/npm"
"github.com/spf13/cobra"
)

func newModNPMCmd(c *modCmd) *cobra.Command {

cmd := &cobra.Command{
Use: "npm",
Short: "Various npm helpers.",
Long: `Various npm (Node package manager) helpers.`,
RunE: func(cmd *cobra.Command, args []string) error {
return c.withHugo(func(h *hugolib.HugoSites) error {
return nil
})
},
}

cmd.AddCommand(&cobra.Command{
Use: "pack",
Short: "Experimental: Prepares and writes a composite package.json file for your project.",
Long: `Prepares and writes a composite package.json file for your project.
You need to put a "package.hugo.json" in the project root. This file will be used as a template file
with the base dependency set.
This command is marked as 'Experimental'. We think it's a great idea, so it's not likely to be
removed from Hugo, but we need to test this out in "real life" to get a feel of it,
so this may/will change in future versions of Hugo.
`,
RunE: func(cmd *cobra.Command, args []string) error {

return c.withHugo(func(h *hugolib.HugoSites) error {
return npm.Pack(h.BaseFs.SourceFs, h.BaseFs.Assets.Dirs)
})
},
})

return cmd
}
24 changes: 23 additions & 1 deletion common/hugo/hugo.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,15 @@ import (
"fmt"
"html/template"
"os"
"path/filepath"
"strings"

"github.com/gohugoio/hugo/hugofs/files"

"github.com/spf13/afero"

"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/hugofs"
)

const (
Expand Down Expand Up @@ -73,8 +80,23 @@ func NewInfo(environment string) Info {
}
}

func GetExecEnviron(cfg config.Provider) []string {
func GetExecEnviron(workDir string, cfg config.Provider, fs afero.Fs) []string {
env := os.Environ()
nodepath := filepath.Join(workDir, "node_modules")
if np := os.Getenv("NODE_PATH"); np != "" {
nodepath = workDir + string(os.PathListSeparator) + np
}
config.SetEnvVars(&env, "NODE_PATH", nodepath)
config.SetEnvVars(&env, "HUGO_WORKDIR", workDir)
config.SetEnvVars(&env, "HUGO_ENVIRONMENT", cfg.GetString("environment"))
fis, err := afero.ReadDir(fs, files.FolderJSConfig)
if err == nil {
for _, fi := range fis {
key := fmt.Sprintf("HUGO_FILE_%s", strings.ReplaceAll(strings.ToUpper(fi.Name()), ".", "_"))
value := fi.(hugofs.FileMetaInfo).Meta().Filename()
config.SetEnvVars(&env, key, value)
}
}

return env
}
24 changes: 24 additions & 0 deletions docs/content/en/hugo-pipes/babel.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,30 @@ Hugo Pipe's Babel requires the `@babel/cli` and `@babel/core` JavaScript package
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 %}}


### Config

{{< new-in "v0.75.0" >}}

In Hugo `v0.75` we improved the way we resolve JS configuration and dependencies. One of them is that we now adds the main project's `node_modules` to `NODE_PATH` when running Babel and similar tools. There are some known [issues](https://github.com/babel/babel/issues/5618) with Babel in this area, so if you have a `babel.config.js` living in a Hugo Module (and not in the project itself), we recommend using `require` to load the presets/plugins, e.g.:


```js
module.exports = {
presets: [
[
require('@babel/preset-env'),
{
useBuiltIns: 'entry',
corejs: 3
}
]
]
};
```



### Options

config [string]
Expand Down
10 changes: 10 additions & 0 deletions hugofs/files/classifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ import (
"github.com/spf13/afero"
)

const (
// The NPM package.json "template" file.
FilenamePackageHugoJSON = "package.hugo.json"
// The NPM package file.
FilenamePackageJSON = "package.json"
)

var (
// This should be the only list of valid extensions for content files.
contentFileExtensions = []string{
Expand Down Expand Up @@ -163,9 +170,12 @@ const (
ComponentFolderI18n = "i18n"

FolderResources = "resources"
FolderJSConfig = "_jsconfig" // Mounted below /assets with postcss.config.js etc.
)

var (
JsConfigFolderMountPrefix = filepath.Join(ComponentFolderAssets, FolderJSConfig)

ComponentFolders = []string{
ComponentFolderArchetypes,
ComponentFolderStatic,
Expand Down
5 changes: 1 addition & 4 deletions hugofs/rootmapping_fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,6 @@ func NewRootMappingFs(fs afero.Fs, rms ...RootMapping) (*RootMappingFs, error) {
(&rm).clean()

fromBase := files.ResolveComponentFolder(rm.From)
if fromBase == "" {
panic("unrecognised component folder in" + rm.From)
}

if len(rm.To) < 2 {
panic(fmt.Sprintf("invalid root mapping; from/to: %s/%s", rm.From, rm.To))
Expand Down Expand Up @@ -113,7 +110,7 @@ func newRootMappingFsFromFromTo(
ToBasedir: baseDir,
}
}

return NewRootMappingFs(fs, rms...)
}

Expand Down
8 changes: 0 additions & 8 deletions hugofs/walk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ import (
"strings"
"testing"

"github.com/gohugoio/hugo/common/hugo"

"github.com/pkg/errors"

"github.com/gohugoio/hugo/htesting"
Expand Down Expand Up @@ -129,12 +127,6 @@ func TestWalkSymbolicLink(t *testing.T) {
})

t.Run("BasePath Fs", func(t *testing.T) {
if hugo.GoMinorVersion() < 12 {
// https://github.com/golang/go/issues/30520
// This is fixed in Go 1.13 and in the latest Go 1.12
t.Skip("skip this for Go <= 1.11 due to a bug in Go's stdlib")

}
c := qt.New(t)

docsFs := afero.NewBasePathFs(fs, docsDir)
Expand Down
35 changes: 31 additions & 4 deletions hugolib/filesystems/basefs.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ type BaseFs struct {
// SourceFilesystems contains the different source file systems.
*SourceFilesystems

// The project source.
SourceFs afero.Fs

// The filesystem used to publish the rendered site.
// This usually maps to /my-project/public.
PublishFs afero.Fs
Expand Down Expand Up @@ -100,6 +103,23 @@ func (b *BaseFs) RelContentDir(filename string) string {
return filename
}

// ResolveJSConfigFile resolves the JS-related config file to a absolute
// filename. One example of such would be postcss.config.js.
func (fs *BaseFs) ResolveJSConfigFile(name string) string {
// First look in assets/_jsconfig
fi, err := fs.Assets.Fs.Stat(filepath.Join(files.FolderJSConfig, name))
if err == nil {
return fi.(hugofs.FileMetaInfo).Meta().Filename()
}
// Fall back to the work dir.
fi, err = fs.Work.Stat(name)
if err == nil {
return fi.(hugofs.FileMetaInfo).Meta().Filename()
}

return ""
}

// SourceFilesystems contains the different source file systems. These can be
// composite file systems (theme and project etc.), and they have all root
// set to the source type the provides: data, i18n, static, layouts.
Expand Down Expand Up @@ -346,8 +366,10 @@ func NewBase(p *paths.Paths, logger *loggers.Logger, options ...func(*BaseFs) er
}

publishFs := hugofs.NewBaseFileDecorator(afero.NewBasePathFs(fs.Destination, p.AbsPublishDir))
sourceFs := hugofs.NewBaseFileDecorator(afero.NewBasePathFs(fs.Source, p.WorkingDir))

b := &BaseFs{
SourceFs: sourceFs,
PublishFs: publishFs,
}

Expand Down Expand Up @@ -696,11 +718,16 @@ type filesystemsCollector struct {

func (c *filesystemsCollector) addDirs(rfs *hugofs.RootMappingFs) {
for _, componentFolder := range files.ComponentFolders {
dirs, err := rfs.Dirs(componentFolder)
c.addDir(rfs, componentFolder)
}

if err == nil {
c.overlayDirs[componentFolder] = append(c.overlayDirs[componentFolder], dirs...)
}
}

func (c *filesystemsCollector) addDir(rfs *hugofs.RootMappingFs, componentFolder string) {
dirs, err := rfs.Dirs(componentFolder)

if err == nil {
c.overlayDirs[componentFolder] = append(c.overlayDirs[componentFolder], dirs...)
}
}

Expand Down
Loading

0 comments on commit 569e0fb

Please sign in to comment.