yutaponのブログ

javascript界隈の興味あるネタを備忘録的に残しておく場所

Browserifyの使い方について調べてみた

最近Browserifyって単語を良く見るようになりました。

Browserifyをざっくり説明すると、

  • ブラウザ上でもNode.js用モジュールを使えるようにする
  • ブラウザでもrequire()を使ったモジュール管理を使えるようにする

という特徴があるようです。

browser(ブラウザ)+ fy(〜する)という単語からもNode.jsをブラウザ化するってニュアンスでしょうか。

require()といえばRequireJSも提供してますよね。
Browserify使うことでRequireJSを使わなくてもモジュール管理ができるようになりそうです。


Browserifyの使い方

まずは公式サイトに載ってるコードを試してみます。
browserify

これはどうやらNode.js向けのモジュールをブラウザでも使えるようにするチュートリアルのようです。

まず作業ディレクトリを作成します。

$ cd path/to/workspace
$ mkdir browserify  # browserifyを試す用のディレクトリ
$ cd browserify


このディレクトリ直下に main.js を作成します。

// @file main.js

var foo = require('./foo');
var gamma = require('gamma');

var n = gamma(foo(5) * 3);
var txt = document.createTextNode(n);
document.body.appendChild(txt);


foo.jsをmain.jsと同じ階層に作成します。

// @file foo.js

module.exports = function (n) { // 外部から呼べるようにexportsする
    return n * 11;
};

Node.jsな書き方ですね。

main.jsではgammaというモジュールも読み込んでいるのでインストールします。

$ npm install gamma
..
gamma@0.1.0 node_modules/gamma


このままではmain.jsは動きません。
browserifyを使ってファイルを結合する必要があります。
browserifyコマンドが使えるようにグローバルにインストールします。

$ npm install -g browserify  # グローバルにインストールする


-o オプションをつけてファイルの結合をします。
このタイミングで依存関係があるファイルを検出し、
ブラウザでも実行できる形に生成するようです。

$ browserify main.js -o bundle.js  # bundle.jsという名前のファイルに書き出す


生成されたbundle.jsの中身はこのようになってました。

(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
// @file foo.js

module.exports = function (n) { // 外部から呼べるようにexportsする
    return n * 11;
};
},{}],2:[function(require,module,exports){
// @file main.js

var foo = require('./foo');
var gamma = require('gamma');

var n = gamma(foo(5) * 3);
var txt = document.createTextNode(n);
document.body.appendChild(txt);


},{"./foo":1,"gamma":3}],3:[function(require,module,exports){
// transliterated from the python snippet here:
// http://en.wikipedia.org/wiki/Lanczos_approximation

var g = 7;
var p = [
    0.99999999999980993,
    676.5203681218851,
    -1259.1392167224028,
    771.32342877765313,
    -176.61502916214059,
    12.507343278686905,
    -0.13857109526572012,
    9.9843695780195716e-6,
    1.5056327351493116e-7
];

var g_ln = 607/128;
var p_ln = [
    0.99999999999999709182,
    57.156235665862923517,
    -59.597960355475491248,
    14.136097974741747174,
    -0.49191381609762019978,
    0.33994649984811888699e-4,
    0.46523628927048575665e-4,
    -0.98374475304879564677e-4,
    0.15808870322491248884e-3,
    -0.21026444172410488319e-3,
    0.21743961811521264320e-3,
    -0.16431810653676389022e-3,
    0.84418223983852743293e-4,
    -0.26190838401581408670e-4,
    0.36899182659531622704e-5
];

// Spouge approximation (suitable for large arguments)
function lngamma(z) {

    if(z < 0) return Number('0/0');
    var x = p_ln[0];
    for(var i = p_ln.length - 1; i > 0; --i) x += p_ln[i] / (z + i);
    var t = z + g_ln + 0.5;
    return .5*Math.log(2*Math.PI)+(z+.5)*Math.log(t)-t+Math.log(x)-Math.log(z);
}

module.exports = function gamma (z) {
    if (z < 0.5) {
        return Math.PI / (Math.sin(Math.PI * z) * gamma(1 - z));
    }
    else if(z > 100) return Math.exp(lngamma(z));
    else {
        z -= 1;
        var x = p[0];
        for (var i = 1; i < g + 2; i++) {
            x += p[i] / (z + i);
        }
        var t = z + g + 0.5;

        return Math.sqrt(2 * Math.PI)
            * Math.pow(t, z + 0.5)
            * Math.exp(-t)
            * x
        ;
    }
};

module.exports.log = lngamma;

},{}]},{},[2])


