💣

@nuxt/postcss8 使用時 "Cannot find module 0" エラーが発生する場合とその対処

2021/06/23に公開

現象

autoprefixer を v10 にバージョンアップする時、postcssをv8以上に更新する必要が生じた。
そこで、 postcss8の移行ガイド に従って以下を試みた。

その後、 nuxt generate によって静的ファイルを生成しようとしたところ、掲題のエラーによって失敗した。

╭───────────────────────────────────────────────────────────────────────────────────────────────╮
│                                                                                               │
│   ✖ Nuxt Fatal Error                                                                          │
│                                                                                               │
│   Error: Cannot find module '0'                                                               │
│   Require stack:                                                                              │
│   - /Users/path/to/app/node_modules/@nuxt/core/dist/core.js                                   │
│                                                                                               │
╰───────────────────────────────────────────────────────────────────────────────────────────────╯

発生時の状況

package.json の依存関係定義

{
  "dependencies": {
    "@nuxtjs/axios": "^5.3.6",
    "cross-env": "^7.0.0",
    "nuxt": "2.15.7"
  },
  "devDependencies": {
    "@nuxt/postcss8": "1.1.3",
    "@nuxtjs/eslint-config": "^6.0.0",
    "@vue/test-utils": "^1.0.0-beta.27",
    "autoprefixer": "^10.0.0",
    "babel-core": "7.0.0-bridge.0",
    "babel-eslint": "^10.0.1",
    "babel-jest": "^27.0.0",
    "eslint": "^7.0.0",
    "eslint-config-prettier": "^8.0.0",
    "eslint-config-standard": ">=12.0.0",
    "eslint-loader": "^4.0.0",
    "eslint-plugin-import": ">=2.16.0",
    "eslint-plugin-jest": ">=22.3.0",
    "eslint-plugin-node": ">=8.0.1",
    "eslint-plugin-nuxt": ">=0.4.2",
    "eslint-plugin-prettier": "^3.0.1",
    "eslint-plugin-promise": ">=4.0.1",
    "eslint-plugin-standard": ">=4.0.0",
    "eslint-plugin-vue": "^7.0.0",
    "jest": "^27.0.0",
    "nodemon": "^2.0.0",
    "postcss": "^8.3.5",
    "prettier": "^2.0.0",
    "tailwindcss": "^2.0.0",
    "vue-jest": "^3.0.3"
  }
}

nuxt.config.js でのモジュール指定

{
  buildModules: [
    '@nuxt/postcss8'
  ]
}

'@nuxt/postcss8' をコメントアウトすると掲題のエラーは発生しなくなった。
そのため、 '@nuxt/postcss8' が原因と判断。

対処法

nuxt.config.jsbuild.postcss.plugins 指定を配列からオブジェクト形式に変更することで、掲題の現象は発生しなくなった。

before

import path from 'path'
// (略)
export default {
  // (略)
  build: {
    postcss: {
      plugins: [
        require('tailwindcss')(
          path.join(__dirname, 'tailwind.js')
        ),
        require('autoprefixer')
      ]
    }
  }
}

after

import path from 'path'
// (略)
export default {
  build:
    postcss: {
      plugins: {
        tailwindcss: require('tailwindcss')(
          path.join(__dirname, 'tailwind.js')
        ),
        autoprefixer: require('autoprefixer')
      }
    }
  }
}

原因

nuxt.config.js におけるPostCSS用プラグインの指定方法と、 @nuxt/postcss8 によるプラグインの加工が食い違っていたことによる。

@nuxt/postcss8 による config の書き換え

@nuxt/postcss8 は、適用時に build.postcss.plugins 指定を書き換えて、 プラグインの順番を並べ替えている [1]
この時、 '@nuxt/postcss8'defu を利用してオプションをマージしているが、 defu(baseObj, defaults) によって build.postcss.plugins が以下のように書き換わる。

{
  build: {
    postcss: {
      plugins: {
        '0': { /* プラグイン略 */ },
        '1': { /* プラグイン略 */ },
        'autoprefixer': {}
      }
    }
  }
}

この時 postcss-loader は plugins各キーをプラグイン名と見て解釈する ので、 '0' という名前のプラグインを参照しようとするが、そのような名前のプラグインは存在しないのでエラーとなった。

defu によるマージ

今回のケースでは、 defu(baseObj, defaults) を実行した時のパラメータが baseObj: Array defaults: Object という組み合わせだったパターンに該当する。

defu は baseObj の各 key を走査して、 defaults[key]存在しない場合 は key と key に対応する値を defaults に挿入することでマージを行う。

この時、 baseObj: Array であるから走査によって得られる key は 0, 1, 2, ... といった添字になるため、 defaults に挿入される key もまた "0", "1", "2", ... といった内容になる。結果、上記のように数字が添字として入った build.postcss.plugins が発生する。

これを防ぐため、そもそも nuxt.config.js における build.postcss.plugins の指定を配列からオブジェクト形式に変更することで、問題が解消した。

懸念

postcss-loader の設定方法

postcss-loader を見ると、 plugins のキーをオブジェクトで指定する方法は deprecated, will be removed in the next major release であり、基本的には配列を用いることが推奨されている。従って、 @nuxt/postcss8 を用いるためにプラグイン指定をオブジェクトにした場合、今後のバージョンアップに際して問題となる可能性がある。

nuxt と postcss-loader のズレ?

他方、 nuxt では Arrayではなくオブジェクト形式での指定を推奨しようとしている?

postcss と nuxt で是とする方向が食い違っているように見える、しかも postcss は次期バージョンでオブジェクト方式を廃止しようとしている点が気がかり。
このI/F差分は、nuxt の plugin によって今後継続して埋めていくことになるのだろうか。

脚注
  1. 並び替えは order(name) に従って行われる。この order(name) はどうやら nuxtによる独自拡張の模様↩︎

Discussion