JavaScript業界は非常に動的です。「古い」は必ずしも「時代遅れ」を意味しません。逆もまた然りで「新しい」は必ずしも「良い」ことにはなりません。

技術を選択する際に注意すべきは、プロジェクトとの整合性です。この原則は、JavaScriptモジュールバンドラーを考えるときにも強く言えます。バンドルが歴史あるものであれ、導入されたばかりのものであれ、それぞれに明確な利点や制限があります。

この記事では、ViteとWebpackという2つの人気のバンドラーに迫ります。その機能、特徴、アーキテクチャ、およびエコシステムへの統合方法に基づいて、これらのバンドラーを比較してみましょう。

JavaScriptモジュールバンドラーとは

ストレーナーに入る複数のアセットのイラスト
JavaScriptのバンドルを表したイラスト

JavaScriptバンドラーとは言うなれば、ウェブ開発において複数のJavaScriptファイルを1つのファイルにまとめるツールです。ウェブアプリケーションのリクエスト数を減らすことでJavaScriptコードの管理を簡素化し、最終的にパフォーマンスを引き上げることができます。

例えば、module1.jsmodule2.jsという2つのJavaScriptファイルがあるとします。

// module1.js
export const greet = (name) => {
    console.log(`Hello, ${name}!`);
}

そして、module2.jsには以下の内容が含まれています。

// module2.js
export const farewell = (name) => {
    console.log(`Goodbye, ${name}!`);
}

これらのモジュールを1つのファイルにバンドルするには、Rollup、Webpack、Parcelのようなバンドルツールが使えます。例えば、以下のコードでindex.jsファイルをプロジェクトディレクトリ内に作成するとします。

// index.js
import { greet } from './module1.js';
import { farewell } from './module2.js';

greet('Kinsta');
farewell('Server Troubles');

JavaScriptバンドラーを利用すると、module1.jsmodule2.jsindex.jsをウェブアプリケーションの用途に合わせて最適化された1つのバンドルにまとめることができます。

最近のウェブブラウザはESモジュールHTTP/2のような技術をサポートし、リクエストのオーバーヘッドに関する懸念に対処していますが、JavaScriptバンドラーはさまざまなコード拡張のために今もなお不可欠な存在です。バンドルは、圧縮、トランスパイル、最適化など、コードの変換を行います。

さらに、JavaScriptモジュールバンドラーは、さまざまなブラウザ間の互換性を確保します。ブラウザ固有の問題を解決し、ユーザーが選択するウェブブラウザに関係なく一貫したエクスペリエンスを確保可能です。

このバンドリングプロセスは、ウェブアプリケーションの読み込み速度を高速化するだけでなく、特に本番環境において効率的なパフォーマンスを後押しします。JavaScriptバンドルとウェブ開発におけるその役割を理解したところで、ViteとWebpackに焦点を移しましょう。

ViteとWebpack─概要

ViteとWebpackが(リソース管理と最適化が不可欠な)ウェブ開発の分野をリードしていることは明らかです。しかし、詳細な比較に入る前に、各バンドラーを簡単に見て何が際立っているのかを理解しましょう。

Vite─素早いオンデマンド開発

Vite(「ヴィート」と発音)はスピードと効率を優先した、ウェブ開発者のための画期的な選択肢です。Viteを際立たせているのは、そのオンデマンドバンドルというアプローチでしょう。すべてのコードとアセットを事前にバンドルする代わりに、ViteはモダンブラウザのネイティブESモジュールを活用し、開発中にブラウザに直接コードを提供します。これにより、ほぼ瞬時にHot Module Replacement(HMR)が行われ、コールドスタートの時間が短縮できます。

Viteの開発サーバーはこのオンデマンドアプローチで強さを発揮し、完全な再コンパイルなしに変更内容を素早く確認することができます。また、効率的な本番ビルドのためにロールアップも使用。その結果、Viteは素早い開発と確かな生産パフォーマンスを実現しています。

Webpack─整理と高い適応性

Webpackは、2012年以来着実に進化し、現代のウェブ開発の礎としてその名を知らしめています。Webpackの優れた点が、ウェブサイトのコンポーネントの整理のしかたです。コードをモジュールに整理することで、読み込み時間とユーザーエクスペリエンスの最適化が捗ります。

Webpackの適応性の高さは驚くべき強みです。シンプルなタスクから複雑なタスクまで、プロジェクトをカスタマイズできます。ワークフローの調整と正確なプロセス構築に有用です。

ViteとWebpackの類似点と相違点

ViteとWebpackの基本的な概念を理解したところで、類似点と相違点をさらに詳しく理解しましょう。これらのバンドラーの様々な側面を比較しそれぞれの優れている点を総合的に見てみます。

1. 構造と指針

どちらのバンドラーもウェブアプリケーションの構築と最適化について独自のスタイルを誇ります。両者にはプラグインという共通点があり、その機能を拡張するためにプラグインを自由に選んで追加したり作成したりすることができます。開発者にとって多目的なツールとなっています。

Viteの基本理念は、無駄のなさと拡張性にあります。ミニマリストとしての戦略を堅持し、一般的なウェブアプリの開発パターンに重点を置いています。このアプローチは、長期的なプロジェクトの保守性を確保する上で重要です。

Viteはロールアップベースのプラグインシステムに依存しており、外部プラグインによる機能の実装を可能にし、コアの肥大化を防いでいます。これにより、コアの合理化が進み、メンテナンスの行き届いたプラグインによる活発なエコシステムが促進されます。さらに、ViteはRollupプロジェクトと積極的に協力し、互換性を確保しながらプラグインエコシステムを維持しています。

