HUGO

Hugo pipes, postCSS & postcss-import - file system context, part 2

Hey,

this question relates to Pipes, postCSS & postcss-import - how to keep filesystem context and somewhat to Regenerating assets directory for Hugo Pipes.

There seems to be a problem with path context while using Hugo pipes with postCSS.

I created a Tailwindcss-starter theme repo (see github) to speed up the development of themes with the use of Tailwindcss. (Thanks @budparr for the direction)

Here is the problem I’m chewing:

If I clone the theme repo as a seperate project with the folder structure:

my-theme/
  ├ assets/
  │ └ css/
  │   ├ dev/
  │   │  └ postcss.config.js
  │   ├ postcss.config.js
  │   ├ site.css
  │   ├ styles.css
  │   └ tailwind.js
  └ exampleSite
      └ content

and start it from within the theme repo folder using the exampleSite directory for the content with:

hugo server -s exampleSite --themesDir=../.. -w --disableFastRender

everything works fine. postCSS and postcss-import handle the imports as expected.

However, if I clone the repo as a theme into the themes directory of a new Hugo site with the following folder structure:

my-site/
  ├ content/
  ├ ... // some other folders
  └ themes/
    └ my-theme/
      ├ assets/
        └ css/
          ├ dev/
          │  └ postcss.config.js
          ├ postcss.config.js
          ├ site.css
          ├ styles.css
          └ tailwind.js

and start the project with:

hugo server -v

I receive the following error:

INFO 2019/01/13 19:48:52 postcss: use config file /Users/user/my-site/themes/my-theme/assets/css/dev/postcss.config.js
Error: Specified Tailwind config file "/Users/user/my-site/assets/css/tailwind.js" doesn't exist.
    at exports.default (/Users/user/my-site/themes/my-theme/node_modules/tailwindcss/lib/lib/registerConfigAsDependency.js:9:11)
    at _postcss2.default.plugin.config (/Users/user/my-site/themes/my-theme/node_modules/tailwindcss/lib/index.js:37:59)
    at creator (/Users/user/my-site/themes/my-theme/node_modules/tailwindcss/node_modules/postcss/lib/postcss.js:150:35)
    at Object.<anonymous> (/Users/user/my-site/themes/my-theme/assets/css/dev/postcss.config.js:16:31)
    at Module._compile (internal/modules/cjs/loader.js:721:30)
    at requireFromString (/usr/local/lib/node_modules/postcss-cli/node_modules/require-from-string/index.js:28:4)
    at parseJsFile (/usr/local/lib/node_modules/postcss-cli/node_modules/cosmiconfig/dist/loadJs.js:15:15)
ERROR 2019/01/13 19:48:54 error: failed to transform resource: exit status 1
Total in 1096 ms
Error: Error building site: logged 1 error(s)

The postcss.config.js is successfully found. It consist of the following setup:

module.exports = {
    plugins: [
        require('postcss-import')({
            path: ['./assets/css'] // where to look for the css assets
        }),
        require('tailwindcss')( './assets/css/tailwind.js'), // where to find the tailwind.js file
        require('autoprefixer')({
            browsers: ['>1%']
        }),
        require('postcss-reporter'),
    ]
}

But the path to tailwind.js from within postcss.config.js is not found. It seems like postCSS is loosing the context, that it was called from within the themes folder and uses instead the my-site root path.

There is a workaround, which is a bit ugly as it breaks the relative paths in styles.css:

// postcss.config.js
const themeDir = __dirname + '/../../..'; // this translates to /Users/user/my-site/themes/my-theme/

module.exports = {
    plugins: [
        require('postcss-import')({
            path: [themeDir]
        }),
        require('tailwindcss')(themeDir + '/assets/css/tailwind.js'),
        require('autoprefixer')({
            browsers: ['>1%']
        }),
    ]
}

But now I have to change the import paths in styles.js to:

/* Tailwind Base - Variables: tailwind-config.js */
@import "node_modules/tailwindcss/preflight";
/* Tailwind component classes registered by plugins*/
@import "node_modules/tailwindcss/components";
/* Site Specific */
@import "assets/css/site";
/* Tailwind's utility classes - generated based on config file */
@import "node_modules/tailwindcss/utilities";

