TL;DR
- webpack→Rspackに移行した
- ビルド時間が約63%短縮された
- 基本的に互換性のある置き換えを選択、差分を減らす方針にした
はじめに
こんにちは、フリューのソフトウェアエンジニアkitajimaです。ピクトリンクの開発を担当しています。今回は自社プロダクトについて、webpackからRspackに移行したことでフロントエンドビルド時間が大幅に短縮した事例を紹介します。
対象のプロダクトについて
かつてピクトリンクweb版のメインとなっていた、以下のような技術スタックのフロントエンドモジュール(以下、本プロダクト)が対象です。
- Vue3(scss/JavaScript)
- Vueインスタンスが複数あるマルチページアプリケーション(MPA)
- 1ページにつき、以下のような構成
- HTMLテンプレート
- Vueコンポーネント群
- Vueインスタンスをマウントするjsファイル
- webpack5
// entry1.js
import { createApp } from 'vue'
import Entry1Page from '../component/pages/Entry1Page.vue'
const vue = createApp(Entry1Page)
.mount('#entry1')
export default vue
<!-- entry1.html --> <!DOCTYPE html> <html lang="ja"> <head> <!-- 略 --> </head> <body> <div id="entry1"></div> </body> </html>
課題
開発ビルド/本番ビルドともにビルド時間が全体で5分程度と長く、リリース作業や開発作業のボトルネックになっていました。ビルドタスクは複数ステップからなりますが、その中でも特にwebpackのビルド処理に時間がかかっていることがわかっていました。
既存のビルド構成
webpack.{env}.config.jsで以下のように、100個程度(=サービスのページ数)のentryに対して個別にhtml-webpack-pluginを使い、js/HTML生成を行っていました。
// webpack.{env}.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// ...
entry: {
'entry1': './src/webpack/entry1.js',
'entry2': './src/webpack/entry2.js',
// ...
},
// 中略
plugins: [
new HtmlWebpackPlugin({
filename: 'entry1.html',
chunks: ['entry1'],
inject: 'body',
template: './src/webpack/templates/entry1.html',
}),
new HtmlWebpackPlugin({
filename: 'entry2.html',
chunks: ['entry2'],
inject: 'body',
template: './src/webpack/templates/entry2.html',
}),
// 100個程度
],
Rspackとは
RspackはRust製で高速と謳われているwebバンドラです。
webpackとのAPI互換性を推しており、特にpluginやloaderはwebpackと互換性があります。多数のコミュニティ製plugin/loaderがそのまま利用可能です。
特にwebpackのpluginとの互換性リストを提示してくれているのが嬉しいです。
選定理由
背景: 本プロダクトは将来的に廃止予定
ピクトリンクのweb版はNext.js製の新プロダクトに移行中であり、日々ページ単位で移行しています。本プロダクトは消えゆく運命であるため、既存コードの大幅な書き換え等積極的なメンテナンスは避けたい状況です。
そんな中Rspackが浮かび上がってきた
webpack互換を謳っているビルドツールであるRspackの正式リリース(v1.0.0)が2024年8月に登場しました。 本プロダクトはwebpack5で動いており、Rspackはwebpack互換を謳っています。Vite等のwebpack互換ではないビルドツールに移行するのに比べ、コスパよくビルド時間を短縮できる可能性があると考えました。
しかも、僕たちが使っているhtml-webpack-pluginの移行先であるhtml-rspack-pluginが公式で用意されており、
its build performance is significantly better than html-webpack-plugin, especially in scenarios where many HTML files are being built.
とあるではありませんか。まさに本プロダクトのことですね。
試験的にRspackに置き換えてローカルビルドタスクを実行したところ、実行時間が大幅に短縮されることがわかりました。
そのため、本腰を入れてRspackへの置き換えを進めることにしました。
移行作業
公式ドキュメントのMigrate from webpackに従い、移行作業を進めました。バージョンは作業当時の最新であるv1.6.8を利用しています。
大まかには以下のような内容であり、既存のwebpack設定をできるだけ流用しつつ、互換性のあるplugin/loaderに置き換えることで実現できました。
webpack→@rspack/core、webpack-cli→@rspack/cliに置き換えwebpack.{env}.config.jsをrspack.{env}.config.jsにリネーム- 互換性のあるplugin/loaderに置き換え
html-webpack-plugin→html-rspack-plugin- その他はそのまま利用
作業のポイント
ビルド結果の差分を最小限に抑える
確認方法
以下のテックタッチ様の記事から着想を得て、ビルド結果をBiomeでフォーマットすることで差分を確認しました。
記事ではCIでの差分検出に利用していますが、今回は1回きりのビルドツール移行となるため、ローカル差分確認ツールであるdifitを利用した差分確認に留めました。
また、ビルドツール自体が変わっているため、ビルド結果は完全に同一ではありません。
- 出力ファイル数、およびソースとの対応関係が同一か
- 差分が、ビルドツール特有の変数や記述の違いくらいである(このあたりは主観なので、あくまで
明らかに異常でないかの確認に留まります)
という観点で差分を確認しました。
もちろんユニットテストやE2Eテストを実行し、動作確認も行った上でリリースしています。
確認例: scriptタグのdefer属性
デフォルトの設定では、出力されるhtmlに埋め込まれるscriptタグについて、以下のような差分がありました。
- <script src="/js/entry1.js"></script> + <script defer src="/js/entry1.js"></script>
html-rspack-pluginでは、デフォルトでdefer属性が付与されるようです。
defer属性がつくことで、htmlのパースがjsの読み込みにブロックされることが無くなるので嬉しいのですが、スクリプトの実行タイミングが変わる可能性があります。
ここは移行方針ビルド結果の差分を最小限に抑えるに合わせ、RspackのscriptLoadingオプションを'blocking'に設定し、webpackと同様の動作にしました。
new HtmlRspackPlugin({
// ...
scriptLoading: 'blocking', // webpackと同様の動作にする
}),
結果
いずれもビルド時間が大幅に短縮されました。
| 環境 | webpack | Rspack | 改善率 |
|---|---|---|---|
| ローカルビルド(MacBook Pro M2, 32GB) | 1:34 | 0:44 | 約53%短縮 |
| 本番ビルド(GitHub Actions) | 3:10 | 1:11 | 約63%短縮 |
※直近の実行ジョブからピックアップした値であり、そのうちRspackが実際に動いているステップのみを抜粋しています。
ビルド時間全体を見ても、5分程度から3分程度にまで短縮されており、リリース作業や開発作業がすぐに完了するようになりました。
さいごに
実際の移行作業は思ったよりもスムーズに完了し、隙間時間で完了させることができました。調査自体はガッツリ行いましたが、移行ガイドをはじめとした公式ドキュメントが充実しており、利用者としても移行しやすい環境が整っていると感じました。廃止予定のプロダクトではありますが、開発効率をコスパよく改善できて満足しています。