はじめに
Laravelを自身の技術スタックの中心として使用してきた私ですが、以前はフロントエンドのassetのビルドにはwebpackを用いていました。
LaravelではLaravel Mixを用いてWebpackを簡単に扱うことができていました。
しかし、Webpackの開発終了やフロントエンドでのビルドツールの選択肢の増加に伴って、Laravel9からViteが公式標準のビルドツールとなりました。
今回の記事では、バックエンドエンジニアを対象にそもそもビルドツールとは、という説明から始めて、Viteの基本的な特徴をまとめてみます。
実際にViteを利用し始める際にはこの記事の内容ほど詳しく知る必要はないと思いますが、実際に利用するうえではある程度仕組みをわかっておくとトラブルシューティングの際に役立つと思います。
ビルドツールとは
まず、WEBアプリケーションにおけるビルドツールとはなにかから再確認します。
WEBアプリケーションの形式として、画面を構成するHTML、CSS、JavaScriptを組み立てる必要があります。
このとき、サーバー側でHTMLを組み立てるのか、クライアント側のJSで組み立てるのかで大きく分かれています。
また、サーバー側でHTMLを組み立てる方式の中でも、バックエンド言語と同じものを利用して、テンプレートエンジンを用いて組み立てるのか、JSを用いて組み立てるのかでも分かれます。
一般的に、サーバーサイドから空のHTMLを返した上でクライアント側のJSでHTMLを組み立てる形式をSPA(シングルページアプリケーション)と呼びます。
一方、サーバーサイドでHTMLを組み立てる形式をSSR(サーバーサイドレンダリング)と呼びます。
React 研修 (2024) - Speaker Deck
ビルドツールとは、もともとJavaScriptが他のモジュールを呼び出すことができなかったため、バンドルして1つのファイルにする作業を自動化するツールでした。
現在ではESMが登場したことにより、バンドルする必然性はなくなっています。
Webpackの基本的な特徴
もともとWeb開発ではブラウザで実行されるJSを直接書くことが一般的でした。
CommonJSの登場により、モジュール化が可能になり、バンドル(ブラウザのためのJSを作る作業)を行うツールが登場します。
Webpackは、JS向けではあるものの、CSSや画像などのアセットをバンドルすることができます。
また、単純なバンドルだけではなく、どの環境でも実行できるシンプルなコードへの変換やコードの最適化を行ってくれます。
Webpackの人気は高く、多くのプロジェクトや フレームワークでは事実上の標準ツールとして採用されていました。
2025年現在においても、多くのプロジェクトで採用されていますが、Turbopackという後継ライブラリの開発にWebpack開発チームのリソースを集中しており、今後の開発はTurbopackに移行する予定です。
Viteとは
ViteはWebpackに代わるビルドツールですが、その登場の背景にはJSの進化があります。
ES Modulesがほぼすべての最新ブラウザでサポートされるようになった今日、Webpackのような従来型のバンドラーは、開発時のコールドスタートやHMRにおいて遅延が生じるという問題が指摘されるようになりました。
下記のスライドは視覚的にもわかりやすいと思います。
ビルドツールViteを10分で解説! - Speaker Deck
逆に言うと、ES Modulesがブラウザで利用できるようになるまで、開発者はモジュール化されたJavaScriptを利用するネイティブな手段を持っておらず、バンドラとしてWebpackやRollupを用いていました。
アプリケーションが大規模になるにつれ、JavaScriptのコード量も増え、開発サーバーの起動に時間がかかるようになってきます。また、HMRのような仕組みを持っているバンドラでさえファイルの編集がブラウザに反映されるまで数秒かかることもあります。
こういったフィードバックの遅さは、たとえ一回の時間が短いとしても、積み重なると開発者の生産性や幸福度に大きな影響を及ぼす可能性があります。
Viteが解決するのは、ES Modulesを前提としてこういった速さに関わる問題です。
ここからはViteが何をどのように解決するのかを、公式サイトの説明を交えながら説明します。
Webpackと比べた際のViteの主な特徴をまとめると、以下のようになります。
開発サーバーの高速起動
ブラウザがネイティブにES Modulesを解釈できることを前提に、必要なときに必要なモジュールだけを配信するため、初回起動やファイル更新時の待ち時間が大幅に短縮される様になりました。
もう少し技術的な仕組みを理解しておきましょう。
Viteの開発サーバーでは、まず大きく分けて依存関係とソースコードを分類します。
依存関係
これまでのバンドラ形式の開発サーバーでは、JSファイルを変更すると、全体を再度バンドルする必要がありました。
この方法が非効率なことは明白で、再構築にかかる時間は線形的に増加します。また、モジュールグラフの一部を無効化して高速化するようなバンドラもありますが、WEBページのリロード時にはアプリケーションの状態が失われてしまうため、開発サーバーを利用したHMRが発明されました。
実際には、開発中に変更されるのは一部のJavaScriptであり、大部分はあまり変更されないプレーンなJavaScriptです。
Viteは、esbuildと呼ばれるGo言語で書かれた高速なバンドルツールを利用して依存関係の事前バンドルを行います。
esbuild - An extremely fast bundler for the web
ソースコード
依存関係とは対象的に、ソースコードにはプレーンなJavaScriptではないものが多く含まれ、頻繁に編集されます。
例えば、JSXやCSS,VueやSvelteのコンポーネントがそれに当たります。また、現代ではルーティングによってページを切り替える事が多く、すべてのコードを同時に読み込む必要はありません。
HMRをネイティブES Modulesで行うため、ファイルが編集されたとき、Viteは編集されたモジュールと最も近いHMRをバウンダリ間のつながりを無効化するため、アプリケーション全体のサイズに関係なく一貫した速度でファイルの編集を反映することができます。
Viteは、ES Modulesを前提としてソースコードを提供します。つまり、バンドラが行っていた処理の一部を、ES Modulesを利用できる様になったブラウザに任せる、というイメージです。
Viteの仕事は、ブラウザからのリクエストに応じてソースコードを変換して提供するまで、になります。
クライアント側の機能の利用による高速化
クライアントの機能をうまく使って、フルページのリロードも高速化します。
公式サイトにもありますが、ソースコードモジュールのリクエストでは、304 Not Modifiedを利用して条件が作成されます。
具体的には、ブラウザがサーバーにファイルをリクエストする際、以前に取得したファイルの情報(例えばLast-ModifiedヘッダーやETag)をIf-None-MatchヘッダーとIf-Modified-Sinceヘッダーと組み合わせて送信します。
次に、サーバーは そのファイルが前回取得したときから変更されていないかどうかを確認し、もし変更がなければHTTPステータス304 Not Modifiedを返します。この304レスポンス自体にはファイル本体が含まれず、ブラウザは自分のキャッシュから以前のファイルをそのまま使用します。この仕組みによって不要なデータ転送を省く事ができるので、全体のリロード時間を短縮することができます。
依存ファイルについてもブラウザの機能を利用して効率的に処理をしています。依存関係(通常はサードパーティライブラリなど)は、Viteのビルドプロセスで一度バンドルされるとその後は内容が変わらないとみなします。
サーバーはこれらのモジュールに対して、Cache-Control: max-age=31536000, immutable
というヘッダーを付与します。このmax-ageは1年を表す値で、immutableはそのファイルが一度キャッシュされたら絶対に変わらないということを示します。
結果として、ブラウザは依存関係を一度キャッシュすれば、次回以降はサーバーに再リクエストせずに、キャッシュから直接読み込むため、ネットワーク負荷が大幅に減少し、ページの読み込みが高速になります。
まとめると、Viteは変更されやすいソースコードには304 Not Modifiedを活用してブラウザのキャッシュを有効に活用し、更新がほとんどない依存モジュールについては長期キャッシュを指示するHTTPヘッダーを利用することで、両方のケースで効率的にリソースを配信し、開発中のリロードや再ビルドを高速化します。
Viteの本番環境でのビルド
Viteは、これまで紹介したように開発環境では依存関係の事前バンドルのためにesbuildを利用してバンドルを実行します。ただし、2025年4月時点では本番ビルドのためのバンドラーとしてはesbuildを利用しません。
本番環境でのバンドルにはRollupを利用します。当面の間、ViteはRollupの柔軟なプラグインAPI等のエコシステムを活用することを選択しており、パフォーマンスと柔軟性のトレードオフに優れているという判断をしています。
個人的にも同意できる内容だと思っていて、開発時は頻繁なソースコードの変更や依存関係の変更の可能性があるので、より高速な手段を選択し、本番環境では互換性の維持や安定性のために枯れた技術を選択するのは妥当かなと思います。
ただし、これは開発者を少し悩ませる問題があります。開発サーバーと本番環境で実際に使用されるコードが異なる可能性があります。対策としてはきちんとViteのビルドコマンドで本番用のビルドをしたり、ステージング環境の活用をするなどするしかないのが現状ですが、作者はRollupのRustによる改善をするためにRolldownというライブラリを準備中です。これは、完成すればViteにおけるesbuildをRollupの両方を置き換える計画になっています。現在は実験的な試用期間のため、本番環境では採用しづらいかもしれませんが、今後に期待しましょう。
深堀り 開発サーバー
ここまでViteの速さのひみつを確認してきましたが、古典的なWeb開発から見るといくつかの要素がブラックボックスであり、どうやって動作しているのか想像ができないなと感じました。
バックエンドから必要なHTML、CSS、JavaScriptを提供する開発サーバーをどのようにして構築するのでしょうか?
ファイルの検知をどのようにして行ってブラウザに反映させているのでしょうか?
Viteの場合を例に確認してみます。
ファイル監視
まずはソースコードの変更を検知する必要があります。これには内部的にchokidar
を用いてファイルの変更を検知しています。
Node.js環境下でファイルの追加、更新、削除をリアルタイムで監視できます。
paulmillr/chokidar: Minimal and efficient cross-platform file watching library
モジュールグラフの更新とキャッシュの無効化
ファイルの変更が検知された場合、Viteは内部で管理している該当部分のキャッシュを無効にし、次回そのモジュールに対するリクエストが来た場合、最新の内容に基づいて再構築を行います。
WebSocketによるHMR
Viteは内蔵しているWebSocketサーバーを通じて、ブラウザ側に「このモジュールが更新された」という通知を送ります。
クライアントは、この通知を受けると更新されたファイルのみを再取得し、ページ全体のリロードなしに変更を反映させます。
HTTPキャッシュとオンデマンド変換
更新が必要な場合、クライアントはHTTPリクエストをします。Viteはこのリクエストに対して必要に応じたオンデマンドの変換(トランスパイルやES Modulesの変換など)を実行します。
そして先述したHTTPヘッダーを利用したキャッシュを活用して通信不可を減らしつつ高速な更新を実現しています。
【余談】Laravelとの統合
弊社でメインで利用している技術スタックであるLaravelを利用する場合、Viteと完全に分離したアプリケーションを構成する方法と、Laravelと統合してViteを活用する方法があります。
BladeのようなテンプレートエンジンとViteを統合する場合のJSはLaravelから返されるわけですが、この際の中身は下記のように構成されます。
- ViteのHMRクライアント(例:
<script type="module" src="http://localhost:5173/@vite/client"></script>
) - アプリケーションのエントリーポイント(例:
<script type="module" src="http://localhost:5173/resources/js/app.js"></script>
)
つまり、完全に分離したアプリケーションの場合と同じくあらかじめビルドされたバンドルは存在せず、各モジュールは必要に応じて動的に変換・提供される仕組みになっているようです。
もちろん本番環境では事前にビルドされた最適化済みのバンドルが返されます。
まとめ
- バンドラとは、HTML、CSS、JavaScriptを提供する際にモジュール化を助けるために生まれたツール
- Webpack等は開発が事実上ストップしており、最新ブラウザがデフォルトでES Modulesをサポートするため、バンドラとしての価値は薄れつつある
- 対照的に、バンドルにかかる時間が目立つようになり、Viteが生まれる
- ViteはES Module前提のツールで、開発サーバー等に高速化のための工夫が沢山組み込まれている
- 現状だと開発サーバーと本番環境で利用されるツールが違うことに留意