Edited at

browserify (browser-pack) はどんなコードを出力するのか?

More than 3 years have passed since last update.


はじめに

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.jsnpm 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()moduleexportsが参照できるようになっている。