仕事で使うJavaScriptにClosureCompilerを推してみる



# 価値観

* AltJSが色々あって、追いかけるのも大変だし、振り回されたくもない
* 廃れた後、改修とかでそれらに依存したコードに遭遇してｳｯ…てなりたくない
* [Vanilla JS](http://vanilla-js.com/)だけが裏切らない

という感じから始めます。

# ClosureCompilerの概要

* Google製
* 5年位前からあり、現在は[GitHubで開発継続中](https://github.com/google/closure-compiler)
* minifyや、コードの最適化ができる
* 依存関係の解決や、型の制約を提供してくれる
* JsDocのアノテーションによる型の宣言なのでソースの可読性が下がらない
* むしろ上がる

# どんな圧縮・最適化を行うのか

ブラウザ上でコンパイルを試せるページがあります。
http://closure-compiler.appspot.com/home
Advanced＋Pretty printを選択します。

ここで`console`の手抜きラッパーの例で試してみます。

```js:コンパイル前
/** @constructor */
function MyLogger(writer) {
  this.writer_ = writer;
}
MyLogger.prototype.log_ = function(level, msg) {
  if (this.writer_ && this.writer_[level]) {
    this.writer_[level](msg);
  }
};
MyLogger.prototype.info = function(msg) {
  this.log_('info', msg);
};
MyLogger.prototype.warn = function(msg) {
  this.log_('warn', msg);
};
MyLogger.prototype.error = function(msg) {
  this.log_('error', msg);
};

// ※以下のように呼び出すコードが無いとデッドコードとして削除されます
var logger = new MyLogger(console);
logger.info('hi');
```


```js:コンパイル後
function b(a) {
  this.a = a;
}
function c(a, d, e) {
  if (a.a && a.a[d]) {
    a.a[d](e);
  }
}
b.prototype.info = function(a) {
  c(this, "info", a);
};
b.prototype.warn = function(a) {
  c(this, "warn", a);
};
b.prototype.error = function(a) {
  c(this, "error", a);
};
(new b(console)).info("hi");
```

`function c` が元 `log_` メソッドなんですが、インスタンスメソッドじゃなくなってます。
それに伴い引数の数も変えられています。
コンパイラを名乗るだけあります。

ちなみに`error`メソッドを削除すると`log_`がインライン展開に変わります。
速度的な最適化と言うより文字数的な最小値を求めている感じだと思います。

ダウンロード版(jar)では更に進化してるようで以下の出力になりました。

```js:jar版出力
var a = new function(){
  this.a = console;
};
a.a && a.a.info && a.a.info("hi");
```


# Pure JavaScriptとの開発の違い

まず、コメントでJsDocを書く必要がありますが、以下の例にあるようにコーディング規約＋αレベルのコメントです。
https://developers.google.com/closure/compiler/docs/js-for-compiler

以前JsDoc Toolkitを併用した時は、`@namespace` など片方で余るタグが出ましたが特に支障ありませんでした。

どんなコードについて怒ってくれるのか、またどんなコードが書けなくなるのかは以下のページのコードが参考になります。
https://developers.google.com/closure/compiler/docs/error-ref

警告/エラー/無視については項目別に細かく切り替えることができます。
https://github.com/google/closure-compiler/wiki/Warnings


使ってるライブラリでJsDocが書かれていない場合は、インターフェイスや型の定義だけを行ったexternファイルを探すか作る必要があります。
以下のようなものであれば既に用意されています。
https://github.com/google/closure-compiler/tree/master/contrib/externs

そして、最終ソースを得るためにコンパイラを動かす必要があります。


# Pure JavaScriptと比較してのデメリット

（比較対象はClosureCompilerに限らず、大抵のビルドするものに当てはまりそうです）

* コンパイル通っても、最適化を有効にしていると動かないことあって怖い (グローバルに残しておいて欲しい識別子が消えるとか)
* コンパイル済みファイルやソースマップが、バージョン管理でノイズになる
* JSファイルを更新する時にテキストエディタ１つだけじゃ済まなくなる
* そのため修正を他人（あるいは他社）に依頼する時にフォローしないといけない (メンテナンスできる人を減らしてしまう)
* 何よりここまでして速度を上げたり減量しないといけない程の要求が無い

# デメリットの解消方法


コンパイルしてコンパイル済みファイルを捨てます。
つまり静的型付け等のためのLinterとして扱います。

そうすると普通のJavaScriptにちょっと精緻なコメントを書くだけのことになりますので、静的型付けの恩恵を受けつつ、導入しても他者(他社)への影響は最小限になります。



# トランスパイラとしてのClosureCompiler

静的な型付けを目的とした場合、Facebookから[Flow](http://flowtype.org/)という良さ気な物が出てて、乗り換えかなと思ってたんですが、
ClosureCompilerにいつの間にかES6→ES3/ES5という[トランスパイル機能](https://github.com/google/closure-compiler/wiki/ECMAScript6)が増えてるようで、babel.js的AltJSとしての側面もありそうだったので今回取り上げてみました。

```js:mylogger.es6.js（入力）
class MyLogger {
  constructor(writer) {
    this.writer_ = writer;
  }
  log_(level, msg) {
    if (this.writer_ && this.writer_[level]) {
      this.writer_[level](msg);
    }
  }
  info(msg) {
    this.log_('info', msg);
  }
  warn(msg) {
    this.log_('warn', msg);
  }
  error(msg) {
    this.log_('error', msg);
  }
}

var logger = new MyLogger(console);
logger.info('hi');
```


```text:コマンド
java -jar compiler.jar --js mylogger.es6.js --js_output_file mylogger.es5.js --language_in ECMASCRIPT6 --language_out ECMASCRIPT5 --formatting pretty_print
```


```js:mylogger.es5.js（出力）
var MyLogger = function(a) {
  this.writer_ = a;
};
MyLogger.prototype.log_ = function(a, b) {
  if (this.writer_ && this.writer_[a]) {
    this.writer_[a](b);
  }
};
MyLogger.prototype.info = function(a) {
  this.log_("info", a);
};
MyLogger.prototype.warn = function(a) {
  this.log_("warn", a);
};
MyLogger.prototype.error = function(a) {
  this.log_("error", a);
};
var logger = new MyLogger(console);
logger.info("hi");
```

仕様先取り型のAltJSなら廃れる方向に進むことは無いと思いますので、比較的手を出しやすいんじゃないでしょうか。