【テクニカル・上級編】Viteの『Rollup Options』でバンドル構成を自在に操る!外部化(External)と出力チャンクの高度な制御術 – ビルド・パッケージ管理ツール生産性向上バイブル

はじめに:Viteの背後に潜むRollupエンジンを飼いならす者だけが、Webパフォーマンスの神に愛される

多くのフロントエンドエンジニアは、Viteを「爆速のHMR(Hot Module Replacement)をもたらす魔法のツール」としてありがたがり、`vite.config.ts` のデフォルト設定に安住している。
しかし、プロダクション環境へ向けた `vite build` を実行した瞬間、その裏で何が起きているかを正確に理解しているアーキテクトはどれほどいるだろうか?

Viteのプロダクションビルドは、実のところ自前でバンドラーを書いているわけではない。その堅牢なコアには、業界標準である Rollup が鎮座している。
Viteの容易な設定の背後には、Rollupという巨大なエンジンが動いており、これを直に制御するのが `build.rollupOptions` である。

この `rollupOptions` を制する者こそが、Webフロントエンドのバンドルサイズ、キャッシュ効率、ひいてはLCP(Largest Contentful Paint)やFIDといったCore Web Vitalsの数値までも支配する。
本稿では、ありふれたチュートリアルでは決して語られない、Rollupの内部AST(抽象構文木)とチャンク生成アルゴリズムの挙動に踏み込み、「外部化(External)によるアセットの極限削減」 と 「`manualChunks` によるキャッシュ効率の極致」 を実現する実践的アーキテクチャを解説する。

—

1. 内部アーキテクチャ理解:Rollupのチャンク生成アルゴリズムとメモリ管理の罠

なぜ、デフォルトのバンドル設定では大規模アプリケーションにおいてパフォーマンスが破綻するのか?
その答えは、Rollupのチャンク分割戦略とメモリ消費モデルにある。

AST解析からモジュールグラフ構築までのライフサイクル

Rollupは、エントリーポイントから開始し、`import` 文を辿って依存関係のモジュールグラフをメモリ上に構築する。
各モジュールはパースされてASTとなり、スコープの巻き上げ(Hoisting)やツリーシェイキング(Dead Code Elimination)が適用される。

デフォルト状態のRollupは、「可能な限りモジュールを統合してファイル数を減らし、HTTPリクエスト数を抑える」というヒューリスティックに基づいて動作する。
しかし、この挙動はSPA(Single Page Application)において致命的なアンチパターンを生む。数メガバイトに及ぶ単一の `vendor.js` が生成され、わずか数行のビジネスロジックを修正しただけでも、ユーザーは全ベンダーライブラリを含んだ巨大なファイルを再ダウンロードさせられることになるのだ。

メモリ消費の最適化ハック(Node.jsヒープの限界突破)

大規模なモノリス型フロントエンドをVite/Rollupでビルドする際、避けて通れないのが `JavaScript heap out of memory` エラーである。
Rollupはすべてのモジュールグラフをメモリ上に展開するため、デフォルトのNode.jsヒープサイズ(通常は1.4GB〜4GB程度)では即座にクラッシュする。

これを回避するため、CI/CDパイプラインやローカル環境では、必ずNode.jsのメモリ上限を引き上げる必要がある。
さらに、Rollupの内部ワーカープロセスを最適化し、ガベージコレクションのタイミングを制御する環境変数チューニングを組み合わせるのが、プロフェッショナルのアプローチだ。

Node.jsのヒープメモリを8GBに拡張し、大規模モジュールグラフのメモリ破綻を防ぐ
NODE_OPTIONS=”–max-old-space-size=8192″ vite build

—

2. 外部化(External)の極意:CDNとESMシャードによるゼロ・バンドル戦略