生成されたbundle.jsをHTMLのscriptタグで読み込むことで
ブラウザでもNode.jsのモジュールが使えることになります。


Browserifyでモジュール管理する

実際はNode.jsのモジュールをブラウザ化するよりも、
フロントのJSをモジュール毎に管理する用途で使われることの方が多いようです。

Browserifyを使った開発をするとなると、browserifyコマンドを実行した後に
生成されるコードでなければ実行できないことになります。
でもいちいちコードを修正してターミナルからbrowserifyコマンドを叩くなんてしたくありません。

そういう面倒くさいことは人間がやるんじゃなくてGrunt.jsにさせましょう。
Grunt.jsはJenkinsのフロント版みたいなやつです。
Grunt: The JavaScript Task Runner

ちなみにGrunt.jsの競合として最近gulp.jsが出てきました。
gulp.js - the streaming build system

後発らしく、Grunt.js使ってて不満な部分を解消していますが、
まだGruntよりも対応するプラグインが少ないのが現状。
いずれはGruntに取って代わる気がする。

今回はGrunt.jsを使っていきます。
まだGrunt.jsが入っていなければインストールします。

$ npm init  # package.jsonを作っておく。
.. # (適当にエンターを押す)

$ npm install -g grunt-cli  # グローバルにインストール. gruntコマンドが使えるようになる
..

$ npm install grunt --save-dev # 本体をインストール
..

$ grunt --version  # バージョン確認
grunt-cli v0.1.13
grunt v0.4.3


他にもGrunt用のプラグインをいくつかインストールしておきます。

$ npm install grunt-browserify grunt-contrib-watch --save-dev


アプリケーションのルートディレクトリにGruntfile.jsを作成して、設定を記述します。

// @file Gruntfile.js

module.exports = function (grunt) {
    var pkg = grunt.file.readJSON('package.json');

    grunt.initConfig({
        browserify : {  // タスク名. $ grunt browserify で実行できる
            dist : {
                src : 'src/main.js',  // エントリーポイントとなるファイル
                dest : 'build.js'  // 出力するファイル名
            }
        },
        watch : {  // タスク名. $ grunt watch で実行できる
            scripts : {
                files : ['src/**/*.js'],  // 監視対象のファイル
                tasks : ['browserify']  // 変更があったら呼ばれるタスク
            }
        }
    });

    // grunt関連のプラグインはpackage.jsonに記述されたものを読み込む
    Object.keys(pkg.devDependencies).forEach(function (devDependency) {
        if (devDependency.match(/^grunt\-/)) {
            grunt.loadNpmTasks(devDependency);
        }
    });

    // $ grunt で実行するタスクに watch を指定
    grunt.registerTask('default', ['watch']);
};


Gruntfile.jsをよく見るとわかりますが、srcディレクトリとdistディレクトリを作成しました。
srcにはmain.jsとfoo.jsが入ってます。

この状態でgruntコマンドを叩くと

$ grunt
Running "watch" task
Waiting...

と、ファイルの監視が始まります。

では新しくsrc/hello.jsというファイルを追加してみます。

// @file hello.js

/**
 * 引数に与えられた文字列の先頭にHello, を追加する
 * @param  {String} str
 * @return {Strint}
 */
function hello(str) {
    return 'Hello, ' + str;
}

module.exports = hello;


src/main.jsに修正を加えます。

// @file main.js

var foo   = require('./foo');
var hello = require('./hello');  // 追記
var gamma = require('gamma');

var n   = gamma(foo(5) * 3);
var txt = document.createTextNode(hello(n)); // 追記

document.body.appendChild(txt);


index.htmlを用意します。

<!DOCTYPE html>
<html>
    <head>
        <title>browserify test</title>
    </head>
    <body>
        <!-- build.js はbodyの最後で読み込む -->
        <script src="dist/build.js"></script>
    </body>
</html>


index.htmlをブラウザで開くと

Hello, 3.287218585534316e+293

という謎の文字列が表示されることが確認できます。


まとめ

  • browserifyを使うとNode.jsで使うモジュールをフロントの開発でも使うことができる
  • module.exports または exports で参照を外出しすることで、他のモジュールからはrequire()で参照を得られる
  • gruntやgulpも同時に使わないと開発が捗らない



おわりに

コードの規模が大きくなると、browserifyによって生成されるコードが大きくなるんじゃないかという懸念がありますが、たぶんビルドの設定で何とかなるんでしょう。
ここらへんはもう少し調査が必要。
あと深く調べられたわけじゃないので入門記事みたいになった。
Grunt.jsをブログで扱うのが何気に初めてでしたが、もっといろいろできるのでまた登場する予定。