Webpackはカスタマイズ性に優れ、基本的な作業から複雑な取り組みまで、特定のニーズに合わせてプロジェクトを調整可能です。Webpackは、ビルドプロセスのあらゆる側面を柔軟に設定できるため、パーソナライズを意識した開発体験を求める開発者に向いています。

さらに、Webpackは、ウェブプロジェクトにレゴを組み立てるようなモジュラーアプローチを導入しています。コードベースのすべてがWebpackのモジュールであり、さまざまな方法で依存関係を表現できます。これについて、いくつかの例を挙げてご紹介します。

  1. ES2015importステートメント
  2. CommonJSrequire()ステートメント
  3. AMDdefineおよびrequireステートメント
  4. @importcss/sass/lessファイル内のステートメント
  5. スタイルシートurl()または HTML<img src="">ファイル内の画像URL

Viteの指針

無駄がなく拡張可能であるというViteの指針は、ウェブアプリケーションを構築するアプローチにもよく表れています。ウェブアプリケーション開発中に、ESモジュールのような最新のJavaScript機能を取り入れたいとします。Viteを使えば簡単に実現できます。簡単な例を挙げてご紹介します。

// app.js
import { greet } from './utilities.js';

const worker = new Worker(new URL('./worker.js', import.meta.url));

// ウェブワーカーでの計算のシミュレーション
worker.postMessage({ input: 42 });

worker.onmessage = (e) => {
  const result = e.data.result;
  console.log(`Result from the web worker: ${result}`);
};

const message = greet('Hello, Vite!');
console.log(message);

このコードでは、ViteはESモジュールの使用を受け入れ、開発中に時間のかかるバンドルステップを避け、その場での効率的なコードのバンドルを行っています。このモジュラーアプローチにより、依存関係を効率的に管理し、保守性の高いコードを確保することができます。これは、ミニマリズムであり開発者に優しいViteの方向性を示す特徴です。

Webpackの指針

Webpackのモジュラーという指針は、大規模なプロジェクトに取り組む際に特に有用です。様々なJavaScriptモジュールで大規模なウェブアプリケーションを構築している状況を考えてみましょう。Webpackを使えば、複数モジュールをシームレスに組み立て、可読性、保守性、ウェブサイトの読み込み時間を改善できます。以下は単純な例です。

// webpack.config.js
const path = require('path');

module.exports = {
  entry: './app.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },
  module: {
    rules: [
      {
        test: /.js$/,
        use: 'babel-loader',
        exclude: /node_modules/,
      },
    ],
  },
};

この例では、Webpackを使用することで、ビルドプロセスを構成し、コードを最適化し、アセットを効率的に処理することができます。プロジェクトをモジュールに整理し、Babelのようなローダーを使用することで、ユーザーエクスペリエンスを改善するクリーンでモジュール化を意識したコードを記述可能です。これは、カスタマイズ性と柔軟性を確保し、開発者が特定のニーズに合わせてプロジェクトを調整できるようにするというWebpackの方針を如実に表しています。

ViteとWebpackはそれぞれ異なる方向性を持つものの、どちらも現代のウェブ開発の限界を押し広げるという共通の目標を掲げています。Viteは近代的なコードパターンに重点を置き、ソースコードのECMAScript Modules (ESM)を推進し、Worker構文のような新たな標準を奨励しています。

一方Webpackは、Node.jsとCommonJSに起因する課題への解決策として進化し、ウェブ開発におけるモジュールの採用を推進してきました。Webpackの自動依存性収集は、パフォーマンスの向上と相まって、シームレスな開発者体験を後押ししています。

2. 人気、コミュニティ、エコシステム

ViteとWebpackの人気やコミュニティ形成の推移は以下の画像をご覧ください。

過去5年間のViteとWebpackのGoogleでのトレンド比較
過去5年間のGoogleトレンドにおけるViteとWebpackの比較

Viteは2020年にデビューした新参者です。Viteは登場から比較的日が浅いにもかかわらず、急速に注目を集め、最新のウェブ開発の分野で有望な存在となっています。

対照的にWebpackは2012年に設立され、かなりのロケットスタートを切っています。この業界に身を置くことで、時間をかけ成熟したエコシステムと強固なコミュニティを築くことができます。

過去5年間のViteとWebpackのnpmtrendsでの比較
過去5年間のnpmtrendsにおけるViteとWebpackの比較

npmtrendsのグラフからは、ViteとWebpackのダウンロード数の比較が確認できます。これは、Webpackが一貫してダウンロード数の面で顕著な位置を維持し、その長年の存在と開発者コミュニティ内での使用範囲の広さを明確に示しています。

ViteとWebpackのスター数を時系列で比較した様子
ViteとWebpackのスター数(時系列での比較)

GitHubの人気やコミュニティからの支持を示す指標であるスター数の推移を見てみると、Viteは60,318スターという優秀な数字を誇り、Webpackは63,598スターとそれを上回る存在感を見せています。このスターの数は、両プロジェクトにおける認知度と積極的な関与を反映しています。Viteの急成長とWebpackの持続的な人気は、ウェブ開発エコシステムの中で貴重な選択肢となっています。

3. 設定と使いやすさ

ViteとWebpackの両方に、特定の要望に応じてバンドルを調整する多くの設定オプションが用意されています。しかし、双方の間には注目に値する重要な違いもあります。両ツールの設定と使いやすさを探ってみましょう。

Viteの合理化された設定

