昨年の途中からちらほら耳にするものの、まだ「なにそれ美味しいの?」なRollupですが、馴染むと手放せなくなる感じ。どんなものか、使い方から、プラグインのつくりかたまで、概観してみたいと思います。
Rollupって何?
複数ファイルに書かれたJavaScriptを、モジュールなどを読み込みつつ、ひとつのバンドルにしてくれるツール。WebPackとかBrowserifyみたいなやつです。依存モジュールの解決や、AltJSのプリコンパイルしたり、など。大きな特徴として、次の点がよく挙げられます。
- 生成ファイルが小さい
- ES6(ES2015)ネイティブ
ドキュメント類はまだ最低限という感じですが、WebPackかBrowserifyにさわったことがあれば、そんなに迷うことはないかも。ただ、トップページに行っても正直よくわからないので、今のところWikiが一番の情報源です。公式の情報で見るべきところを、以下ピックアップしておきます。
- 本家サイト ブラウザ上で動作を試せるけど、それだけw
- 公式ガイド ざっと眺めておくとよし。まだ書き途中。
-
Wiki 現状一番情報が豊富。
- コマンドライン
- JavaScript API
- プラグイン - 作成ガイドライン
- ほか。
- npmでプラグインを検索
ES6ネイティブ
BrowserifyはCommonJSで書かれたブラウザ向けコードを、Nodeの作法でコンパイルしてブラウザーで使えるようにするというものでした。WebPackはさらにCSS、画像などJavaScriptに限らずさまざまなファイルをまとめて変換してくれますが、いずれもES5を前提にしている点は共通です。たとえばCoffeeであれば、
-
CoffeeScript
-->ES5
-->CommonJS
/AMD
という変換を経ます。一方でRollupは、一旦なんでもかんでもES6にして、あとは必要に応じてbabelでES5に変換します。内部処理は、常にES6を前提に行われます。
-
CoffeeScript
-->ES6
--(babel)-->ES5
-->CommonJS
/AMD
実際のところ、npmに公開されているCommonJSのモジュールはそのままだと読み込むことすらできません...!! rollup-plugin-commonjs
プラグインで、ES6に変換してから結合してやる必要があります。
-
CommonJSモジュール(ES5)
--(プラグイン)-->ES6
--(babel)-->ES5
-->CommonJS
/AMD
Tree Shaking
ES5
からES6
へ、さらにES5
に戻すなんて「無駄!」と思うかもしれませんが、ES6中心にすることにはメリットがあります。その一つがコードの「静的解析」です。両者でのコードのインポートを考えてみてください。
- CommonJSスタイル:
const something = require('something')
- ES6スタイル:
import { foo } from 'something'
ここだけだと似てはいますが、CommonJSはあくまでもJavaScript(ES5)なので、コード内のどこにでも書くことができてしまいます。またrequire()
に渡す内容は変数でも良いため、どんなライブラリが使われるかも事前には決定しません。これは、コードの最適化の観点からは、辛いこと尽くし...。実際には膨大なライブラリ群のごく一部しか利用していなくても、全体を読み込む必要が生じます。
その点、ES6のスタイルは、コンパイル(あるいは読み込み)の時点で、依存ライブラリのどの部分が実際に使われるかが「決定」しています。つまり、不要部分はコンパイルの時点で除去してしまっても構わないのです。また、お互いの関数がグローバル汚染していないのであれば、無駄にクロージャで囲む必要もありません。Tree Shakingは「静的解析」をコードにかけることで、バンドルサイズを非常に小さく抑えてくれます。
補足: 近々リリースされる、WebPack2にも同様の機能が実装されるようです。
使い方
gulp他から使うこともできますが、ここではコマンドラインから利用する方法と、JavaScript APIから実行する方法を紹介します。
インストール
何はともあれインストール。ひとまずローカルに。
$ npm i -D rollup
本体のほか、プラグインもまとめてインストールしましょう。定番はこのあたり。
rollup-plugin-node-resolve
rollup-plugin-commonjs
rollup-plugin-json
とBabel関連。後者はプラグインではないですが、バベるときに必要なので一緒に。
rollup-plugin-babel
babel-preset-es2015-rollup
$ npm i -D rollup-plugin-node-resolve rollup-plugin-commonjs rollup-plugin-json rollup-plugin-babel babel-preset-es2015-rollup
.babelrc
を置いてrollup
用のプリセットを指定します。
{
"presets": [ "es2015-rollup" ]
}
これで下準備は完了。(1)コンフィグファイル編か、(2)JavaScript API編かお好きな方をどぞ。
(1) コンフィグファイル編
たぶん、これが一番簡単な方法で、8割がたのケースではこれでOK。
こんな感じで書きます。ES6です。
// rollup.config.js
import nodeResolve from 'rollup-plugin-node-resolve'
import commonjs from 'rollup-plugin-commonjs'
import babel from 'rollup-plugin-babel'
export default {
entry: 'src/main.js',
dest: 'dist/bundle.js',
plugins: [
nodeResolve({ jsnext: true }), // npmモジュールを`node_modules`から読み込む
commonjs(), // CommonJSモジュールをES6に変換
babel() // ES5に変換
]
}
-
entry
: エントリーポイント -
dest
: ファイルの出力先 -
plugins
: 利用するプラグインを指定
プラグインのオプションは、オブジェクトとして渡すことができます。npmプラグインには次のオプションを指定可能で、読み込むモジュール内のpackage.json
のどこを参照するかが変わります。
-
jsnext
:jsnext:main
を参照 -
main
:main
を参照 -
browser
:browser
を参照
package.jsonのscripts
セクションに、実行コマンド$ rollup -c
を書いておきます。
{
"scripts": {
"build": "rollup -c"
}
}
実行は、次のコマンドで。これで、コンパイル済みのコードが出力されるはずです。
$ npm run build
メモ1: rollupをグローバルにインストールした場合は、もちろん$ rollup -c
で直接実行可。
メモ2: rollup.config.js
以外のファイル名にした場合は、$ rollup -c <ファイル名>
で指定可。
(2) JavaScript API編
細かくコントロールしたい場合はこちらがお勧め。ライブラリを作る場合、ES6やAMDなど複数の形式に対応したファイルを出力する必要があったり、いろいろ調整が必要だったりするのでこの方法を使っています。
詳細はコメントを参照のこと。ES6で書いていますが、素のNodeだとimport
とかは使えないので注意。
// build.js
const
rollup = require('rollup'),
npm = require('rollup-plugin-npm'),
commonjs = require('rollup-plugin-commonjs'),
babel = require('rollup-plugin-babel'),
name = 'awesomeapp'
rollup
.rollup({
// rollup.config.jsと同じようにentryやpluginを指定
entry: 'dist/main.js',
plugins: [npm({ jsnext: true }), commonjs(), babel()]
})
.then(bundle => {
// ES6形式で出力
bundle.write({ format: 'es6', dest: `dist/${ name }.es6.js` })
// AMD形式で出力
bundle.write({ format: 'amd', dest: `dist/${ name }.amd.js` })
// CommonJSで出力
bundle.write({ format: 'cjs', dest: `dist/${ name }.cjs.js` })
// グローバル変数を使う形式で出力
bundle.write({
format: 'iife',
dest: `dist/${ name }.js`,
moduleName: name // iifeで出力する場合は、moduleNameの指定が必須
})
})
.catch(error => {
console.error(error)
})
write()
の代わりにgenerate()
を使うと、文字列として返します。
-
bundle.write()
: 上記のようにファイルに出力 -
bundle.generate()
: ファイルに出力せず、文字列を返す
あとは、package.jsonのscripts
セクションに、実行コマンドを書いておきます。
{
"scripts": {
"build": "node build.js"
}
}
実行は、次のコマンドで。
$ npm run build
若干の補足を
説明しきれていない項目を、2つほど。その他については、Wiki参照。
jsnext:main
最近、package.json
にときどき見かけるアレです。main
に指定されたコードは基本require()
形式で書かれていることが想定されるので、ES6のまま書く場合は不適当となってしまいます。そこで、ES6で書かれていることを明示したい場合は、jsnext:main
を使うことが提唱されています。
external
例えば、import riot from 'riot'
がコードに含まれていても、riot
モジュールだけ展開せずにおきたい場合があります。その場合は、external
オプションで除外モジュールを指定します。例:
{
entry: 'src/main.js',
external: ['riot'],
plugins: [...]
}
まとめ
次期HTML仕様に、2016/1/21付で待望の「モジュール」が登場しました! 今後はブラウザがネイティブでモジュールローダを備えるようになると期待します。とは言え、まだしばらくは時間がかかるでしょう。
つまり、それまではなんらかの方法でコードをバンドルする必要があります。従来、有力なのはBrowserifyとWebPackでしたが、この数ヶ月でRollupもその一角を担い始めました。筆者自身は、gulp + Browserifyで長らくやってきましたが、ES6を扱うのがしっくりこないんですよね...。WebPackは"Do One Thing and Do It Well"じゃない感じが苦手。もし、そんな同じような悩みを持っていたら、Rollupは気にいるかもしれません :-)
- Browserify: APIに少々クセあり・コンパイルに時間がかかる
- WebPack: ちょっと色々できすぎるのが難・Code splitは魅力的
- Rollup: シンプルでちょうどいい感じ
Appendix:
プラグイン
RollupのWikiにプラグインが掲載されています。2016年1月時点のものを転載してみます。下記のほか、npmにはすでに30種類ほどが公開されているようです。
- babel – バベる
- coffee-script – CoffeeScriptを変換
- commonjs – CommonJSをES6モジュールに
- inject – 依存性を検知してインジェクト
- json – JSONの取り込み
- memory - ファイルからではなく、文字列でソースを渡す
- multi-entry – 複数のエントリーポイントを扱う
-
npm – npmで
node_modules
にインストールされたものを扱えるようにする - pegjs - PEG.jsの文法定義ファイルを変換
- postcss - PostCSS変換とheadへの挿入
- replace – 文字列置換
- riot - Riot.jsのタグファイルを変換 (拙作)
- string – テキストファイルを文字列としてインポート
- uglify - Uglifyでバンドル結果をミニファイ
プラグインの作り方
もし、ソースを問答無用で全部.toUpperCase()
するプラグインを作るとすると、こんな感じです(使い道ないけど)。options
を受け取って、transform()
を含むオブジェクトを返すという作法になっているのが見て取れると思います。
import { createFilter } from 'rollup-pluginutils'
export default function uperCase(options = {}) {
// options.include(=含める), options.exclude(=除外する)は対応が必須
const filter = createFilter(options.include, options.exclude)
return {
transform (code, id) {
if (!filter(id)) return null // ファイルパスでフィルターする
return code.toUpperCase() // 変換したコードを返す
}
}
}
.toUpperCase()
する代わりに、お好きなAltJSコンパイラに通せば、あれやこれやできそうですね。このまま、自分のrollup.config.js
などから呼び出せば、プライベートなプラグインとして使用可能です。
プラグインの公開
プラグインを公開する場合は、「モジュール名をrollup-plugin-
で始める」「rollup-plugin
キーワードをつける」などのガイドラインが決まっています。目を通しておきましょう。