巨大なサードパーティライブラリ(React, Lodash, Moment.js, あるいは社内共通UIコンポーネント群など)を自前のバンドルに含めるのは、ストレージとネットワーク帯域の無駄遣いだ。
これらをバンドルから切り離し、ブラウザのネイティブESM(ES Modules)キャッシュやCDNへオフロードする手法が External(外部化) である。

`build.rollupOptions.external` と `output.globals` の正確な連携

Viteで外部化を行う場合、単にRollupに「このモジュールは無視しろ」と伝えるだけでは不十分だ。
ブラウザがランタイムでそのモジュールをどこから見つければいいのか、グローバル変数名やCDNのURLマッピングを正確に指示する必要がある。

以下は、ReactおよびReactDOMをバンドルから完全に排除し、Skypackやesm.shなどのCDN、あるいは社内プライベートCDNから非同期ロードさせるための高度な `vite.config.ts` の実装例である。

import { defineConfig } from ‘vite’
import react from ‘@vitejs/plugin-react’
import { visualizer } from ‘rollup-plugin-visualizer’

export default defineConfig({
plugins: [
react(),
// ビルド成果物のサイズと依存関係を視覚的に監査するプラグイン
visualizer({
filename: ‘stats.html’,
open: false,
gzipSize: true,
brotliSize: true,
})
],
build: {
rollupOptions: {
// 1. バンドルに含めず、外部依存として扱うモジュールの指定
external: [
‘react’,
‘react-dom’,
‘react-router-dom’
],
output: {
// 2. UMD/IIFE形式で出力する場合のグローバル変数マッピング
globals: {
react: ‘React’,
‘react-dom’: ‘ReactDOM’,
‘react-router-dom’: ‘ReactRouterDOM’
}
}
},
// アセットのインライン化しきい値を設定(4KB未満はBase64化してリクエスト数を削減)
assetsInlineLimit: 4096,
}
})

HTMLテンプレート側でのインジェクション制御

外部化したライブラリは、HTMLのエントリーポイント(`index.html`)またはランタイムのインジェクターで明示的に読み込む必要がある。
インポートマップ(Import Maps)を使用することで、CDN上のESMモジュールを綺麗に解決できる。





Enterprise Architecture Vite App





このアプローチにより、自社製アプリケーションコードのバンドルサイズを劇的に縮小でき、CDNのエッジキャッシュによる爆発的なロード高速化の恩恵を受けられる。

—

3. `output.manualChunks` による高度なコード分割戦略:キャッシュヒット率の極限追求

Vite/Rollupにおける最大のアーキテクチャ的課題は、「どのようにチャンクを切り分けるか(Chunk Splitting Strategy)」 である。
デフォルト設定では、動的インポート(`import()`)されたコードや、`node_modules` 内のモジュールが適当にまとめられるが、これでは不十分だ。

「頻繁に変更されるビジネスロジック」と「滅多に変更されないサードパーティ製ライブラリ」を完全に分離し、ブラウザの長期キャッシュ(Cache-Control: immutable)を最大限に活かすための `manualChunks` 設計を構築する。

関数型 `manualChunks` による知的なルーティング

`manualChunks` にはオブジェクトだけでなく、関数を渡すことができる。
この関数は、モジュールID(ファイルパス)を受け取り、それが属すべきチャンク名を文字列で返す。これを利用して、`node_modules` 内のパッケージをドメインごとに完璧に分離する。

以下に、実戦投入レベルの極限まで洗練された `manualChunks` 設計を示す。

import { defineConfig } from ‘vite’
import react from ‘@vitejs/plugin-react’