Viteは、ウェブ開発の旅路を簡素化するゼロコンフィグの指針によって他を圧倒しています。最小限の手間で基本的なVue 3コンポーネントライブラリを作成できることを意味します。そのようなプロジェクトの簡単なViteの設定が以下の通りです。

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
})

上記の例では、Vue.jsのVite公式プラグインをインポートしてインストールしています。Viteの強みとして、あらゆるプロジェクトに適した設定を自動で検出することができます。

Webpackの複雑な設定

一方、Webpackでは詳細な設定が必要になる傾向があります。最近のバージョンではゼロコンフィグに移行していますが、Viteほど自動ではありません。Vue 3では、基本的なWebpackの設定は次のようになります。

const webpack = require('webpack');
const path = require('path');
const { HotModuleReplacementPlugin } = require('webpack');
const { VueLoaderPlugin } = require('vue-loader');

module.exports = {
    entry: './src/main.js',
    output: {
        path: path.resolve(__dirname, './build'),
        filename: 'bundle.js',
    },
    module: {
        rules: [
            {
                test: /.js$/,
                exclude: /(node_modules|bower_components)/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env'],
                    },
                },
            },
            {
                test: /.vue$/,
                use: {
                    loader: 'vue-loader',
                },
            },
            {
                test: /.css$/,
                use: ['vue-style-loader', 'css-loader'],
            },
        ],
    },
    resolve: {
        alias: {
            vue: 'vue/dist/vue.js',
        },
    },
    plugins: [
    new HotModuleReplacementPlugin(),
    new VueLoaderPlugin(),
    ]
};

Viteと比較すると、Webpackの設定はより手作業になります。エントリと出力のパスの指定、異なるファイルタイプ用のローダーの設定、特定の機能用のプラグインの設定など、複雑な設定が含まれます。設定の各部分を分解し、複雑さを理解していきましょう。

  • エントリーとアウトプットentryがWebpackのバンドルするアプリケーションのエントリーポイントを指定します。この場合、アプリケーションのメインのJavaScriptファイルがsrcディレクトリにあると仮定して、./src/main.jsに設定されます。一方、outputはバンドルしたファイルの保存場所を定義します。出力パスはpath.resolveで解決され、バンドルしたファイルはbundle.jsという名前でビルドディレクトリに保存されます。
  • モジュールのルールmodule.rulesセクションは、各種ファイルがどのように処理されるかを定義します。この例では、JavaScript ファイル(babel-loader─トランスパイル用)、Vueシングルファイルコンポーネント(vue-loader)、CSSファイル(vue-style-loadercss-loader─スタイルの管理用)のルールがあります。
  • エイリアスの設定resolve.aliasセクションでは、モジュールのインポート用のエイリアスを定義します。ここでは、Vueのエイリアスをvue/dist/vue.jsに設定します。
  • プラグイン:pluginsセクションには、HotModuleReplacementPluginがあります。これは、Hot Module Replacementを有効にする機能で、これを使うことで開発中にページをフルリロードすることなく変更を確認することができます。そしてVueLoaderPluginは、Vueのシングルファイルコンポーネント処理に必要です。

このセクションの締めくくりとして、Viteは使いやすさの点で際立っており、セットアップの簡素化と開発体験の合理化で他を圧倒します。最小限の設定要件とネイティブのESモジュールの使用により、初心者や素早い開発に向いている選択肢です。

対照的に、Webpackの広範にわたる設定の柔軟性は、複雑なプロジェクトには有益ですが、初心者の開発者にとっては困難である可能性があります。複雑なセットアップとメンテナンスは、特に小規模なプロジェクトでは開発の遅延を招く可能性があります。

4. 開発サーバー

開発サーバーは、開発者のワークフローにおいて重要な役割を果たし、効率と生産性に影響を与えます。ViteとWebpackを比較し、開発サーバーのパフォーマンスと使いやすさを評価し、ウェブ開発プロジェクトに適したツールを見極めましょう。

サーバー構成

Viteはビルトインですぐに使える開発サーバーで強さを発揮し、多くの場合、大規模な設定が不要です。

対照的に、Webpackは柔軟ですが追加の設定が必要です。Webpackのwatchモードwebpack-dev-serverwebpack-dev-middlewareのようなオプションを選択することができます。しかし、これらを活用するためには一般的に設定作業が必要になります。

コールドスタートの速度

従来のバンドラーベースのセットアップでは、念入りなクロールが行われ、配信前にアプリケーション全体を構築する必要があるため、特に複雑なプロジェクトでは顕著な遅延につながる可能性があります。

Viteは、根本的に異なるアプローチでこのコールドスタートに革命をもたらし、初期化にかかる時間を劇的に短縮しています。

