kanta's spike

HugoでPostCSSのプラグインとしてtailwindcssを使いたい。

PostCSStailwindcss の設定は以下とする。

  • postcss.config.js
var tailwind_config = require("./tailwind.config")
module.exports = {
  plugins: {
    'postcss-import': {
      root: __dirname,
      path: ['assets/css', 'node_modules'],
    },
    'tailwindcss/nesting': {},
    tailwindcss: tailwind_config,
    autoprefixer: {},
  }
}
  • tailwindcss.config.js
const path = require('path')
const theme_dir = path.basename(__dirname)

const colors = require('tailwindcss/colors')
module.exports = {
  content: ["./layouts/**/*.html", `./themes/${theme_dir}/layouts/**/*.html`, "./content/**/*.md"],
  // 略
}

解決策

いくつか方法がある。 Hugo v0.112.0以降で採用された方法3を採用する。

本サイトでは、トリッキーだがシンプルな方法2を採用している。 方法1 は、bundlerツールのインストールや設定が複雑なため採用しない。

方法1: CSSファイルの生成は、webpack などのbundlerツールにまかせる

webpack を導入し、以下のようなwebpack.config.jsを定義する。

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
  output: {
      path: path.resolve(process.cwd(), 'assets'),
  },
  plugins: [new MiniCssExtractPlugin({ filename: 'css/theme.css' })],
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"],
      },
    ],
  },
};

Hugoのレイアウトでは、以下のように、webpackで生成したCSSファイルを取得し読み込む。

{{- $css := resources.Get "css/theme.css" -}}
<link rel="stylesheet" href="{{ $css.RelPermalink }}">

あとは、開発時は以下のように webpackhugo の両方を起動する。

webpack --watch &
hugo server --disableFastRender --ignoreCache --cleanDestinationDir

サイト生成時は、webpackを実行後にhugoを実行する。

webpack
hugo --cleanDestinationDir

方法2: HugoのPostCSS機能とresources.ExecuteAsTemplateを利用する(Hugo v0.111.3以前)

Hugoには、リソースとして、assetsフォルダに格納したcssファイルを取得し、resources.PostCSS関数により変換する機能がある。

この機能を利用すれば、PostCSSをプリプロセッサにしてtailwindcssを利用できる。

しかし、tailwindcss は、設定ファイルのcontentに指定されたCSSファイルやHTMLファイルを解析し、使用されているユーティリティクラスのみ抽出しCSSファイルに保存する。 そのため、解析対象のCSSファイルやHTMLファイルが更新されるたびに、PostCSS関数を実行する必要がある。

ただ、たとえ解析対象のファイルが更新されたとしても、CSSファイルが既にHugoにキャッシュされているため、CSSファイルの更新を検知できず、PostCSS関数はCSSファイルを処理できない。

そこで、resources.ExecuteAsTemplate 関数を使い、開発時は画面更新毎に、擬似的にCSSファイル名を変更させて、 毎回、強制的にPostCSS関数が実行されるようにする。

    {{- $css := resources.Get "css/theme.css" -}}
    {{- if hugo.IsProduction }}
        {{- $css = $css | resources.PostCSS }}
    {{- else }}
        {{- $css = $css | resources.ExecuteAsTemplate (printf "css/theme.dev.%v.css" now.Unix) .
            | resources.PostCSS }}
    {{- end }}
    <link rel="stylesheet" href="{{ $css.RelPermalink }}">

方法3: HugoのPostCSS機能とnew cache busterを利用する(Hugo v0.112.0以降)

Hugo Release v0.112.0で、TailwindCSS v3.x Support, new cache buster configurationという機能が追加された。1

これは、既に存在するCSS purging with PostCSSのための仕組みであるhugo_stats.jsonを利用し、ページ変更にともなうCSSファイルの変更要否を検知し、 変更要の場合は、CSSファイルをHugoのキャッシュから削除し、PostCSS関数で再度CSSファイルを処理するようにする方法である。

