🦀

webpack to Rspack ~ Rspack移行の結果と注意点 ~

2024/10/01に公開

プロダクトのビルドツールを webpack から Rspack に移行したので、その経緯と注意点をまとめます。

🦀 Rspackとは?

Rustで書かれた高速なJavaScriptのバンドルツールです。 webpackエコシステムとの強力な互換性を持ちます。

2024/08/24にv1.0.0がリリースされました。

https://rspack.dev/

🤔 なぜRspackに移行したのか?

ビルド速度改善のため以前からwebpackの移行を検討していましたが、プロダクトがwebpack依存の構成[1]で、Viteやその他のビルドツールへの移行が少し面倒でした。 Rspackであればwebpackのプラグインもそのまま動き移行が容易、かつ速度改善が見込まれるためRspackへの移行を選びました。

💡 移行方法

移行方法は、rspackの公式ドキュメントの migrate from webpack をそのまま参考にして進めました。それだけで、devもproducitonもビルドは通ったので本当に楽でした。

修正内容

  • webpack.config.js を rspack.config.js に変更
  • const webpack = require('webpack')const rspack = require('@rspack/core') に変更
  • MiniCssExtractPlugin など特定のプラグインを rspack の同等のプラグインに変更
  • ts-loader など特定のローダーを rspack のビルトインのローダーに変更
  • 起動コマンドを webpack から rspack に変更

https://rspack.dev/guide/migration/webpack

🎉 移行結果

以下、プロダクトをビルドした結果の比較です。

📦 バンドル対象ファイル数

  • TypeScript: 390
  • Vue.js: 360
  • Media File: 100

💻 PC spec

  • CPU: Apple M1 Max
  • RAM: 32GB

developmentビルド

ツール 5回実行の平均時間
webpack 18.0s
rspack 8.0s

(左がwebpack、右がrspack)
https://youtu.be/Ov7tx4OpuUw

productionビルド

ツール 5回実行の平均時間
webpack 24.5s
rspack 8.7s

(左がwebpack、右がrspack)
https://youtu.be/mlETuonf2n8

爆速 🚀

🚨 移行時の注意点

devビルドはとくに移行に詰まることはありませんでしたが、prodビルドは中々詰まりどころがありました・・。

① CSSの読み込みエラーで画面が真っ白

RspackのcssExtractRspackPlugin で出力されるCSSのファイル名と、bundleされたJSが読み込み先として指定するCSSファイルのハッシュが微妙に異なり(JSから読み込み先として指定されるパスでは20文字ハッシュ、実際の出力は32文字ハッシュ)、CSSの読み込みエラーが発生。画面が真っ白に・・

対応

出力ファイル名の[name].[contenthash].css[name].[contenthash:20].cssとハッシュの長さを20に固定して解決(指定なしだと、32文字で出力。JSのハッシュは標準で20文字)。CssExtractRspackPluginのバグなのか、他プラグインとの互換性の問題なのか調査中。

rspack.config.js
plugins: [
    new rspack.CssExtractRspackPlugin({
-      filename: '[name].[contenthash].css',
+      filename: '[name].[contenthash:20].css',
    }),
]

② 一部スタイルが適応されず画面が崩れる

自社コンポーネントライブラリのスタイルをsassでnode_modulesからimportしていましたが、なぜかprodビルドではバンドルから除外。一部CSSが読み込まれず、デザイン崩れが発生。

style.scss
// これだけバンドルから除外される
@import '~hoge/dist/style.css';

対応

builtin:lightningcss-loader の最適化による挙動のようなので postcss-loader に変更して解決。ただ、ビルド速度が若干遅くなるので、回避策を検討中。

rspack.config.js
  {
    test: /\.(c|sc|sa)ss$/,
    use: [
      rspack.CssExtractRspackPlugin.loader,
      { loader: 'css-loader' },
-     { loader: 'builtin:lightningcss-loader' },
+     { loader: 'postcss-loader' },
      { loader: 'sass-loader' },
    ],
  },

③ 同名のハッシュで内容が異なるファイルが生成される

JSの出力ファイル名を [name].[chunkhash].js としているのに、なぜか同名で内容の異なるファイルが生成されました(本来はチャンク対象のファイルの内容が異なればハッシュも変わるはず)。そのため、CloudFront経由の配信で新旧のリリースファイルのinvalidateが適切に行われず、一部過去キャッシュが使われてしまい画面が壊れました。

対応

ハッシュ形式を [contenthash] に変更して解決。webpackのビルドでは同様の問題は起こらないので、おそらくRspackのバグ。再現環境を構築して近々Issueを上げる予定です。

rspack.config.js
output: {
-  filename: '[name].[chunkhash].js',
+  filename: '[name].[contenthash].js',
-  chunkFilename: '[name].[chunkhash].js',
+  chunkFilename: '[name].[contenthash].js',
}

その他、今後同様のキャッシュ事故が起こらないようにreleaseのCIで以下スクリプトを実行して同名で内容の異なるファイルが生成されていないかチェックすることにしました。

#!/bin/bash
set -eu

# CI側でPRブランチとreleaseブランチをビルドして、
# それぞれのビルド結果を/tmp/currentと/tmp/releaseに配置する
dir1="/tmp/current"
dir2="/tmp/release"

different_files_found=0

# dir1内の全てのファイルを再帰的に探索
while IFS= read -r -d '' file1; do
    relative_path="${file1#"$dir1"/}"
    file2="$dir2/$relative_path"

    if [ -f "$file2" ]; then
        # 内容が異なるか検証
        if ! cmp -s "$file1" "$file2"; then
            echo "Different content: $relative_path"
            different_files_found=1
        fi
    fi
done < <(find "$dir1" -type f -print0)

if [ $different_files_found -eq 1 ]; then
    echo "🚨 同名で内容が異なるファイルが見つかりました。"
    exit 1
else
    echo "✅ 同名で内容が異なるファイルが見つかりませんでした。"
    exit 0
fi

💭 おわりに

webpackから移行コストを抑えて、ビルド速度を上げたいのならRspackは良さそうです。ただProdビルドだけで起こる不具合もあり、まだ不安定なので移行は慎重に・・。(revertのrevertのrevetのrevet PRを作るくらい試行錯誤してました)

また、webpackほど成熟したエコシステムを持つプロダクトでも、こういう形で新しいビルドツールへ移行が進むと思うと考え深いです。Rspackや、今Viteで進んでいる Rollup -> Rolldownもその一例ですが、既存のプラグインをそのまま使える状態でRustなど高速な言語で書き直すケースは今後もさらに増えていきそうですね。

脚注
  1. django-webpack-loaderでwebpackの出力するstatsを読み込み、djangoがエントリーポイントとなるhtmlを配信する構成。 ↩︎

Discussion