export default defineConfig({
plugins: [react()],
build: {
sourcemap: process.env.NODE_ENV !== ‘production’, // 本番以外ではソースマップを有効化
rollupOptions: {
output: {
// チャンク分割のアルゴリズムを関数で完全制御
manualChunks(id) {
// node_modulesに含まれる外部パッケージの制御
if (id.includes(‘node_modules’)) {
// Reactエコシステムを単一のチャンクに固めて依存関係の整合性を担保
if (
id.includes(‘react’) ||
id.includes(‘react-dom’) ||
id.includes(‘scheduler’)
) {
return ‘vendor-react’
}

// UIコンポーネントライブラリ(例: @mui や antd)を分離
if (id.includes(‘@mui’) || id.includes(‘@emotion’)) {
return ‘vendor-ui’
}

// ユーティリティ系(lodash, date-fnsなど)を分離
if (id.includes(‘lodash’) || id.includes(‘date-fns’)) {
return ‘vendor-utils’
}

// その他のサードパーティライブラリはすべて一網打尽に vendor-libs へ
return ‘vendor-libs’
}

// 自社製コードのドメイン駆動型分割(例: 管理画面と一般画面の分離)
if (id.includes(‘/src/features/admin/’)) {
return ‘feature-admin’
}

if (id.includes(‘/src/features/portal/’)) {
return ‘feature-portal’
}
},

// 生成されるアセットのファイル名ハッシュ付与ルール(キャッシュバスティングの確実化)
entryFileNames: ‘assets/js/[name]-[hash].js’,
chunkFileNames: ‘assets/js/[name]-[hash].js’,
assetFileNames: ‘assets/[ext]/[name]-[hash].[ext]’
}
}
}
})

この戦略がもたらす実務上の利益

1. キャッシュバスティングの局所化:
ビジネスロジック(`feature-admin` や `src/` のコード)をどれだけ書き換えても、`vendor-react` や `vendor-ui` のハッシュ値は一切変化しない。ユーザーは変更のあった数キロバイトのファイルだけを再取得すればよく、通信量が劇的に削減される。
2. サーブ時の並列ダウンロード最適化:
HTTP/2以降の multiplexing(多重化)において、巨大な1ファイルよりも、適度に分割された複数のチャンクファイルを並列ダウンロードする方が、ブラウザのパーサースレッドを効率的に活用できる。

—

4. CI/CDパイプライン統合 & 完全自動構成の要塞化

ここまで高度なRollup設定を施した場合、ローカル開発環境とCI/CDパイプライン(GitHub Actions, GitLab CI, Argo CDなど)の間でビルド挙動の差異や、メモリ不足エラー、キャッシュの不整合が発生してはならない。

以下に、Docker環境およびGitHub Actionsにおける、Viteビルド自動化の完全版構成コードを示す。

Dockerfile(マルチステージビルドによる軽量化とメモリ制御)

ビルド時とランタイム(Nginxなどによる静的配信)を分離し、さらにNode.jsのメモリ制約をビルド引数で安全に処理するマルチステージDockerfile。

— Stage 1: Build Environment —
FROM node:20-alpine AS builder

ワークディレクトリの設定
WORKDIR /app

パッケージマネージャーのロックファイルをコピー
COPY package.json package-lock.json ./

依存関係の確実なインストール(CI環境向けクリーンインストール)
RUN npm ci

ソースコードの全転送
COPY . .

ビルド実行時のNode.jsメモリ上限を拡張(8GB)
ENV NODE_OPTIONS=”–max-old-space-size=8192″

プロダクションビルドの実行(Rollup Optionsがフル稼働)
RUN npm run build

— Stage 2: Runtime Environment (Nginx) —
FROM nginx:alpine-slim

セキュリティ強化: デフォルトのHTMLを削除
RUN rm -rf /usr/share/nginx/html/

Builderステージから最適化された成果物のみをコピー
COPY –from=builder /app/dist /usr/share/nginx/html

Nginxカスタム設定のインポート(必要に応じて)
COPY nginx.conf /etc/nginx/conf.d/default.conf

ポート開放
EXPOSE 80

ヘルスチェックの設定
HEALTHCHECK –interval=30s –timeout=3s \
CMD wget –quiet –tries=1 –spider http://localhost/ || exit 1

