🌟

WebpackをRspackに移行したビルドを高速化した話

に公開

はじめに

フロントエンド開発において、ビルドツールの選択は開発効率に大きな影響を与えます。特に規模が大きくなったプロジェクトでは、ビルド時間の長さがデベロッパーエクスペリエンスを左右する重要な要素となっています。

以前の記事「Webpackバンドルサイズを大幅に削減した話」では、Webpackの最適化によってバンドルサイズの大幅な削減ができた点について紹介させてもらいました。
その記事の中ではビルド時間について割愛していましたが、元々10分程度かかっていたビルド時間を2分強まで改善できました。

本記事では、その次のステップとして、TROCCOで使用していたWebpackを、Rust製の互換バンドラーであるRspack[1]に移行した成果を紹介します。

移行の主な目的は以下の通りです:

  • ビルド時間の短縮によるデベロッパーエクスペリエンスの向上
  • リソース使用量(メモリ、CPU)の最適化
  • 将来的なスケーラビリティの確保

本記事では触れないこと

  • TROCCOではStorybookも活用して開発を行っています。StorybookにおいてもWebpackからRsbuild[2]を利用して置き換える事ができ、 移行手順としてMigrate from Storybook webpackも用意されていますがそこはまた別の記事で書きます。
  • CI/CDへの変更についても本記事では割愛します。(大きな変更はありませんが焦点を絞ってお伝えするためです 🙏)

Rspackとは

概要と特徴

公式ドキュメントを読んで得られた主な特徴は以下の通りです:

  1. 高速なビルドパフォーマンス

    • Rustによる実装で、JavaScriptベースのWebpackと比較して大幅に高速化
    • マルチスレッド処理による並列化の最適化
    • インクリメンタルビルドの効率化により、開発時の再ビルド時間を短縮
  2. Webpackとの高い互換性

    • Webpackの設定ファイル形式との互換性
    • 多くのWebpackプラグインやローダーとの互換性
    • 既存のWebpackプロジェクトからの移行が比較的容易
  3. モダンなアーキテクチャ

    • プラグインシステムの改善
    • ビルドキャッシュの最適化
    • メモリ使用量の削減
  4. 開発者体験の向上

    • 高速なHMR(Hot Module Replacement)
    • 詳細なエラーメッセージとデバッグ情報
    • 開発サーバーの応答性向上

パフォーマンス比較

執筆時点で最新バージョンは1.3系ですが、Rspack公式により公表されたベンチマーク[3](ちょっと古めです)によると、大規模プロジェクトでは以下のようなパフォーマンス向上が報告されています:

  • コールドスタート時のビルド時間: Webpackと比較して5〜10倍高速
  • 開発時の再ビルド時間: 2〜5倍高速
  • メモリ使用量: 最大50%削減

これらの改善は特に大規模なプロジェクトや、多数のモジュールを持つアプリケーションで顕著に現れます。

Webpackからの移行手順

本記事では既存のWebpack設定を極力変更せず、互換性を重視した移行アプローチを採用しました。以下に具体的な移行手順を紹介します。

基本方針

  • 既存のwebpackプロジェクトの構成を極力変更しない
  • 完全な互換性があるプラグインのみを置き換える
  • 利用するローダーは極力変更しない
  • rspackにbuiltinされているSWCローダーへの切り替えは移行後に実施する

1. Rspackの導入

まず、必要なパッケージをインストールします:

yarn add -D @rspack/core @rspack/cli

次に、package.jsonのscriptsを変更します:

{
  "scripts": {
    "build": "NODE_ENV=production rspack build", // NODE_ENV=production webpack から置き換えます
    "watch": "NODE_ENV=production rspack --watch", // NODE_ENV=production webpack --watch --progress から置き換えます
  }
}

既存のwebpack.config.jsをrspack.config.jsとしてコピーします:

cp webpack.config.js rspack.config.js

2. プラグインの置き換え

互換性のあるプラグインを置き換えます:

DefinePlugin

- const webpack = require('webpack');
+ const { rspack } = require('@rspack/core');

