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.

Fixes gohugoio#7644
Fixes gohugoio#7656
  • Loading branch information
bep committed Sep 12, 2020
1 parent d4611c4 commit 3ea55cf
Show file tree
Hide file tree
Showing 15 changed files with 498 additions and 22 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
}
17 changes: 16 additions & 1 deletion common/hugo/hugo.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@ import (
"fmt"
"html/template"
"os"
"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 +79,17 @@ func NewInfo(environment string) Info {
}
}

func GetExecEnviron(cfg config.Provider) []string {
func GetExecEnviron(cfg config.Provider, fs afero.Fs) []string {
env := os.Environ()
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
}
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
18 changes: 14 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 @@ -346,8 +349,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 +701,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
54 changes: 53 additions & 1 deletion hugolib/hugo_modules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (
"testing"
"time"

"github.com/gohugoio/hugo/modules/npm"

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

"github.com/spf13/afero"
Expand All @@ -38,7 +40,6 @@ import (
"github.com/spf13/viper"
)

// https://github.com/gohugoio/hugo/issues/6730
func TestHugoModulesVariants(t *testing.T) {
if !isCI() {
t.Skip("skip (relative) long running modules test when running locally")
Expand Down Expand Up @@ -129,6 +130,57 @@ JS imported in module: |
`)
})

t.Run("Create package.json", func(t *testing.T) {

b, clean := newTestBuilder(t, "")
defer clean()

b.WithSourceFile("package.json", `{
"name": "mypack",
"version": "1.2.3",
"scripts": {},
"dependencies": {
"nonon": "error"
}
}`)

b.WithSourceFile("package.hugo.json", `{
"name": "mypack",
"version": "1.2.3",
"scripts": {},
"dependencies": {
"foo": "1.2.3"
},
"devDependencies": {
"postcss-cli": "7.8.0",
"tailwindcss": "1.8.0"
}
}`)

b.Build(BuildCfg{})
b.Assert(npm.Pack(b.H.BaseFs.SourceFs, b.H.BaseFs.Assets.Dirs), qt.IsNil)

b.AssertFileContentFn("package.json", func(s string) bool {
return s == `{
"dependencies": {
"foo": "1.2.3",
"react-dom": "^16.13.1"
},
"devDependencies": {
"@babel/cli": "7.8.4",
"@babel/core": "7.9.0",
"@babel/preset-env": "7.9.5",
"postcss-cli": "7.8.0",
"tailwindcss": "1.8.0"
},
"name": "mypack",
"scripts": {},
"version": "1.2.3"
}`
})
})

}

// TODO(bep) this fails when testmodBuilder is also building ...
Expand Down
6 changes: 6 additions & 0 deletions hugolib/resource_chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,10 @@ func TestResourceChainPostCSS(t *testing.T) {

postcssConfig := `
console.error("Hugo Environment:", process.env.HUGO_ENVIRONMENT );
// https://github.com/gohugoio/hugo/issues/7656
console.error("package.json:", process.env.HUGO_FILE_PACKAGE_JSON );
console.error("PostCSS Config File:", process.env.HUGO_FILE_POSTCSS_CONFIG_JS );
module.exports = {
plugins: [
Expand Down Expand Up @@ -954,6 +958,8 @@ class-in-b {

// Make sure Node sees this.
b.Assert(logBuf.String(), qt.Contains, "Hugo Environment: production")
b.Assert(logBuf.String(), qt.Contains, fmt.Sprintf("PostCSS Config File: %s/postcss.config.js", workDir))
b.Assert(logBuf.String(), qt.Contains, fmt.Sprintf("package.json: %s/package.json", workDir))

b.AssertFileContent("public/index.html", `
Styles RelPermalink: /css/styles.css
Expand Down
43 changes: 43 additions & 0 deletions modules/collect.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"time"

Expand Down Expand Up @@ -382,6 +383,11 @@ func (c *collector) applyMounts(moduleImport Import, mod *moduleAdapter) error {
return err
}

mounts, err = c.mountCommonJSConfig(mod, mounts)
if err != nil {
return err
}

mod.mounts = mounts
return nil
}
Expand Down Expand Up @@ -549,6 +555,43 @@ func (c *collector) loadModules() error {
return nil
}

// Matches postcss.config.js etc.
var commonJSConfigs = regexp.MustCompile(`(babel|postcss|tailwind)\.config\.js`)

func (c *collector) mountCommonJSConfig(owner *moduleAdapter, mounts []Mount) ([]Mount, error) {
for _, m := range mounts {
if strings.HasPrefix(m.Target, files.JsConfigFolderMountPrefix) {
// This follows the convention of the other component types (assets, content, etc.),
// if one or more is specificed by the user, we skip the defaults.
// These mounts were added to Hugo in 0.75.
return mounts, nil
}
}

// Mount the common JS config files.
fis, err := afero.ReadDir(c.fs, owner.Dir())
if err != nil {
return mounts, err
}

for _, fi := range fis {
n := fi.Name()

should := n == files.FilenamePackageHugoJSON || n == files.FilenamePackageJSON
should = should || commonJSConfigs.MatchString(n)

if should {
mounts = append(mounts, Mount{
Source: n,
Target: filepath.Join(files.ComponentFolderAssets, files.FolderJSConfig, n),
})
}

}

return mounts, nil
}

func (c *collector) normalizeMounts(owner *moduleAdapter, mounts []Mount) ([]Mount, error) {
var out []Mount
dir := owner.Dir()
Expand Down
Loading

0 comments on commit 3ea55cf

Please sign in to comment.