読者です 読者をやめる 読者になる 読者になる

UglifyJSの--defineと--define-from-moduleが便利すぎる

JavaScript

JavaScriptのコードの中にアプリの設定を定数として埋め込みたいことってありますよね。

例えば、このFacebook Mobileのドキュメントにあるログインの例を借りると、

window.fbAsyncInit = function() {
    FB.init({
        appId: FB_APP_ID,
        status: true,
        cookie: true,
        xfbml: true,
        oauth: true
    });

    FB.Event.subscribe('auth.authResponseChange', function(response) {
        if (__DEBUG__) {
            console.log("Got auth.authResponseChange event");
            console.log(response);
        }
        // Do something
    });
};

のように、あるメソッド(ここではFB.init)を呼び出す際にFacebookアプリIDを指定しなくてはならないわけです。

グローバル変数やオブジェクトのプロパティとしてこうした値を持っていると行儀の悪いやつがこの値を書き換えないとも限らない。(「constを使え」という話になるかもしれないが、とりあえずそれはおいておく。)

こういう状況で、UglifyJS--define--define-from-moduleが非常に便利だということに気がついた。

--defineオプションを使って定数を定義すると、UglifyJSがその定数をリテラルに置き換えてくれる。

$ uglifyjs --define FB_APP_ID=1234 -b fb.js

上のコマンドの出力結果は以下の通り。

window.fbAsyncInit = function() {
    FB.init({
        appId: 1234,
        status: !0,
        cookie: !0,
        xfbml: !0,
        oauth: !0
    }), FB.Event.subscribe("auth.authResponseChange", function(a) {
        __DEBUG__ && (console.log("Got auth.authResponseChange event"), console.log(a));
    });
};

定数の数が多い場合は、node.jsスタイルのモジュールを定義して、

// constants.js
exports.FB_APP_ID = "1234";

--define-module-fromでモジュールへのパスを指定すると、exportsした変数の値がリテラルに置換される。

$ uglifyjs --define-from-module /path/to/constants.js -b fb.js

このコマンドの出力結果は先ほどと同じ。

さらに--define--define-from-moduleは組み合わせて使うことができるので、例えば、

$ uglifyjs --define __DEBUG__=0 --define-from-module /path/to/constants.js -b fb.js

のように__DEBUG__に0を設定すると、UglifyJSがコードの__DEBUG__という変数をリテラルに置き換えて、さらにデッドコードも削除をしてくれるので、最終的な出力は下記のようになり、デバッグ用のロギングコードが消えている。

window.fbAsyncInit = function() {
    FB.init({
        appId: "1234",
        status: !0,
        cookie: !0,
        xfbml: !0,
        oauth: !0
    }), FB.Event.subscribe("auth.authResponseChange", function(a) {});
};

ひとつハマったのが、UglifyJSの--no-mangleオプションを使ってローカル変数の短縮化を無効にしていると、--define--define-from-moduleが効かなくなるということ。つまり、

$ uglifyjs --no-mangle --define __DEBUG__=0 --define-from-module /path/to/constants.js -b fb.js

はうまく行かないので、上の例で言うと、FB_APP_IDや__DEBUG__はそのまま出力される。これがバグなのか仕様なのかはまだ調べていない。


ときどきJavaScriptのコードを圧縮(minify)していないWebサービスを見かけるが、これはmod_deflateなりで転送時に圧縮しているからminifyは不要と割り切っているのかなぁと思う。

そういうサイトのJavaScriptコードを眺めていると、「微妙なやり方だけど仕方がない ( ̄(エ) ̄”)」とか「なぜか動かないのでこうしておきます┐(´ー`)┌」みたいなコメントが残っていたり、console.log, console.errorがもりもり出力されたりして、微笑ましい。

UglifyJSをかけると当然コメントは削除してくれるし、上記の__DEBUG__のような定数を定義しておけば、開発時にのみ必要なデッドコードを削除することもできるので、UglifyJSはファイルサイズの圧縮以外にも有用なツールだと思う。