はじめに
「バンドルサイズが思ったより大きい」「使っていないコードまで出力に混ざっている」——JavaScriptでライブラリやアプリを配布するとき、こうした無駄に悩んだ経験はないでしょうか。
Rollupは、この問題にいち早く取り組んだJavaScriptのモジュールバンドラーです。ES modulesの静的構造を活かして未使用コードを除去する「tree-shaking」という考え方を広めたツールとして知られており、現在でも多くのライブラリのビルドに使われ続けています。実は、私たちが普段使っているVite自体も、本番ビルドの内部ではRollupを利用しています。
この記事では、Rollupの特徴からインストール方法、設定ファイルの書き方、そしてライブラリ配布やVite連携といった実践的な使い方までを順を追って紹介します。
Rollupとは
Rollupは「Compile small pieces of code into something larger and more complex」を掲げるJavaScriptのモジュールバンドラーです。ES modules形式で書かれたコードを、CommonJSやUMD、IIFEといった複数の形式にコンパイルできます。
webpackがアプリケーション全体のビルドを得意とするのに対し、Rollupはライブラリのビルドに強みを持ちます。出力されるコードが人間に読みやすい形を保ちやすく、不要なランタイムコードが混入しにくいことが評価され、React・Vue・Sveltekit・D3など数多くのOSSプロジェクトのビルドツールとして採用されてきました。
主な特徴
- Tree-shaking - ES modulesの静的なimport/export構造を解析し、実際に使われているコードだけを出力に残します。この概念をJavaScriptバンドラーに持ち込んだ先駆者的存在です。
- 複数の出力フォーマット - ES modules、CommonJS、UMD、SystemJS、IIFEなど、用途に応じたフォーマットを同じ入力コードから生成できます。
- プラグインAPI -
resolveIdやtransformといったフックを備えたシンプルなプラグインインターフェースにより、Babel連携やTypeScriptコンパイル、CSS処理などを柔軟に組み込めます。 - コード分割 - 複数エントリーポイントや動的
import()をもとに、共通コードを自動的に別チャンクへ切り出せます。 - Viteの内部エンジン - Viteは開発時にesbuildを使いつつ、本番ビルドではRollupのプラグインエコシステムをそのまま活用しています。
インストール
npm・yarn・pnpm・bunのいずれからでもインストールできます。
npm install rollup --save-dev
yarn add rollup --dev
pnpm add -D rollup
bun add -d rollup
グローバルにインストールせず、npx rollupやpackage.jsonのscriptsから呼び出すのが一般的です。
基本的な使い方
Rollupは設定ファイルrollup.config.jsにinput(エントリーポイント)とoutput(出力先とフォーマット)を書き、CLIから実行するのが基本の流れです。
// rollup.config.js
export default {
input: 'src/index.js',
output: {
file: 'dist/bundle.js',
format: 'esm',
},
};
このファイルを用意したら、-cオプション付きでCLIを実行します。
npx rollup -c
inputには複数ファイルをオブジェクトで渡すこともでき、outputを配列にすれば1つの入力から複数フォーマットを同時に出力できます。
// rollup.config.js
export default {
input: 'src/index.js',
output: [
{ file: 'dist/bundle.cjs.js', format: 'cjs' },
{ file: 'dist/bundle.esm.js', format: 'esm' },
],
};
実践的なユースケース
ライブラリを配布用に複数フォーマットで出力する
npmでライブラリを公開する場合、CommonJSしか読めない古い環境向けのcjs、モダンなバンドラー向けのesm、CDNから直接<script>で読み込めるumdの3種類を同時に用意しておくと、利用者の環境を選びません。@rollup/plugin-node-resolveでnode_modules内の依存を解決し、@rollup/plugin-terserで圧縮するのが定番の構成です。
// rollup.config.js
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import terser from '@rollup/plugin-terser';
export default {
input: 'src/index.js',
output: [
{ file: 'dist/my-lib.cjs.js', format: 'cjs' },
{ file: 'dist/my-lib.esm.js', format: 'esm' },
{ file: 'dist/my-lib.umd.js', format: 'umd', name: 'MyLib' },
],
plugins: [nodeResolve(), commonjs(), terser()],
};
umd形式のみ、グローバル変数として公開する名前をnameオプションで指定する必要がある点に注意してください。
動的importでコード分割する
初回に必要なコードだけを読み込み、重い処理は使われるタイミングで遅延読み込みしたいケースでは、import()を使った動的インポートが有効です。Rollupは静的なimportだけでなくimport()も解析し、output.dirを指定しておけば共通コードを自動的に別チャンクへ切り出します。
// rollup.config.js
export default {
input: 'src/main.js',
output: {
dir: 'dist',
format: 'es',
},
};
// src/main.js
document.querySelector('#run').addEventListener('click', async () => {
const { heavyTask } = await import('./heavy.js');
heavyTask();
});
heavy.jsは#runボタンがクリックされるまでネットワークに読み込まれず、初期表示のバンドルサイズを小さく保てます。
ViteのrollupOptionsでビルドを細かく制御する
Viteは本番ビルドで内部的にRollupを呼び出しており、vite.config.jsのbuild.rollupOptionsにはRollupのinput・outputオプションをそのまま渡せます。複数のHTMLエントリーポイントを持つマルチページアプリや、特定の依存関係を専用チャンクにまとめたい場合に使います。
// vite.config.js
export default {
build: {
rollupOptions: {
input: {
main: 'index.html',
admin: 'admin.html',
},
output: {
manualChunks: {
vendor: ['lodash', 'dayjs'],
},
},
},
},
};
manualChunksで指定したlodashやdayjsは、どのページからも共通のvendorチャンクとして出力されるため、複数ページをまたいだキャッシュ効率が上がります。VueやReactのプロジェクトでVite標準のビルドに手を加えたくなったときは、まずRollupのオプションリファレンスを見に行くとよいでしょう。
まとめ
Rollupは、ES modulesの静的構造を活かしたtree-shakingをいち早く実用化し、複数フォーマット出力・軽量なプラグインAPI・コード分割を備えたモジュールバンドラーです。単体のビルドツールとしてライブラリを配布する場面はもちろん、Viteのビルドエンジンとしても動き続けており、rollupOptionsを通じて日々の開発でも間接的に触れている方が多いはずです。
自作ライブラリの配布形式を整えたいときや、Viteのビルド出力を細かく制御したいときには、ぜひRollupの設定に踏み込んでみてください。