使ってみよう!「Closure Tools」 #6 ~Closure Compiler~

2012.02.02

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

毎度お世話になっております。クラスメソッドの稲毛です。

今回『使ってみよう!「Closure Tools」』の第六回では「Closure Compiler」を紹介します。

Closure Compilerを利用すると、依存スクリプトをひとつのJSファイルへ統合できるだけでなく、JSファイルサイズを小さくする為の最適化を施すことができます。

早速、次のセクション「Closure CompilerをEclipse上で利用する際のプロジェクト構成」から見ていきましょう。

Eclipseプロジェクトの構成

今回コンパイルに利用するプロジェクトの構成は以下の通りです。

workspace
│      
├─closure-library・・・(1)
│  │  :
│  │  
│  ├─closure
│  │  ├─bin
│  │  │  └─build
│  │  │          closurebuilder.py・・・(2)
│  │  │  
│  │  ├─css
│  │  │  
│  │  ├─goog
│  │  │  
│  │  └─known_issue
│  │  
│  └─third_party
│      
└─closure-example・・・(3)
    ├─jar
    │      compiler.jar・・・(4)
    │      
    └─WebContent
        │  index.html・・・(5)
        │  deps.js・・・(6)
        │  
        └─scripts
            ├─goog・・・(7)
            │      
            └─example
                    app.js・・・(8)
                    hello.js・・・(9)

(1) closure-library

SVNリポジトリからEclipseプロジェクトとしてチェックアウトしたClosure Libraryです。

(2) closurebuilder.py

Pythonスクリプトclosurebuilder.pyは、第4回「クラスの表現と依存性管理」で利用したdepswriter.pyと同じく「closure-library/closure/bin/build」内に用意されています。ソースコードをコンパイルする際にはこのスクリプトを呼び出します。

(3) closure-example

コンパイル対象の静的 Web プロジェクトです。依存性管理スクリプト(deps.js)によって既に動作するWEBアプリケーションとなっており、実行するとブラウザ上に「Hello, closure!」の文字列を出力します。

(4) compiler.jar

closurebuilder.pyは、自身では依存ファイルの統合を行い、ソースコードの最適化についてはJavaアプリケーションのcompiler.jarを呼び出すことで実現しています。Javaランタイムがお使いのPCにインストールされている事を確認してください。compiler.jarは、Closure Compilerのホームページの右端「How do I start?」内「Download the application.」からダウンロードできます。ダウンロード&解凍したらcompiler.jarをプロジェクトの任意の場所に配置します。(今回は対象プロジェクト内のjarフォルダに配置しました。)

(5) index.html

依存性管理スクリプトで動作する状態になっているので、Closure Libraryのベーススクリプト(base.js)と依存性管理スクリプト(deps.js)をロードし、エントリークラスをRequireしています(11行目)。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello Example</title>
  </head>
  <body>
    <script type="text/javascript" src="scripts/goog/base.js"></script>
    <script type="text/javascript" src="scripts/deps.js"></script>
    <script type="text/javascript">
      goog.require('example.App');
    </script>
  </body>
</html>

(6) deps.js

depswriter.pyで生成された依存性管理スクリプトです。

// This file was autogenerated by closure-library/closure/bin/build/depswriter.py.
// Please do not edit.
goog.addDependency('../../scripts/example/app.js', ['example.App'], ['example.Hello']);
goog.addDependency('../../scripts/example/hello.js', ['example.Hello'], ['goog.dom']);

(7) goog

closure-libraryプロジェクトのgoogフォルダへリンクしたフォルダです。実ファイルはclosure-libraryプロジェクト内に存在しますが、WTPのHTTPプレビューを利用する際には実ファイルがプレビューサーバーにデプロイされます。

(8) app.js

アプリケーションのエントリークラスexample.Appのスクリプトファイルです。example.Helloクラスのインスタンスを生成し、そのsayメソッドを呼びます。

goog.provide('example.App');

goog.require('example.Hello');

goog.scope(function() {
  var Hello = example.Hello;
  
  /**
   * @constructor
   */
  example.App = function() {
    var hello = new Hello();
    hello.say();
  };
});

new example.App();

(9) hello.js

example.Appクラスで利用されるexample.Helloクラスです。文字列「Hello, closure!」を持ったh1要素を生成しbody要素へ追加します。Closure Libraryに用意されているDOM操作ユーティリティgoog.domを利用しています。

goog.provide('example.Hello');

goog.require('goog.dom');

goog.scope(function() {
  var dom = goog.dom;
  
  /**
   * @constructor
   */
  example.Hello = function() {};
  
  /**
   * @const
   * @type {string}
   */
  example.Hello.STRING = 'Hello, closure!';
  
  /**
   * Say hello.
   */
  example.Hello.prototype.say = function() {
    var body = dom.getDocument().body;
    var h1 = dom.createDom('h1', null, example.Hello.STRING);
    dom.appendChild(body, h1);
  };
});

外部ツールの構成

今回も、depswriter.pyの時と同様にclosurebuilder.pyをEclipseの外部ツールとして登録して利用します。