Any hint on how to make postCSS make remember the file system context?

Okay, I think I traced it back to a Hugo core function in hugo/resources/resource_transformers/postcss/postcss.go:

func (t *postcssTransformation) Transform(ctx *resources.ResourceTransformationCtx) error {

    const localPostCSSPath = "node_modules/postcss-cli/bin/"
    const binaryName = "postcss"

    // Try first in the project's node_modules.
    csiBinPath := filepath.Join(t.rs.WorkingDir, localPostCSSPath, binaryName)

    binary := csiBinPath

    if _, err := exec.LookPath(binary); err != nil {
        // Try PATH
        binary = binaryName
        if _, err := exec.LookPath(binary); err != nil {
            // This may be on a CI server etc. Will fall back to pre-built assets.
            return herrors.ErrFeatureNotAvailable
        }
    }

    // rest of file left out
}

Here Hugo tries the projects path and then the global PATH.

Maybe this function should be refactored to first try the projects theme path (if a theme is used), than the projects root path and after that the global PATH.

As I updated my starter report to use Tailwindcss v1.0.1, the error with context aware paths still exists. Does anyone maybe has a hint for me? Help is greatly appreciated.

What about working around the issue by configuring tailwind from within postcss.config.js?

require('tailwindcss')({
      theme: {
        extend: {}
      },
      variants: {},
      plugins: []
    })

Interesting…, but no!

The Tailwindcss configuration should stay separate and not entangled with another any configuration file.

It is actually not an issue with the configuration of Tailwindcss. It is an issue with the lookup order from where PostCSS is started and where it looks for the /node_modules folder.

If I start a new site and start Hugo server from root, it should look first for /node_modules under the specific theme, and after that under the sites root /node_modules. Fallback is the global /node_modules folder.

my-site/
  ├ content/
  ├ ... // some other folders
  ├ node_modules // site's node_modules (if any)  <-- look here second
  └ themes/
    └ my-theme/
      ├ assets/
      ├ ... // some other theme content folders
      └ node_modules // <-- look here first

Instead Hugo does not look into the themes /node_modules folder at all, and so does not find the tailwind package.

Any movement on this? I’m actually using @dirkolbrich’s theme and would love to see this work so I can use Tailwind CSS on my projects…

As per Hugo v0.68.3 not yet. Here is my proposed solution for hugo/resources/resource_transformers/postcss/postcss.go:

func (t *postcssTransformation) Transform(ctx *resources.ResourceTransformationCtx) error {

	const localPostCSSPath = "node_modules/.bin/"
	const binaryName = "postcss"

	// Try first in the theme’s node_modules.
	themeBinPath := filepath.Join(t.rs.WorkingDir, t.rs.ThemesDir, localPostCSSPath, binaryName)
	// Try second in the project's node_modules.
	siteBinPath := filepath.Join(t.rs.WorkingDir, localPostCSSPath, binaryName)

	if _, err := exec.LookPath(themeBinPath); err != nil {
		// Try site
		if _, err := exec.LookPath(siteBinPath); err != nil {
			// Try PATH
			f _, err := exec.LookPath(binaryName); err != nil {
				// This may be on a CI server etc. Will fall back to pre-built assets.
				return herrors.ErrFeatureNotAvailable
			}
		}
	}

	// rest of file omitted
}

As I’m not familiar with the inner workings of Hugo or any side effects, would this be a feasible solution @bep?

Solution to what?

On path traversing in case of starting Postcss from the Node_modules folder.

Current order:

  1. search in site’s node_module folder
  2. fall back to global node_module folder

Proposed:

  1. search in theme’s node_modules folder
  2. search in site’s node_module folder
  3. fall back to global node_module folder

This would fall in line with other content and overwrite modes in a Hugo project.

1 Like

The theme [ hugo-theme-tailwindcss-starter] is usable and works. Even though the path workarund is a bit ugly.

1 Like