はじめに
browserifyは、CommonJSスタイルで書かれたJavaScript (Node.js)のコードをクライアントで実行可能な形式に変換するライブラリです。ここではbrowserifyがビルドの際に用いているbrowser-packが、与えられたコードをどんなコードを出力するのかを見ていきます。browser-packのバージョンは6.0.1。
入力
Readmeにある以下のjsonを試してみる。この形式はちょうどmodule-depsの出力結果と同じもので、browserifyも内部でmodule-depsを利用している。内容は
- 各モジュールを一意に指すID
- コードの本体
- 各モジュールが依存する(内部で
require()
を使って呼び出している)モジュールのパスとID - そのモジュールがエントリポイントかどうか
の4種類。
[
{
"id": "a1b5af78",
"source": "console.log(require('./foo')(5))",
"deps": { "./foo": "b8f69fa5" },
"entry": true
},
{
"id": "b8f69fa5",
"source": "module.exports = function(n) { return n * 111 }",
"deps": {}
}
]
出力
関数outer()
を即時実行するコードがビルドされているのが見てとれる。index.js
を読むとわかるのだが、関数定義はすでにprelude.js
に書かれており、入力したjsonの情報は関数outer()
に与える引数の形に整形されている(なお実際のビルドにはprelude.js
を圧縮・難読化した_prelude.js
がnpm prepublish
時に生成され、そちらが使用される)。
(function outer(modules, cache, entry) {
var previousRequire = typeof require == "function" && require;
function newRequire(name, jumped) {
if (!cache[name]) {
if (!modules[name]) {
var currentRequire = typeof require == "function" && require;
if (!jumped && currentRequire) return currentRequire(name, true);
if (previousRequire) return previousRequire(name, true);
var err = new Error('Cannot find module \'' + name + '\'');
err.code = 'MODULE_NOT_FOUND';
throw err;
}
var m = cache[name] = { exports: {} };
modules[name][0].call(m.exports, function (x){
var id = modules[name][1][x];
return newRequire(id ? id : x);
}, m, m.exports, outer, modules, cache, entry);
}
return cache[name].exports;
}
for (var i = 0; i < entry.length; i++) newRequire(entry[i]);
return newRequire;
})({
"a1b5af78": [function(require, module, exports) { console.log(require('./foo')(5)); }, { "./foo": "b8f69fa5" }],
"b8f69fa5": [function(require, module, exports) { module.exports = function (n) { return n * 111; }; }, {}]
}, {}, ["a1b5af78"]);
理解のためのメモ
- 関数
outer()
は関数newRequire()
を返す。ちなみにbrowserifyの-r
オプションでモジュールを外部から利用できるようにすると、この結果がグローバル変数require
に代入され(先頭にrequire =
のついたコードがビルドされるだけ)、外からrequire()
が呼び出せるようになる。 - また
outer()
はnewRequire()
を返す傍ら、エントリポイントのそれぞれに対してnewRequire()
を実行する。 - 同じモジュールを複数回
require()
する場合はキャッシュを利用して再度評価されるのを防ぐ。 - 関数
outer()
の引数で各モジュールのコードがfunction(require, module, exports) { ... }
にラップされることで、クライアントサイドでもrequire()
やmodule
、exports
が参照できるようになっている。