Closure Compilerを使う!
--manage_closure_dependenciesによる依存関係の自動解決
最終更新:
aias-closurecompiler
--manage_closure_dependenciesによる依存関係の自動解決
ブラウザはJavaScriptを記述された順に処理します。アプリケーションが複数のJavaScriptファイルから構成されている場合、開発者はそれらが適切な順序で実行されるようにファイルの並び順を管理しなければなりません。以下の説明における依存関係とは、このようなファイル単位のコードの実行順序のことを指します。
Closure Libraryはファイル間の依存関係の管理を助けるいくつかのpythonスクリプトを提供しています。Closure Libraryの世界では、各JavaScriptファイル内でシンボルが最初に定義されるところで goog.provide が呼び出され、シンボルが使用されるファイルの先頭で goog.require が呼び出されます。つまり goog.provide によるシンボルの宣言は、そのシンボルを指す全ての goog.require よりも先行して実行されなければならないのです。Closure Libraryのスクリプトはこの原則を利用してファイルの順序を自動的に決定します。
そしてClosure Compiler Applicationもその原則に基づいたファイルの依存関係の自動解決をサポートしています。この機能はは --manage_closure_dependencies オプションによって有効化されます。
- 注意:Closure Compiler Service UIとClosure Compiler Service APIは依存関係の自動解決をサポートしません。
- このページは公式サイトのこちらを元に作成しました。
目次:
ファイルを並べ替える
次のような3つのファイルがあったとします:
goog.provide('ice.cream');
// File 2 - cone.js
goog.provide('waffle.cone');
// File 3 - shop.js
goog.require('ice.cream');
goog.require('waffle.cone');
あなたは shop.js をコンパイルしたいと考え、それをCompilerに渡しました:
$ java -jar compiler.jar --js shop.js
shop.js:2: ERROR - required "ice.cream" namespace not provided yet
goog.require('ice.cream');
^
shop.js:3: ERROR - required "waffle.cone" namespace never provided
goog.require('waffle.cone');
^
2 error(s), 0 warning(s)
何がおきたのでしょうか?Compilerは shop.js が ice.cream と waffle.cone を必要としているが、それがどこに存在するか分からないと伝えています。あなたはそれらを明示的に渡す必要があります。もう一度やってみましょう:
$ java -jar compiler.jar --js shop.js --js icecream.js --js cone.js
shop.js:2: ERROR - required "ice.cream" namespace not provided yet
goog.require('ice.cream');
^
shop.js:3: ERROR - required "waffle.cone" namespace not provided yet
goog.require('waffle.cone');
^
2 error(s), 0 warning(s)
Compilerは ice.cream と waffle.cone の場所を知りましたが、それらを間違った順序で処理しようとしました。通常Closure Compilerはコマンドライン上のファイル名の記述順に従ってファイルを処理するので、この例では cone.js は shop.js より後に現れることになります。
コマンドライン引数を正しく並べ替えればいいのですが、JSファイルの数が多い場合にはそれはとても大変な作業です。何かのツールがこれをやってくれればいいのですが・・・そこで、 --manage_closure_dependencies が登場します:
$ java -jar compiler.jar --js shop.js --js icecream.js --js cone.js --manage_closure_dependencies true
var ice={};ice.cream={};var waffle={};waffle.cone={};
これで、全てうまくコンパイルされました。
依存関係のないファイルの順序
依存関係のないファイルの間では、コマンド指定によるファイル順が維持されます。上の例のコンパイル結果において icecream.js の内容が cone.js より前に来ているのは、コマンドライン上で icecream.js が cone.js より前にあるからです。
goog.provideとgoog.requireの展開
上のコードの中には goog.provide と goog.require を定義した箇所がどこにもありません。それにも関わらずエラーが発生しなかったのはなぜでしょうか?
それはClosure Compilerがそれらをある種の予約語のように見なしているからです。Compilerはそれらを機械的に変数やプロパティの宣言に展開します。例え goog.provide / goog.require の定義がコード内になくても、また仮にJSファイル内でそれらの関数が全く別物として実装されていたとしても、CompilerはやはりClosure Libraryの実装に従ってシンボルの展開を行います。このロジックはハードコーディングされたものです。
コンパイルされていない状態でコードを動作させたい場合は、Closure Libraryのbase.jsをコードと共にロードするか、base.js内の実装をコピーペーストしてコード内に取り込んでください。
goog.provideとgoog.requireはそのままの形式で使う
Closure Compilerは依存関係を決定するためのコード解釈を一切行ないません。行うのは正規表現による検索だけです。もし provide または require ステートメントがファイルの先頭にCompilerが想定するような形式で存在しないのであれば、それらが発見されることはありません。仮に goog.require を毎回記述するのが面倒だったとしても、以下のように短縮形を使用してはいけません:
var r = goog.require;
r('waffle.cone');
コード解釈を行わない理由はGoogleによって次のように説明されています -- 我々がその方法を取らないのには理由があります。それは100%の怠惰からではありません...せいぜい80-90%というところです。結局、数千のファイルをディスクからロードしそれらのパースツリーを作成することは時間とメモリを消費します。もしあなたが数個のファイルを必要としているだけだとしても、実際には数百の、最後には除去されてしまうであろうライブラリファイルが渡されているのです。それらのファイルを理解しようとしてコンパイル処理にかかる時間が一桁遅くなってしまうことを我々は望みません。
- 備考:バグか仕様かは不明ですが、管理人の環境ではコンパイルレベルに ADVANCED_OPTIMIZATIONS を指定した場合 goog.provide と goog.require の展開は行われませんでした。この場合はClosure Libraryのbase.jsをコンパイル対象に含めるか、base.js内の実装をコピーペーストしてコード内に取り込む必要があります。
不要なファイルの除去
しばらくして、あなたの友人がアイスクリーム店のチェーンを始めることになりました。彼のチェーンはあなたのオリジナルの店と依存関係をもつので、あなたは明示的に goog.provide で店のクラスを宣言します:
// File 3 - shop.js
goog.provide('ice.cream.Shop');
goog.require('ice.cream');
goog.require('waffle.cone');
それから、再度Compilerを実行します:
$ java -jar compiler.jar --js shop.js --js icecream.js --js cone.js --manage_closure_dependencies true
なんと!出力は空でした。何が起こったのでしょうか?
ライブラリファイルとアプリケーションファイル
--manage_closure_dependencies はライブラリファイルとアプリケーションファイルという概念でファイルを2種類に分類します:
-
ライブラリファイル
- goog.provide メソッドによるシンボルを定義をもつ。
- 他のファイルから goog.require メソッドで呼び出されたシンボルを含むファイルだけがコンパイル対象になる。呼び出し側のファイルはライブラリファイルでもアプリケーションファイルでもよい。
-
アプリケーションファイル
- goog.provide メソッドによるシンボルを定義をもたない。
- 常にコンパイル対象になる。
上記の定義に従うと shop.js は goog.provide メソッドによるシンボルを定義をもつライブラリファイルです。しかし ice.cream.Shop はどのファイルからも require されていないので、 shop.js はコンパイル対象から除外されます。このため ice.cream と waffle.cone は require しているファイルが存在しなくなり、 icecream.js と cone.js もコンパイル対象から除外されます。結果、出力されるコードは全く無くなってしまいました。
では、新しいファイルを定義しましょう:
// File 4 - shop-app.js
goog.require('ice.cream.Shop');
shop-app.js をCompilerに渡すファイルに含めれば、コンパイルは予想通りに行われます:
$ java -jar compiler.jar --js shop-app.js --js shop.js --js icecream.js --js cone.js --manage_closure_dependencies true
var ice={};ice.cream={};var waffle={};waffle.cone={};ice.cream.Shop={};
shop-app.js → shop.js → icecream.js , cone.js とつながる依存関係のチェーンが成立したことで、意図したとおりにプログラムを動作させることができるようになりました。
この機能は依存関係のチェーンに含まれる(つまり実際に使用されている)ファイルだけをコンパイル対象にしてくれます。このことで goog.provide を使用している大きなライブラリのファイル管理が容易になります。もしあなたがClosure Libraryを使うコードをコンパイルするのなら、Closure Libraryの全部のファイルをClosure Compilerに渡してください。 --manage_closure_dependencies を有効にしておけば、アプリケーションファイルから明示的に要求されていないファイルは自動的に結果から除去されます。
- Closure Libraryを使用するコードのコンパイルについては、Closure Libraryが用意するツールからCompilerを利用する方がより簡単です。詳しくはClosure Libraryのチュートリアルを参照してください。
--manage_closure_dependencies が行う除去はファイル単位の処理である点に注意してください。不使用コードのより厳密な削除はcompilation_levelオプションを ADVANCED_OPTIMIZATIONS に設定することで実現できます。( ADVANCED_OPTIMIZATIONS はライブラリファイルとアプリケーションファイルを区別しません。)
--manage_closure_dependenciesオプションとコードチェック
--manage_closure_dependencies オプションを使用すると、Closure Compilerは依存関係を見つけ出し不要なライブラリファイルを除去します。この判定は処理の早い段階で行われ、除去されたファイルに対してはそれが文法的に正しいかどうかをチェックする処理は行われません。
もしあなたがライブラリを書いておりライブラリの文法チェックを主な目的としてClosure Compilerを使っているのなら、 --manage_closure_dependencies は使うべきではありません。アプリケーションが必要とするコンパイル処理とは別に、文法チェックのためのビルドルールを作るべきでしょう。
コンパイルに含まれるファイルを確認する
--manage_closure_dependencies オプションの動作は時折魔術的に思われることがあります。Compilerは全てのファイルを読み込み、その後コンパイルに含めるファイルを抜き出してサブセットを作成します。どのファイルが選ばれたのか知ることはできないのでしょうか?この目的のためには--output_manifestオプションが用意されています:
$ java -jar compiler.jar --js shop-app.js --js shop.js --js icecream.js --js cone.js --manage_closure_dependencies true --output_manifest manifest.MF
...
$ cat manifest.MF
icecream.js
cone.js
shop.js
shop-app.js
コマンドライン上の --output_manifest には任意の出力ファイル名を指定します。Compilerはそのファイルにソート済みの処理対象ファイルリストを書き出します。
// File 1 - icecream.js