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 #7644
Fixes #7656
Fixes #7675
  • Loading branch information
bep committed Sep 13, 2020
1 parent 9df60b6 commit 85ba9bf
Show file tree
Hide file tree
Showing 16 changed files with 721 additions and 46 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
58 changes: 58 additions & 0 deletions commands/mod_npm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// 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.
On first run it creates a "package.hugo.json" in the project root if not alread there. This file will be used as a template file
with the base dependency set.
This set will be merged with all "package.hugo.json" files found in the dependency tree, picking the version closest to the project.
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
3 changes: 0 additions & 3 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
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 85ba9bf

Please sign in to comment.