名前 ClosureBuilder コンパイル(任意の名前)
ロケーション
${env_var:PYTHON_HOME}\python.exe
作業ディレクトリー
${project_loc}/WebContent/
引数
${project_loc:closure-library}/closure/bin/build/closurebuilder.py
  --root=${project_loc:closure-library}
  --root=scripts
  --namespace="example.App"
  --output_file=compiled.js
  --output_mode=compiled
  --compiler_jar=${project_loc}/jar/compiler.jar
  --compiler_flags="--compilation_level=${string_prompt:compilation_level:WHITESPACE_ONLY}"

closurebuilder.pyのオプション「--compiler_flags」ではClosure Compilerアプリケーションに対してのオプションを指定することが可能です。今回の例では「--compilation_level」というオプションでコンパイルレベルを指定していますが、この指定によりJSファイルに対する最適化の強度を変更することが出来ます。

今回は外部ツールの構成に使用しているEclipseの内部引数を少し工夫しました。プロジェクトの構成が同じであれば、プロジェクトツリーペインで選択中のプロジェクトに対して外部ツールが実行されるようになっています。併せて、後述するコンパイルレベルがコンパイル実行時に入力できるようにプロンプトを設けました。

以上でコンパイルを行う環境が整いました。

コンパイルの実行

Eclipseのプロジェクトツリーペインでコンパイル対象のプロジェクトclosure-exampleを選択状態にし、外部ツールとして設定した「ClosureBuilder コンパイル」を実行します。compilation_levelはとりあえず初期値「WHITESPACE_ONLY」のまま処理を進めるとWebContent内にcompiled.jsが生成されますので、index.htmlを以下のように書き換えます。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello Example</title>
  </head>
  <body>
    <script type="text/javascript" src="scripts/compiled.js"></script>
  </body>
</html>

依存関係にあるJSファイルが全て統合されるので、ひとつのJSファイルを読み込むだけで動作するようになりました。HTTPプレビューで実行すれば、ブラウザには「Hello, closure!」の文字列が変わらずに表示される筈です。

コンパイルレベル

指定できるコンパイルレベルには三種類あり、コメントの除去はレベルを問わず実施されますが、その他の最適化内容についてはレベルによって異なります。最適化強度が強い程ソースコードに変更が加わる為、コンパイルレベルによって課せられる制約が変わります。この制約に従わない場合、コンパイル後の動作に不具合が生じる可能性があるため注意が必要です。制約事項の詳細についてはこちらに記されています。

WHITESPACE_ONLY

コンパイル前サイズ 292,519 バイト(概算)
コンパイル後サイズ 78,061 バイト

コンパイルレベル「WHITESPACE_ONLY」では、空白に相当する文字(スペースや改行)が除去されます。単純に不要な空白を除去するのみなので、コンパイル前の動作がほぼ保証されていると考えて良いでしょう。

SIMPLE_OPTIMIZATIONS

コンパイル前サイズ 292,519 バイト(概算)
コンパイル後サイズ 61,474 バイト

コンパイルレベル「SIMPLE_OPTIMIZATIONS」では、コードサイズを減らすために関数パラメータ、ローカル変数、およびローカルで定義された関数の名前が変更されます。

このレベルのコンパイルを実施する為には、下記の実装を避ける必要があります。

  • with構文の使用
  • eval()の使用
  • 関数やパラメータ名の文字列表現

ADVANCED_OPTIMIZATIONS

コンパイル前サイズ 292,519 バイト(概算)
コンパイル後サイズ 4,924 バイト

コンパイルレベル「ADVANCED_OPTIMIZATIONS」では、SIMPLE_OPTIMIZATIONSの変更に加え、プロパティ、変数、およびグローバルな関数名の変更、デッドコードの除去、およびプロパティ平坦化が行われます。コンパイル後のファイルサイズ(約60分の1!)から分かるように、非常にアグレッシブな最適化が施されます。

このレベルのコンパイルを実施する為には、SIMPLE_OPTIMIZATIONSの制約事項に加えて、下記の実装を避ける必要があります。

  • グローバル変数、関数、およびプロパティ名の変更による影響
    • 非コンパイルコードとの相互参照
    • オブジェクトに対する文字列を用いたプロパティ参照
    • グローバルオブジェクトのプロパティとしての変数参照
  • デッドコードの除去による影響
    • コンパイルされたコード外部からの関数呼び出し
    • 反復処理でプロパティによるコンストラクタやプロトタイプの関数取得
  • オブジェクトプロパティの平坦化による影響
    • thisをコンストラクタやプロトタイプメソッドの外で使用

まとめ

如何でしたでしょうか?Closure Compilerによるコード圧縮&最適化の片鱗は感じて頂けたのではないかと思います。実プロジェクトでの利用に関しては制約事項の徹底コンパイルレベルの選定がキーポイントとなりますが、上手く運用すれば受けられる恩恵は大きなものになるでしょう。

これまでの連載で「Eclipseの利用」、「クラスベースのオブジェクト指向」、「スクリプトファイル間の依存性管理」、「スクリプトファイルのコンパイル(統合&最適化)」と紹介してきました。これらの特徴により「Adobe Flex」に触れたことのある開発者であれば比較的容易に「HTML + JavaScript」の開発が始められるのではないかと思います(私はそうでした。^^;)。今まで二の足を踏んでいた方も是非Closure Toolsで「HTML + JavaScript」開発を始めてみてください!