コンテナ起動時にNginxをフォアグラウンドで実行
CMD [“nginx”, “-g”, “daemon off;”]

GitHub Actions ワークフロー(依存関係キャッシュとビルド監査)

CIパイプラインにおいて、npmキャッシュを効率的にヒットさせつつ、ビルド成果物のサイズ異常を検知するステップを組み込む。

name: Production Vite Build & Deploy Pipeline

on:
push:
branches:

  • main

jobs:
build-and-verify:
name: High-Performance Vite Build
runs-on: ubuntu-latest

# ジョブ全体のタイムアウト設定(無限ループやハングアップの防止)
timeout-minutes: 15

steps:

  • name: Checkout Repository

uses: actions/checkout@v4

  • name: Setup Node.js Environment

uses: actions/setup-node@v4
with:
node-version: ’20’
cache: ‘npm’

  • name: Install Dependencies

run: npm ci

  • name: Execute Production Build with Extended Memory

env:
NODE_OPTIONS: “–max-old-space-size=8192”
run: npm run build

  • name: Audit Bundle Output Size

run: |
echo “=== チャンクサイズ一覧 ===”
ls -lh dist/assets/js/

# 500KBを超えるチャンクが存在する場合、警告またはビルド失敗とするガードレール
find dist/assets/js/ -name “.js” -size +500k | awk ‘{print “WARNING: Large chunk detected -> ” $9 ” (” $5 “)”}’

—

5. トラブルシューティング:現場で遭遇する「Rollup地獄」の処方箋

最後に、アーキテクトが実務で必ず直面するRollup起因のトラブルと、その低レイヤな解決策を記す。

トラブル1:循環参照(Circular Dependency)警告の嵐

`manualChunks` を導入した途端、コンソールに `Circular dependency` の警告が溢れ返ることがある。これはモジュールAがBを呼び、BがAを呼び返している状態で、コード分割の境界線をまたぐ際に発生するとランタイムエラー(初期化順序の崩壊)を引き起こす。

処方箋:
Rollupプラグイン `rollup-plugin-circular-dependency` を導入するか、依存関係の方向性を単方向(Unidirectional)にリファクタリングする。基本原則として、`vendor` チャンクと `feature` チャンクの間で循環参照が発生しないよう、共通の純粋関数や型定義は独立した小さなモジュールに切り出すべきである。

トラブル2:サードパーティ製ライブラリのCommonJS/ESM混在による `Cannot read properties of undefined (reading ‘call’)`

一部の古いライブラリ(CommonJS形式)をViteでバンドルすると、RollupのCJS変換プラグイン `@rollup/plugin-commonjs` の挙動により、名前付きエクスポート(Named Exports)がうまく解決できずにクラッシュすることがある。

処方箋:
`vite.config.ts` の `commonjsOptions` を明示的に設定し、特定のモジュールを強制的にESM化する。

export default defineConfig({
build: {
rollupOptions: {
// …
}
},
optimizeDeps: {
// 事前バンドル(Pre-bundling)の段階で強制的に変換対象にする
include: [‘some-legacy-commonjs-library’]
}
})

—

おわりに:インフラとコードの境界線を消し去るエンジニアリングへ

Viteの `rollupOptions` を自在に操る技術は、単なる「設定ファイルの書き方」の範疇にとどまらない。
それは、ブラウザのネットワークスタック、JavaScriptエンジンのメモリ管理、そしてCI/CDパイプラインの自動化ロジックまでを一本の線で貫く、フルスタック・エンジニアリングの極みである。

デフォルトの便利さに甘んじることなく、バンドルの内部構造をその手で掌握し尽くしたとき、あなたのアプリケーションは競合他社が追随できない圧倒的なパフォーマンスとスケーラビリティを手に入れるだろう。

さあ、今すぐ `vite.config.ts` を開き、独自のチャンク戦略をコードに刻み込め。

タイトルとURLをコピーしました