できない.dev

babel-loader で node_modules 内のパッケージが変換できない

babel-loader の定番設定 exclude: /node_modules/ は依存パッケージを丸ごと変換対象から外す。
素の構文で配布されたパッケージだけを例外にする書き方に変えれば解決する。

公開:

要約

自分のコードは変換されるのに、依存パッケージ由来のファイルで構文エラーが出るときは、exclude: /node_modules/ が効いています。
この 1 行は「node_modules の中は変換しない」という指示なので、素の構文のまま配布されたパッケージはそのままバンドルに入ります。

babel-loader は、この用途のために exclude を条件オブジェクトで書く形を用意しています。

// webpack.config.js
{
  test: /\.(?:js|mjs|cjs)$/,
  exclude: {
    and: [/node_modules/],
    not: [/unfetch/, /d3-array|d3-scale/],
  },
  use: { loader: "babel-loader" },
}

not に挙げたパッケージだけが変換対象に戻ります。

よくある原因

  1. exclude: /node_modules/ が一律に効いている: これはビルド時間を守るための定番設定で、ほとんどの場合は正しい。
    ただし依存パッケージが変換を必要とする場合は、そのパッケージだけを例外にする必要がある。
  2. 依存パッケージが素の構文で配布されている: 近年は「変換は利用側の責任」という方針で、オプショナルチェーンやクラスフィールドをそのまま含んだまま公開するパッケージが増えている。
    babel-loader の公式リポジトリも、レガシーな対象をサポートする場合は node_modules 配下の変換が必要になると説明している。
  3. main が変換前のソースを指している: moduleexports の指す先が変換済みでも、webpack の解決結果が変換前のファイルになっていることがある。
  4. test.mjs が入っていない: ESM で配布されたパッケージは .mjs を持つ。test: /\.js$/ だけだと loader に渡らず、変換されないまま通過する。

解決策

1. exclude を条件オブジェクトに変える

正規表現ひとつでは「node_modules を除く。
ただしこれは除かない」を表現できません。
babel-loader の公式リポジトリが示している書き方は次の形です。

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(?:js|mjs|cjs)$/,
        exclude: {
          and: [/node_modules/],
          not: [/unfetch/, /d3-array|d3-scale/, /@hapi[\/\\]joi-date/],
        },
        use: {
          loader: "babel-loader",
          options: {
            presets: [["@babel/preset-env", { targets: "ie 11" }]],
          },
        },
      },
    ],
  },
};

and / not は webpack の条件(Condition)の記法で、babel-loader 固有の拡張ではありません。

2. 対象パッケージを名指しで特定する

not に何を書くかは、ビルドエラーが指しているファイルのパスから決めます。

ERROR in ./node_modules/some-lib/dist/index.js 12:24
Module parse failed: Unexpected token (12:24)
You may need an appropriate loader to handle this file type

この場合は /some-lib/not に足します。
パッケージ名を丸ごと書くと、そのパッケージが依存している別のパッケージまでは含まれない点に注意してください。
エラーが次のパッケージに移ったら、同じ手順を繰り返します。

3. .mjs を test に含める

拡張子の指定漏れは見落としやすい原因です。

// before
test: /\.js$/,
 
// after
test: /\.(?:js|mjs|cjs)$/,

4. targets を実態に合わせる

そもそも古いブラウザを対象にしていなければ、依存パッケージの変換は不要です。@babel/preset-env の対象が広すぎないかを見直すと、not に足すパッケージ自体が減ります。
対象の決め方は /babel/preset-env-not-transpiling/ を参照してください。

{
  "browserslist": ["> 0.5%", "last 2 versions", "not dead"]
}

なお node_modules を丸ごと変換対象にするとビルド時間が大きく伸びます。
範囲は必要なパッケージだけに絞ってください。

この記事は役立ちましたか?