JavaScript
Node.js
browserify
browser-pack

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

More than 1 year has 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が参照できるようになっている。