module.exports = {
  // ...
  plugins: [
-   new webpack.DefinePlugin({
+   new rspack.DefinePlugin({
      // ...
    }),
  ],
}

WebpackManifestPlugin → RspackManifestPlugin

yarn add -D rspack-manifest-plugin
- const { WebpackManifestPlugin } = require('webpack-manifest-plugin')
+ const { RspackManifestPlugin } = require('rspack-manifest-plugin')

module.exports = {
  // ...
  plugins: [
    // ...
-   new WebpackManifestPlugin({
+   new RspackManifestPlugin({
      fileName: 'manifest.json',
      publicPath: '/packs/',
      writeToFileEmit: true,
    }),
  ]
}

ForkTsCheckerWebpackPlugin → TsCheckerRspackPlugin

yarn add -D ts-checker-rspack-plugin

プラグインのREADMEによるとincrementalビルドを有効化するためには mode: 'write-references' の設定が必要との事だったので追加します:

- const { ForkTsCheckerWebpackPlugin } = require('fork-ts-checker-webpack-plugin')
+ const { TsCheckerRspackPlugin } = require('ts-checker-rspack-plugin')

module.exports = {
  // ...
  plugins: [
    // ...
-   new ForkTsCheckerWebpackPlugin({
+   new TsCheckerRspackPlugin({
      typescript: {
        memoryLimit: 4096,
+       mode: 'write-references', // incrementalビルドによるチェックの高速化のため追加します
      },
    }),
  ]
}

併せて tsconfig.json にもincrementalビルドを有効にする設定を追加します:

{
  "compilerOptions": {
    // ....
    "incremental": true,
  }
}

3. キャッシュ設定の移行

Rspackのキャッシュ設定はWebpackとは若干異なります。以下のように変更します:

module.exports = {
  // ...
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename],
    },
  },
}
// ↓ へ変更
module.exports = {
  // ...
  cache: true,
  experimental: { // キャッシュの永続化はまだexperimentalなので設定を移動します
    cache: {
      type: 'persistent',
      buildDependencies: [__filename],
      storage: {
        // Default: node_modules/.cache/rspack
        type: 'filesystem',
      }
    },
  },
}

キャッシュの永続化について

キャッシュの永続化(type: 'persistent')については storage.typefilesystem としているためFile I/Oへの依存によるオーバーヘッドが気になる点です。しかし今回は基本方針に沿って、同じ構成になるようにしました。

オーバーヘッドが影響するようであれば別途対策をする必要があります。そこは移行後に継続して改善したいところですね。

experimental について

experimental という設定項目はあくまでも「実験的」機能として用意されているものですので将来的に仕様変更される可能性があります。
ロードマップによると将来的な正式サポートは予定されているようでした。

アップデート時には設定の見直しが必要になる場合があるため、その辺りのロードマップにもご注意ください 🙏

4. ローダー処理の並列化

Rspackではローダー処理の並列化オプションを有効にすることで、さらなるパフォーマンス向上が期待できます:

module.exports = {
  // ...
  experimental: {
    // ...
    parallelLoader: true // こちらもexperimentalですが有効化してローダーの処理を高速にします
  },
}

5. 解析ツールの変更

WebpackのBundleAnalyzerPluginの代わりに、RspackエコシステムにあるRsdoctorを導入します:

yarn add -D @rsdoctor/rspack-plugin
- const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
+ const { RsdoctorRspackPlugin } = require('@rsdoctor/rspack-plugin')

module.exports = {
  plugins: [
    // ...
    (process.env.RSDOCTOR &&
      new RsdoctorRspackPlugin({
        mode: 'brief',
        disableClientServer: true,
        experiments: {
           enableNativePlugin: true,
        },
        output: {
           reportDir: '/path/to/reportDir',
        },
        supports: {
           parseBundle: true,
           generateTileGraph: true,
        }
     })
    ),
  ]
}

Rsdoctorを使用して生成されたタイルグラフの例:

Rsdoctorタイルグラフ

パフォーマンス比較

実際に移行を行った結果、ビルドパフォーマンスに大きな改善が見られました。

ビルド時間の変遷

プロジェクトのビルド時間は、前記事の最適化から本記事までの取り組みによって段階的に改善されてきました:

  1. 最適化前のWebpack:

    • バンドルサイズが肥大化した状態で、ビルド時間は約10分
    • 大規模なJavaScriptアプリケーションでは珍しくない状況でした
  2. バンドルサイズ最適化後のWebpack:

    webpack 5.99.1 compiled with 12 warnings in 140024 ms
    
    • 約2分20秒(2.3分)のビルド時間
    • バンドルサイズの最適化により、約75%の時間短縮を実現
  3. Rspack移行後:

    Rspack compiled with 36 warnings in 59.08 s
    
    • 約1分のビルド時間
    • Webpackの最適化版と比較して約60%の時間短縮
    • 最適化前と比較すると、約90%の時間短縮を実現

この一連の最適化により、当初10分程度かかっていたビルド時間が約1分程度までに改善され、開発サイクルの効率化に大きく貢献しています。特に、頻繁にビルドが必要な開発環境では、この時間短縮がデベロッパーエクスペリエンスを大幅に向上させています。

注意点と課題

移行過程で以下のような課題も見つかりました:

  1. built-inローダーへの切り替え

    • CSS/SASSのトランスパイルはRailsのassets:precompileで行っているため、builtin:lightningcss-loaderへの置き換えも将来的な検討事項としました
  2. キャッシュ設定

    • Rspackのファイルシステムキャッシュはまだ実験的な機能であり、I/Oに依存するため若干のオーバーヘッドがあります
    • キャッシュありのビルドでは、最適化されたWebpack環境と比較して大きな差が出ない場合もありました
  3. バンドル解析ツール

    • Rsdoctorは高機能ですが、リソース消費が激しく、一部のモードではメモリ不足が発生することがありました
    • mode: 'brief'設定とdisableClientServer: trueオプションで安定動作を確保しました
  4. ビルド時のwarnings

    • rspackに切り替えることでビルド時に出力されるwarningsの数が増えましたが、実際の挙動確認やテストでは、今回の移行によって既存動作への影響はありませんでした
    • ただし、warningsの数が増えることを許容するわけではないので随時解消していく必要があります

まとめ

WebpackからRspackへの移行は、比較的スムーズに行うことができました。Rspackの高いWebpack互換性により、設定ファイルの大部分をそのまま流用でき、必要な変更は最小限に抑えることができました。

最初にバンドルサイズの最適化によって元々10分程度かかっていたビルド時間を2分強まで短縮し、さらにRspackへの移行によって約1分あたりまで改善できました。

この一連の最適化により、ビルド時間を当初の約10%まで削減することに成功し、開発効率の向上に大きく貢献しています。特にコールドスタート時のビルドパフォーマンスの向上は顕著でした。

今後の展望としては:

  • built-inローダー(SWC、LightningCSSなど)への段階的な移行
  • Rspackの新機能やアップデートの積極的な活用
  • Storybookのrsbuild化

Rspackはまだ比較的新しいツールですが、Webpackとの高い互換性と優れたパフォーマンスにより、多くのプロジェクトにとって魅力的な選択肢となっています。特に大規模なプロジェクトや、ビルド時間がボトルネックとなっている場合には、検討する価値があるでしょう。

本記事が、WebpackからRspackへの移行を検討されている方々の一助となれば幸いです。

脚注
  1. Rspack(Rust-based webpack)は、ByteDanceが開発したRust言語で実装されたJavaScriptバンドラーです。Webpackとの互換性を保ちながら、ビルドパフォーマンスを大幅に向上させることを目的としています。 ↩︎

  2. Rsbuildは、Rspackをベースにした高性能なビルドツールで、Storybookなどのツールと統合しやすいように設計されています。 ↩︎

  3. 参考にしたベンチマークの情報はv0.x系の時に発表された内容で、執筆時点では最新ではない可能性があるので参考程度にとどめてください。 ↩︎

株式会社primeNumber

Discussion