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

Closure CompilerのAPIをnode.jsで叩こう!

JavaScript

Closure Compiler使ってますか?
JavaScriptのコードを圧縮してくれるGoogleのサービスです.
圧縮してくれるだけじゃなくて, ちゃんとErrorやWarningも吐いてくれるので,
私はJavaScriptのコードの検証にも使っています.

さて, このClosure CompilerにはAPIがあって, 詳しいチュートリアルがあるのですが(Googleチュートリアルはいつも詳しい!),
サンプルがPythonです.
JavaScriptなんだからJavaScriptから叩きたいよね〜
ってことで, 練習も兼ねて書いてみました.
node.jsはインストールしているものとします.

#! /usr/bin/env node

var http = require('http'),
    querystring = require('querystring');

var compile = function (code, callback, option) {

  //     compile(JavaScript_code, callback , option);
  //       JavaScript_code :: String
  //       callback :: Function
  //         callback = function (error, result) {}
  //       option :: Object (optional)
  //         option = {
  //           // 'SIMPLE_OPTIMIZATIONS' (default) or 'WHITESPACE_ONLY' or 'ADVANCED_OPTIMIZATIONS'
  //           compilation_level: 'SIMPLE_OPTIMIZATIONS',
  //           output_info: 'compiled_code', // (default) or 'warnings' or 'errors' or 'statistics'
  //             //  OR  set output_info in Array
  //             output_info: ['compiled_code', 'warnings', 'errors', 'statistics'],
  //         }
  //

  var option = option || {},
      info = option.output_info;

  if(Object.prototype.toString.call(info) === '[object Array]') {
    for(var i = -1, l = info.length, o = option; ++i < l;) {
      o.output_info = info[i];
      compile(code, callback, o);
    }
    return;
  }

  try {

    if(option.compilation_level && 
        ['SIMPLE_OPTIMIZATIONS', 'WHITESPACE_ONLY', 'ADVANCED_OPTIMIZATIONS']
          .indexOf(option.compilation_level) < 0) {
      throw 'unknown compilation_level: ' + option.compilation_level;
    }

    if(option.output_info && 
        ['compiled_code', 'warnings', 'errors', 'statistics']
          .indexOf(option.output_info) < 0) {
      throw 'unknown output_info: ' + option.output_info;
    }

    var client = http.createClient(80, 'closure-compiler.appspot.com')
                     .on('error', callback),
        req = client
                .request('POST', '/compile', {
                  'host': 'closure-compiler.appspot.com',
                  'Content-Type': 'application/x-www-form-urlencoded'
                })
                .on('error', callback);
    req.on('response', function (r) {
      r.setEncoding('utf8');
      r.on('data', function (c) {
        if(c === '' || c === '\n') {
          // Code was invalid.
          compile(
            code,
            function (e, c) {
              console.log(c.toString());
            },
            {
              output_info: 'errors',
              compilation_level: option.compilation_level
            }
          );
          return;
        }
        callback(null, c);
      });
    });
    req.end(
      querystring.stringify({
        js_code: code.toString('utf-8'),
        output_format: 'text',
        output_info: option.output_info || 'compiled_code',
        compilation_level: option.compilation_level || 'SIMPLE_OPTIMIZATIONS'
      })
    );
  } catch (e) {
    callback(e);
  }
};

compile(
  '(function () {var hoge = {}; var f = function () {return parseInt(2);}; hoge["foo"]= 2; /*  comment  */ ;;if(  f() > 0)  { alert(hoge["foo"])}; console.log(f()); })()',
  function (e, c) {
    if(e) {
      console.log(e.toString());
      return;
    }
    console.log(c);
 }
);

このコードをclosureCompile.jsとして保存して, 実行すれば,

$ chmod +x closureCompile.js
$ ./closureCompile.js
(function(){var a={};a.foo=2;parseInt(2)>0&&alert(a.foo);console.log(parseInt(2))})();

と表示されるはずです.

第三引数にオプションを付けることも出来ます.
output_infoには, 文字列でも配列でも与えることが出来ます.

compile(
  'alert(  10 * 2);',
  function (e, c) {
    if(e) {
      console.log(e.toString());
      return;
    }
    console.log(c);
 },
 {
   compilation_level: 'ADVANCED_OPTIMIZATIONS',
   output_info: ['compiled_code', 'statistics']
 }
);
alert(20);

Original Size: 16
Original Gzip Size: 36
Compressed Size: 10
Compressed Gzip Size: 30
Compilation Time: 0

ファイルに書き込むときはリダイレクトを使いましょう.

発展編

ここまで来てなんですが, モジュールとして作ってくれた親切な方がいます.
Use the Google Closure Service With Node.jsです.
使い方はこんな感じです.
1. サイトの下の方の"Get the code here."をクリックすると, githubへ.
2. ダウンロードする. 必要なファイルはlib/closure.jsだけ.
3. closure.jsと同じディレクトリに次のようなJavaScriptコードを書く.

#! /usr/bin/env node
var closure = require('./closure');
closure.compile(
  '(function () {var variable =    10 *   20;  /*  comment   */  ;;   alert (  variable  );})();',
  function (e, c) {
    if(e) {
      console.log(e.toString());
      return;
    }
    console.log(c);
  }
);

4. 実行権限を与えて実行すれば, alert(200);と表示されます.

モジュールになってるから使いやすいですね(^^
ただ, オプションがADVANCED_OPTIMIZATIONSになっているので,
SIMPLE_OPTIMIZATIONSに書きなおしておいたほうがいいかと.
コード見てみても, 私の書いたものとあんまり変わりませんね!
あ, 違います. 参考にして書いただけですごめんなさい.
ファイルの書き込みはnode.jsのfs.writeFileを使いましょう.
あと, Closure CompilerのAPIを叩き過ぎると規制がかかります.
気を付けましょう.


結論: node.jsやばい.