この方法を利用すると、CSSファイルの読み込みは以下のようにシンプルになる。

    {{- $css := resources.Get "css/theme.css" -}}
    {{- $css := $css | resources.PostCSS -}}
    {{- if hugo.IsProduction }}
        {{- $css = $css | resources.Minify | resources.Fingerprint }}
    {{- end }}
    <link rel="stylesheet" href="{{ $css.RelPermalink }}">

設定手順は以下になる。

  1. hugo_hugo_stats.jsonを出力するようにconfig.tomlを修正

    # config.toml or hugo.toml
    #  ... 略 ...
    [build]
      # 手順1. hugo_hugo_stats.jsonを出力
      writeStats = true
    #  ... 略 ...
    
  2. テーマディレクトリ/tailwindc.config.jscontent設定にhugo_hugo_stats.jsonを追加

    // tailwindc.config.js
    const path = require('path')
    const theme_dir = path.basename(__dirname)
    //  ... 略 ...
    
    module.exports = {
      // 手順2. `content`設定に`hugo_hugo_stats.json`を追加
      content: [`./hugo_stats.json`, `./themes/${theme_dir}/assets/**/*.{js,css}`],
    //  ... 略 ...
    }
    
  3. Hugoでhugo_hugo_stats.jsonを監視するために、Assetとしてマウントするようにconfig.tomlを修正 2

    # config.toml or hugo.toml
    [module]
      [[module.mounts]]
        # 手順3. hugo_stats.jsonをmountし、Hugoの監視対象に
        source = "hugo_stats.json"
        target = "assets/watching/hugo_stats.json"
    
    #  ... 略 ...
    [build]
      # 手順1. hugo_hugo_stats.jsonを出力
      writeStats = true
    #  ... 略 ...
    
  4. hugo_hugo_stats.jsonの更新を検知したら、theme.cssをキャッシュから外すようにcachebustersに設定する

    # config.toml or hugo.toml
    [module]
      [[module.mounts]]
        # 手順3. hugo_stats.jsonをmountし、Hugoの監視対象に
        source = "hugo_stats.json"
        target = "assets/watching/hugo_stats.json"
    
    #  ... 略 ...
    [build]
      # 手順1. hugo_hugo_stats.jsonを出力
      writeStats = true
      [[build.cachebusters]]
        # 手順4. hugo_stats.jsonに変更があれば、theme.cssを更新する
        source = "assets/watching/hugo_stats\\.json"
        target = "theme\\.css"
    #  ... 略 ...
    

以上の設定後に、--debugオプション付きでHugoのサーバーを起動すると、コンソールにログが出力される。 そのなかのcachebusterl:というプレフィックスのログメッセージを確認するとhugo_stats.jsonの監視状況を確認できる。

hugo server --debug

また、実際のtailwind.config.jsconfig.tomlの例は以下を参照のこと。

参考 resources.ExecuteAsTemplate について

本来、resources.ExecuteAsTemplate 関数は、 ロジックを含むテンプレートファイルを処理するための関数である。

例えば、以下のようなtest.css.tmplを、

:root {
  --main-bg-color: {{ .Site.Param.bgcolor }};
}
/* ..略.. */

処理してからtest.cssとして読み込むために利用する。

{{ $css := resources.Get "tmpl/test.css.tmpl" | resources.ExecuteAsTemplate "css/test.css" . }}
<link rel="stylesheet" href="{{ $css.RelPermalink }}">

参考


  1. Configure cache busters - Configure Hugo | Hugo ↩︎

  2. Hugoの初回起動時はhugo_stats.jsonが存在しないため、hugo_stats.jsonをマウントできない。あらかじめ空のhugo_stats.jsonを作成するか、再度Hugoを起動する必要がある。 ↩︎

作成日: 2022/07/27更新日: 2023/05/26