esbuildによるthree.jsプロジェクトのバンドル速度と他のバンドルソフトの比較スクリーンショット
esbuildの実測─デフォルトの設定を使用して、three.jsライブラリの本番バンドルx10コピーをゼロから作成するのにかかった時間(画像参照元:Esbuild
  • 効率的な依存関係の処理:Viteは、高性能なGoベースのバンドルツールであるesbuildを活用して、プレーンなJavaScriptや大規模なモジュールを含む依存関係を事前にバンドルします。プリバンドルプロセスの一部として、Viteでは多数の内部モジュールを持つESM依存関係を単一のモジュールにマージすることでパフォーマンスの最適化が行われます。例えば、lodash-esには600以上の内部モジュールがあります。従来の方法で、debounceのような関数をインポートすると、600以上のHTTPリクエストが発生します。Viteの解決策は、lodash-esをあらかじめ一つのモジュールにバンドルし、HTTPリクエストをたった一つに減らすことです。このリクエストの劇的な削減により、開発サーバーのページ読み込み速度が大幅に向上します。

    ESMベースの開発サーバーの表
    ESMベースの開発サーバー(画像参照元:Vite

  • オンデマンドのソースコード読み込み:ViteはネイティブのESモジュールを利用してソースコードを提供し、サーバーの負荷と待ち時間を最小限に抑えています。ブラウザのリクエストに応じてソースコードの変換と配信が行われるため、効率が向上し、待ち時間が短縮できます。

    バンドルベースの開発サーバーの表
    バンドルベースの開発サーバーグラフ(画像参照元:Vite

一方、Webpackはバンドルベースのアプローチを採用しており、ソースコードと依存関係を事前にバンドルします。この性質により、Viteの効率的な初期化と比較すると、Webpackのサーバーセットアップにかかる時間は本質的に長くなります。

しかし、Viteのオンデマンドのアプローチでは、利用者が追加のデータ、CSS、およびアセットを必要とするルートに移動するときに、わずかな遅延が発生する可能性があります。これは、これらのリソースがさらにバンドルステップを要求する場合に特に顕著です。逆にWebpackでは、すべてのサイトデータが利用可能になり、開発サーバー内の新しいページへのスムーズなブラウザナビゲーションが実現します。

HMR(Hot Module Replacement)

ViteはネイティブESMではなくHMRを採用し、バンドル作業の一部をブラウザにオフロードすることで、サーバーの負荷と待ち時間を削減しています。これにより、開発中のリアルタイムのフィードバックに重要な、全ページの再読み込みを伴わない高速更新が保証されます。

WebpackもHMRをサポートし、リアルタイムでの更新を可能にし、開発中のアプリケーションの状態保持が容易になっています。しかし、ネイティブのESモジュールを活用する際の潜在的な制限により、サーバーの負荷や遅延が高くなる可能性があります。

キャッシュパフォーマンス

キャッシュは、ウェブアプリケーションのパフォーマンスを引き上げ、保存したアセットを再利用することで負荷とビルド時間を削減する上で不可欠な要素です。

Viteのキャッシュファイルシステムで管理され、package.json、ロックファイル、vite.config.jsの変更に基づいて依存関係が更新される仕組みです。解決した依存関係のリクエストをキャッシュすることで、ページの再読み込みを最適化します。

Webpackも同様にファイルシステムキャッシュを使用し、watchモードでは変更されたファイルをクリアし、非watchモードでは各コンパイルの前にキャッシュをパージします。

開発サーバーの比較をまとめると、ViteとWebpackは開発サーバーに対して異なるアプローチを採用していることが明確にわかります。

  • Viteはすぐに使える開発サーバーを誇り、設定に伴うオーバーヘッドを最小限に抑えている
  • Webpackは柔軟な挑戦が可能であるものの追加での設定が必要
  • Viteはコールドスタートの速さとHMR(素早いコード変更)の面で優れている
  • Webpackは、あらかじめバンドルされたサイトデータにより、ブラウザのナビゲーション速度で優れたパフォーマンスを発揮
  • どちらもHMRをサポートしているものの、モジュール提供のメカニズムが異なる
  • Viteはローカルとブラウザのキャッシュをシームレスに管理し、Webpackではカスタム設定が必要になる

5. ビルド時間とバンドルサイズ

次に、開発ビルド、開発サーバー中のホットチェンジ、本番ビルドを考慮して、ViteとWebpackのビルド時間とバンドルサイズを比較してみましょう。

テスト環境は以下の通りです。

  • Apple M1チップと8 コアGPUを搭載したMacBook Airでのテスト実行
  • 10個のコンポーネントからなる中規模のVue 3プロジェクトで、state管理にVuex、ルーティングにVue Routerを利用
  • スタイルシート(CSS/SASS)、画像、フォントなどのアセット、および適度な数の依存関係の組み込み

バンドル時間の比較から始めましょう。

Vite [v4.4.11 Webpack [v5.89.0
開発初回ビルド 376ミリ秒 6秒
ホットチェンジ 即時 1.5秒
本番ビルド 2秒 11秒

ビルド時間を大幅に短縮し、バンドル速度ではViteが明らかに勝者であることがわかります。Webpackは細かな設定が可能で堅牢な開発ツールですが、Viteに遅れをとっています。

Vite [v4.4.11] (KB) Webpack [v5.89.0] (KB)
本番バンドルサイズ 866kb 934kb

中程度の依存関係を持つ中規模のVue.jsアプリケーションでの数値です。実際のバンドルサイズは、プロジェクトの複雑さ、依存関係、最適化技術によって異なる可能性があります。

Viteのバンドルサイズが小さいのは、esbuildとネイティブESモジュールによる効率的なプリバンドルによるものです。

Webpackのバンドルサイズは、さまざまな設定オプションやプラグインによって最適化できますが、包括的なバンドルプロセスにより一般的に大きなバンドルが生成されます。

6. ビルドの最適化

最新のウェブ開発におけるビルドプロセスの最適化に関しては、ViteとWebpackはそれぞれ異なるアプローチを採用しており、それぞれに独自の機能と特徴があります。ViteとWebpackの主な違いを探ることで、ビルド最適化について掘り下げてみましょう。

プリロードディレクティブの生成

Viteでは、ビルドしたHTML内のエントリポイントとなるメインファイルやそれと直接関係するインポートに対して、<link rel="modulepreload">ディレクティブが自動で生成されます。これにより、必要に応じてモジュールを効率的にプリロードすることで、読み込み時間が改善できます。

そのため、ページを見てみると次のように表示されます。

<!-- Vite - Module Preloading -->
<link rel="modulepreload" href="/module-a.js">

Webpackはリソースのブラウザヒントをネイティブサポートしていませんでしたが、Webpackv4.6.0では、プリフェッチとプリロードがサポートされました。インポート宣言時にインラインディレクティブを使用することで、Webpackでリソースヒントを出力することができます。例えば以下の通りです。

import(/* webpackPreload: true */ '/module-a.js');

すると以下が出力されます。

<!-- Webpack - Manual Module Preloading -->
<link rel="preload" as="script" href="/module-a.js">

CSSコード分割

ViteはCSSコードの分割を合理的に行っています。非同期チャンクのモジュールで使用される CSSを自動で抽出し、個別のファイルを生成します。これにより、関連する非同期チャンクがロードされるときに、<link>タグを介して必要なCSSだけが読み込まれます。

特筆すべきことに、ViteはCSSが読み込まれた後にのみ非同期チャンクが評価されるようにし、FOUC(Flash of Unstyled Content)を防ぎます。この機能は事前に設定されるため、追加の手順を踏むことなくCSSファイルをインポートし続けることができます。

import './main.css';

Webpackには柔軟性がありますが、CSSコードの分割にはより多くの設定が必要です。mini-css-extract-pluginなど、さまざまなプラグインや設定を使ってCSSコードを分割することができます。

// Webpack - CSSコードの分割
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

コード分割とチャンク読み込み

コード分割は、コードをより小さく管理しやすい断片に分割し、必要なときに必要なものだけを読み込む基本的なテクニックです。これにより、初期読み込み時間が大幅に短縮できリソースの節約につながります。

Viteでのチャンクの扱い方

動的ロードや複数のエントリポイントを伴う場合など、ViteがRollupを使ってコードを自動的に分割するケースがあります。また一方で、output.manualChunksを使い分割するモジュールを明示的に指定することもできます。

Viteの設定済みのコード分割機能とは別に、変数を使った動的インポートもサポートされています。

const module = await import(`./dir/${kinsta}.js`)

Viteでは、公式のsplitVendorChunkPlugin()を使ってベンダーによるコードのチャンクを分割することもできます。

import { splitVendorChunkPlugin } from 'vite'
export default defineConfig({
  plugins: [splitVendorChunkPlugin()],
})

動的インポートやコードの分割により、コードがモジュールやチャンクに構造化されることは一般的であり、これらのモジュールの一部はウェブアプリケーションのさまざまな部分で共有されます。Viteはこのような共通のチャンクを認識し、読み込み処理を最適化します。これについて理解するために、Viteの公式ドキュメントの例を見てみましょう。

2つの非同期チャンクに必要な共通チャンクを示した図(画像参照元:Vite)
2つの非同期チャンクに必要な共通チャンクを示した図(画像参照元:Vite

最適化なしでは、ユーザーがウェブアプリケーションのあるセクション(共通のチャンクCに依存するセクションAと呼ぶことにします)を開くと、ブラウザはセクションAをフェッチすることから始めます。セクションAを解析する間に、共通のチャンクCが必要であることがわかります。このため、追加のネットワークラウンドトリップが必要となり、最初のページ読み込みに遅延が発生する可能性があります。

Entry (Section A) ---> async chunk A ---> common chunk C

一方で、Viteには非同期チャンク読み込み最適化と呼ばれる機能があります。ブラウザによる「必要性」の発見を待つのではなく、積極的にその準備を行います。ユーザーがセクションAを要求すると、ViteはセクションA共通のチャンクCの両方を同時に送信します。このように必要なチャンクを並行して取得することで、読み込みプロセスが大幅にスピードアップします。

Entry (Section A) ---> (async chunk A + common chunk C)

しかし、それだけでは終わりません。共通のチャンクCにはそれ自身の依存関係があり、最適化されていない場合には、さらなるラウンドトリップを引き起こす可能性があります。Viteはこの遅延も見逃しません。そのような依存関係を厳密に分析し、深さに関係なく必要なものをすべてまとめて効率的に読み込みます。これにより、追加のネットワークラウンドトリップが不要になり、応答性の高いウェブアプリケーションが実現します。

Viteの非同期チャンク読み込みでは、必要なすべてのコードチャンクを先回りしてフェッチし並行して提供することで、プロセスを最適化します。余分なネットワークのラウンドトリップを排除することで、素早いウェブ体験を実現します。これは、ブラウザが不必要な遅延なしに必要なリソースをすべて受け取れるように、丁寧に準備した旅程を用意するようなものです。

Webpackでのコード分割

Webpackのコード分割には3つの一般的な手法があります。

  1. エントリーポイント:コードの一部を分割する最も簡単な方法です。設定ファイルに新しいエントリーポイントを定義するだけで、Webpackがそれを別のチャンクとして追加してくれます。
    const path = require('path');
     module.exports = {
      mode: 'development',
      entry: {
        index: './src/index.js',
        another: './src/separate-module.js',
      },
       output: {
        filename: '[name].bundle.js',
         path: path.resolve(__dirname, 'dist'),
       },
     };

    しかし、この方法には注意点があります。モジュールが異なるエントリーのチャンクでインポートされた際には、両方のバンドルに含まれることになり、コードの重複につながります。さらに、必要なときにコアアプリケーションロジックを分割するのにはあまり適していません。

  2. 重複を防ぐ:別のアプローチとして、entryの依存関係やSplitChunksPluginを使ってチャンクを分割することで、重複を減らすことができます。entry依存関係を使ったコード分割の設定例を以下に示します。
    const path = require('path');
    
     module.exports = {
       mode: 'development',
       entry: {
         index: {
           import: './src/index.js',
           dependOn: 'shared',
         },
         another: {
           import: './src/another-module.js',
           dependOn: 'shared',
         },
         shared: 'lodash',
       },
       output: {
         filename: '[name].bundle.js',
         path: path.resolve(__dirname, 'dist'),
       },
      optimization: {
        runtimeChunk: 'single',
      },
     };
  3. 動的インポート:Webpackは動的インポートもサポートしています。オンデマンドでコードを読み込む優れた機能です。動的インポートにはECMAScriptの提案に準拠した構文が使用されます。この方法は柔軟で粒度が細かく、さまざまなコード分割の用途に活用できます。
    const { default: _ } = await import('lodash');

    また、Webpackのマジックコメントを使用して、チャンクの名前を設定し、遅延読み込みを実装、モジュールのエクスポートを指定し、フェッチの優先順位を設定することができます。

    import(
      /* webpackChunkName: "my-chunk-name" */
      /* webpackMode: "lazy" */
      /* webpackExports: ["default", "named"] */
      /* webpackFetchPriority: "high" */
      'module'
    );

ツリーシェイキング

ツリーシェイキングは、ViteとWebpackの両方がJavaScriptバンドルのサイズを削減するために使用している大事な最適化手法です。

ViteはRollupを利用し、ESモジュールの使用を可能にするだけでなく、インポートしたコードを静的に分析します。つまり、Viteではモジュールの使用しない部分を除外し、バンドルサイズを小さくできます。例えば、複数の関数を持つモジュールがあり、そのうちの1つしか使用しない場合、Viteはその関数だけをバンドルに組み込みます。簡単な例を以下に示します。

  • ESモジュールを使わずに、./utils.jsからajaxをインポートするには、ファイル全体をインポートする必要があります。
    const utils = require('./utils');
    const query = 'Kinsta';
    // utilsオブジェクトのajaxメソッドを使う
    utils.ajax(`https://api.example.com?search=${query}`).then(handleResponse);
  • 一方、ESモジュールを使用すると、必要なものだけをインポートできるため、より軽量で高速な、複雑さの少ないライブラリやアプリケーションを用意できます。Viteは明示的なimportexportステートメントを使用するため、未使用のコードを検出する自動圧縮だけに頼ることなく、非常に効果的なツリーシェイキングを実行できます。
    import { ajax } from './utils';
    const query = 'Kinsta';
    // ajax関数を呼び出す
    ajax(`https://api.example.com?search=${query}`).then(handleResponse);

最後に、Viteでは、ツリーシェイキングにRollupの設定済みオプションを使うこともできます。

Webpackもツリーシェイクに対応していますが、仕組みが異なります。Webpackはコードの依存関係を分析し、バンドル時に使用されていない部分を削除します。効果的ではありますが、特に大規模なモジュールやライブラリを扱う場合、Viteのアプローチほど徹底できないかもしれません。

さらにWebpackのドキュメントにあるように、本番環境で別のコードが依存するコードを削除しないように、ファイルを副作用なしとしてマークしておく必要があります。

これを明確にする方法として、sideEffects package.jsonプロパティが使用できます。

{
  "name": "kinsta-app",
  "sideEffects": false
}

ちなみに、Viteのロールアップにも副作用を定義する同様の設定オプションが存在します。

7. 静的アセットの扱い

画像、フォント、その他のファイルなどの静的アセットは、ウェブ開発に不可欠な要素です。ViteとWebpackはアセット処理に対して異なるアプローチをとり、それぞれに長所と最適化効果があります。

Viteのアセット処理

Viteの静的アセット処理は、合理的かつ効率的です。静的アセットをインポートすると、Viteはそれが提供されるときに解決後の公開URLを返します。例えば、画像をインポートした場合は以下のようになります。

import kinstaImage from './kinsta-image.png';

開発中は、imgUrl/img.pngに解決されます。本番では、/assets/img.2d8efhg.pngのようになり、キャッシュとパフォーマンスの最適化が期待できます。

Viteはこのようなインポートを絶対パス(パブリック)でも相対パスでも扱え、プロジェクトに応じて柔軟に利用できます。この動作はCSSのURL参照にも適用され、Viteによるシームレスな処理が期可能です。

さらに、VueのSingle File Component(SFC)でViteを使用する際には、テンプレート内のアセット参照が自動でインポートに変換されるため、開発ワークフローがさらにスムーズになります。

Viteにおけるアセット処理はこれにとどまらず、一般的な画像、メディア、フォントのファイルタイプを検出しアセットとして扱います。これらのアセットはビルドアセットグラフの一部として含まれ、ファイル名はハッシュ化されます。また最適化のためにプラグインで処理することも可能です。

Webpackのアセット処理

一方、Webpackによる静的アセットの処理方法は異なります。Webpackでは、以下のように通常の方法でアセットをインポートします。

import kinstaImage from './kinsta-image.png';

Webpackは、画像を出力ディレクトリに追加し、画像への最終的なURLを提供することで、このインポートを処理します。これにより、アセットを簡単に扱うことができ、url('./my-image.png')を使用してCSS内でも動作します。Webpackのcss-loaderはこれをローカルファイルとして認識し、パスを出力ディレクトリ内の最終的な画像のURLに置き換えます。<img src="./kinsta-image.png" />html-loaderを使用する場合も同様です。

バージョン5から導入されたWebpackのアセットモジュールでは、画像だけでなく様々な種類のアセットを扱うことができます。例えば、Webpackでフォントファイルを扱うように設定できます。

module.exports = {
  module: {
    rules: [
      {
        test: /.(woff|woff2|eot|ttf|otf)$/,
        type: 'asset/resource',
      },
    ],
  },
};

この設定により、@font-faceの宣言を通じてプロジェクトにフォントを組み込むことが可能です。

8. 静的サイトのサポート

静的サイトには、読み込み時間の速さ、セキュリティの向上、ホスティングの簡素化など、数多くのメリットがあります。静的サイトはHTMLCSSJavaScriptで構成され、軽量なユーザーエクスペリエンスと効率的なコンテンツ配信を可能にします。ViteもWebpackも、パフォーマンスの高い静的サイトの構築に活用できますが、それぞれに効率の面での違いがあります。

Viteと静的サイト生成

Viteは(静的サイトと特に相性の良い)合理的な開発とデプロイシステムを活かして、静的サイトのデプロイ専用の手順を確立しています。

Viteはさらに、previewスクリプトを搭載しています。このスクリプトにより、開発者は本番ビルドをローカルで起動し、アプリケーションの最終結果を実際に確認することができます。本番サーバーへのデプロイ前に、本番ビルドをテストしてプレビュー可能です。

ただし、Viteのpreviewスクリプトはローカルでビルドをプレビューするためのものであり、本番サーバーとして使用するものではないことには注意が必要です。つまり、開発者がデプロイ前にアプリケーションをテストするには有用なツールですが、本番サイトのホスティングには向いていません。

{
  "scripts": {
    "preview": "vite preview --port 3000"
  }
}

Viteエコシステムの中でも特に便利なツールの一つであるVitePressも注目に値します。VitePressは、コンテンツに特化したウェブサイトを素早く生成することのできる静的サイトジェネレーター(SSG)です。VitePressがMarkdownベースのソーステキストを受け取りテーマを適用します。出力される静的HTMLページは、Kinstaにて無料でホスティングできます。

Webpackと静的サイト生成

Webpackは静的サイト生成のために特別に設計されているわけではありませんが、様々なプラグインや設定を通して静的サイトを作成できます。Webpackの主な焦点はJavaScriptモジュールのバンドルと最適化にあり、複雑なウェブアプリケーションを構築する際に頼りになる選択肢となっています。

9. サーバサイドレンダリングのサポート

サーバサイドレンダリング(SSR)は、サーバー上でウェブページをレンダリングし、クライアントのブラウザにレンダリング済みHTMLを送信するウェブ開発技術です。SSRサポートの観点から2つのバンドルソフトを比較してみましょう。

  • Vite:Viteはサーバーサイドレンダリングをサポートし、SSRを必要とするアプリケーションに合理的なアプローチをもたらします。Viteを使えば、同じアプリケーションをNode.jsで実行し、HTMLにプリレンダリングし、その後クライアントサイドでハイドレートするフロントエンドフレームワークを統合できます。これにより、ViteはSSR機能を必要とするアプリケーションに最適な選択肢です。
  • Webpack:Webpackもサーバーサイドレンダリングに使用できます。しかし、Webpackを使ったSSRの実装は複雑になりがちで、設定やセットアップをより深く理解する必要があります。Viteの合理的なアプローチと比較して、Webpackを使用してSSRを設定するために多くの時間を投資する必要があるかもしれません。

10. JSONのサポート

ViteとWebpackの両方がJSONファイルのインポートをサポートしています。Viteを除き、JSONの名前付きインポートもツリーシェイキングで利用できるようにサポートされています。

// オブジェクトをインポート
import json from './example.json'
// ルートフィールドを名前付きエクスポートとしてインポート
import { test } from './example.json'

11. Vue.jsとJSXのサポート

著名なJavaScriptフレームワークであるVue.jsは、SFC(Single File Component)構文に従っており、ユーザーインターフェースの構築に利用することができます。対照的に、JSXはJavaScript構文の拡張で、主にReactで使用され、HTMLライクなタグや要素を使用してユーザーインターフェース構造を定義可能です。

ViteにはVueとシームレスに統合する公式プラグインがあります。また、esbuild transpilationにより、JSXファイル(.jsx.tsx)もすぐに扱えます。Vue.jsユーザーであれば、Vue 3用に調整された@vitejs/plugin-vue-jsxプラグインを介して、HMR、グローバルコンポーネントの解決、ディレクティブ、スロットなどの機能が利用できます。

JSXがReactPreactのような他のフレームワークで使用されている場合、Viteではesbuildオプションを介してjsxFactoryjsxFragmentを調整することができます。このレベルの柔軟性は、JSXのカスタマイズが必要なプロジェクトにとって非常に価値あるものでしょう。

// vite.config.js
import { defineConfig } from 'vite'

export default defineConfig({
  esbuild: {
    jsxFactory: 'h',
    jsxFragment: 'Fragment',
  },
})

一方、WebpackはVue.jsやその他の特定のライブラリやフレームワークのネイティブサポートを欠いています。最新のJavaScriptフレームワークのプロジェクトセットアップに際して、関連するローダーと依存関係をインストールする必要があり、手作業での複雑なプロセスが予想されます。

12. TypeScriptのサポート

ViteはTypeScriptをネイティブでサポートしており、.tsファイルをシームレスにプロジェクトに組み込むことができます。esbuildトランスパイラを使用して、開発中に素早くコードを変換可能です。Viteは型チェックではなく、トランスパイルに重点を置いています。IDEやビルドプロセスが型チェックを行うことを想定してのものです。

WebpackはネイティブでTypeScriptをサポートしていないため、開発時にはtypescriptコンパイラとts-loaderを手動でセットアップする必要があります。TypeScriptのオプションを指定するためにtsconfig.jsonの設定が必要になります。一度設定すると、Webpackはts-loaderを用いてTypeScriptコードのコンパイルを行います。設定手順が多くなる一方で、柔軟性と他のWebpack機能との互換性が確保できます。

13. Glob Importのサポート

ViteはGlob Importをサポートしています。この機能は、import.meta.glob functionを介してファイルシステムから複数のモジュールをインポートするのに使用できます。

const modules = import.meta.glob('./kinsta/*.js')

これを利用すると以下が出力されます。

const modules = {
  './kinsta/isCool.js': () => import('./kinsta/isCool.js'),
  './kinsta/isAwesome.js': () => import('./kinsta/isAwesome.js'),
  './kinsta/isFun.js': () => import('./kinsta/isFun.js'),
}

また、ViteはGlob Import Asをサポートしており、import.meta.glob を使ってファイルを文字列としてインポートすることができます。以下にコード例を示します。

const modules = import.meta.glob('./kinsta/*.js', { as: 'raw', eager: true })

これは次のように変換されます。

const modules = {
  './kinsta/rocks.js': 'export default "rocks"n',
  './kinsta/rules.js': 'export default "rules"n',
}

{ as: 'url' }もサポートされています。これを使ってURLとしてアセットを読み込めます。

一方、WebpackがGlob Importを行うには、webpack-import-glob-loaderglob-import-loaderのような追加のプラグインが必要です。例えば、以下のようなコードが対象になります。

import modules from "./test/**/*.js";

これが次のコードに展開されます。

import * as moduleOne from "./foo/1.js";
import * as moduleTwo from "./test/bar/2.js";
import * as moduleThree from "./test/bar/3.js";

var modules = [moduleOne, moduleTwo, moduleThree];

14. Web Workerのサポート

Web Workerは、メインのウェブページをフリーズさせることなく、バックグラウンドで重いタスクを実行するために不可欠です。ViteとWebpackがこれをどのように扱うかを見てみましょう。

ViteではWeb Workerを簡単に使うことができます。別のWeb Workerファイルを作成し、メインのコードにインポートしてWeb Workerと通信します。Viteには、メインコードにWorkerをインポートする2つの方法があります。

  1. new Worker()と新しいコンストラクタSharedWorker()
    const worker = new Worker(new URL('./worker.js', import.meta.url));
    
    // または
    
    const worker = new SharedWorker(new URL('./worker.js', import.meta.url));
  2. ?workerまたは?sharedworkerを付加した直接インポート
    import MyWorker from './worker?worker';
    
    const worker = new MyWorker();
    
    myWorker.postMessage('Hello from the main thread!');

    WebpackもWeb Workerをサポートしており、Webpack 5からはWorkerを利用するためにローダーを使用する必要がなくなりました。

    Const worker = new Worker(new URL('./worker.js', import.meta.url));

15. ライブラリ開発能力

ライブラリとフレームワークは、ウェブアプリケーションの開発を加速させる優れものです。ViteとWebpackの両方が堅牢なソリューションを備えています。

Viteは、特化したライブラリモードにより、ライブラリ開発を次のレベルに引き上げ、ブラウザに特化したライブラリの作成プロセスを簡素化します。さらにViteには、VueやReactのような、ライブラリバンドルに含めたくない特定の依存関係を外部化する柔軟性もあります。

一方Webpackは、ライブラリ作成者にも対応する汎用的なバンドルです。JavaScriptライブラリの作成にWebpackを使用している場合、ライブラリバンドルの要件に合わせてWebpackを設定できます。ライブラリのコードがどのようにパッケージ化されるべきかを定義できるので、幅広いライブラリを構築するのに適した選択肢となります。

16. ブラウザの互換性

Viteは最新のブラウザを優先し、Chrome >=87、Firefox >=78、Safari >=14、Edge >=88など、ネイティブのESモジュールをサポートしたブラウザをターゲットにしています。カスタムターゲットはbuild.targetで設定することもできます。レガシーブラウザは@vitejs/plugin-legacyを通してサポートされています。

WebpackはES5準拠のすべてのブラウザをサポート(IE8以下を除く)しています。古いブラウザに対応するため、import()require.ensure()などの機能にはポリフィルが必要です。

ブラウザの互換性という点では、どちらも優れていますが、プロジェクトのターゲットとするユーザーとそのブラウザの機能によって選択する必要があります。

まとめ

Viteは、そのネイティブESモジュールアプローチを武器に、素早い更新と広範なカスタマイズ、高速な開発を後押しします。逆に、Webpackは、その堅牢性と幅広いエコシステムで知られており、本番ビルドで強さを発揮しますが、使いこなすのは簡単ではありません。

ViteとWebpackの比較の際には、実際のプロジェクトの要件と設定の容易さ(そして柔軟性)を考慮することが欠かせません。どちらにも強みがあるため、プロジェクトで求められる条件に基づき選択しましょう。

最後に、Viteを使用したプロジェクトを支えるサーバーをお探しであれば、堅牢かつ効率化に寄与するソリューションであるKinstaの静的サイトホスティングをおすすめします。

以下のコメント欄で、お好みのバンドルと選択の決め手となった主な理由をお聞かせください。

Mostafa Said

Laravel、Inertia、JavaScriptフレームワークを得意とするフルスタック開発者。コードを書く以外の時間は、解説記事を執筆して知識を共有したり、ハッカソンに参加したり(何度か優勝経験あり)、技術の素晴らしさを伝えるべく他の人の学習をサポートしたりしている。