価値観
- AltJSが色々あって、追いかけるのも大変だし、振り回されたくもない
- 廃れた後、改修とかでそれらに依存したコードに遭遇してウッ…てなりたくない
- Vanilla JSだけが裏切らない
という感じから始めます。
ClosureCompilerの概要
- Google製
- 5年位前からあり、現在はGitHubで開発継続中
- minifyや、コードの最適化ができる
- 依存関係の解決や、型の制約を提供してくれる
- JsDocのアノテーションによる型の宣言なのでソースの可読性が下がらない
- むしろ上がる
どんな圧縮・最適化を行うのか
ブラウザ上でコンパイルを試せるページがあります。
http://closure-compiler.appspot.com/home
Advanced+Pretty printを選択します。
ここでconsole
の手抜きラッパーの例で試してみます。
/** @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');
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)では更に進化してるようで以下の出力になりました。
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ファイルを更新する時にテキストエディタ1つだけじゃ済まなくなる
- そのため修正を他人(あるいは他社)に依頼する時にフォローしないといけない (メンテナンスできる人を減らしてしまう)
- 何よりここまでして速度を上げたり減量しないといけない程の要求が無い
デメリットの解消方法
コンパイルしてコンパイル済みファイルを捨てます。
つまり静的型付け等のためのLinterとして扱います。
そうすると普通のJavaScriptにちょっと精緻なコメントを書くだけのことになりますので、静的型付けの恩恵を受けつつ、導入しても他者(他社)への影響は最小限になります。
トランスパイラとしてのClosureCompiler
静的な型付けを目的とした場合、FacebookからFlowという良さ気な物が出てて、乗り換えかなと思ってたんですが、
ClosureCompilerにいつの間にかES6→ES3/ES5というトランスパイル機能が増えてるようで、babel.js的AltJSとしての側面もありそうだったので今回取り上げてみました。
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');
java -jar compiler.jar --js mylogger.es6.js --js_output_file mylogger.es5.js --language_in ECMASCRIPT6 --language_out ECMASCRIPT5 --formatting pretty_print
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なら廃れる方向に進むことは無いと思いますので、比較的手を出しやすいんじゃないでしょうか。