使ってみよう!「Closure Tools」 #4 ~クラスの表現と依存性管理~

2011.09.21

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

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

今回は『使ってみよう!「Closure Tools」』の第四回として、Closure Toolsを利用する際に推奨される形式でクラスを作成し、そのクラスを依存性管理を使って利用してみます。

なお、今回からブラウザには外部ブラウザとして「Google Chrome」を利用します。HTMLのプレビューでEclipseの内部ブラウザが表示される場合は、ウィンドウメニューから「設定>一般>Web ブラウザー」でChromeを使用するように変更してください。

クラスの表現

プロトタイプベースのJavaScriptでクラスを表現する方法にはいくつかの書式がありますが、Closure Toolsではクラスを下記のように表現します。

Hello.js

goog.provide('example.Hello');

goog.require('goog.dom');

example.Hello = function(opt_word)
{
  this.word_ = opt_word || 'Hello, Closure!';
};

example.Hello.prototype.word_ = null;

example.Hello.prototype.say = function(element)
{
  goog.dom.setTextContent(element, this.word_);
};

この「Hello」クラスは、名前空間「example」に定義され、「文字列を指定可能なコンストラクタ」と「引数に指定した要素へ文字列を設定できるsayメソッド」を持っています。順に見ていきましょう。

goog.provide('example.Hello');

1行目では、後述する依存性管理に加える為の宣言をしています。ここではこのクラス自身を「example.Hello」という名称で依存性の管理に加えていることを表しています。

goog.require('goog.dom');

3行目では、Closure Libraryに用意されているDOM操作ユーティリティ「goog.dom」をこのクラスで利用しますという宣言をしています。前回にも出てきましたね。

example.Hello = function(opt_word)
{
  this.word_ = opt_word || 'Hello, Closure!';
};

5行目からのセクションではクラスの宣言をしています。JavaScriptではクラスを表現するのに関数オブジェクトを使用します。「example.Hello」へ無名関数を代入していますが、この関数はコンストラクタとなるのでインスタンス生成時に行うべき処理を記述します。ここでは「引数が指定されていたらインスタンス変数『word_』にその値を代入し、指定されていなければ"Hello, Closure!"を代入する。」という処理になっています。

example.Hello.prototype.word_ = null;

10行目では、前段で宣言をしたクラスの「prototype」に「word_」を定義することでインスタンス変数を宣言しています。JavaScriptにはJavaのようなアクセス修飾子が用意されていないので、変数名の末尾にアンダースコアを付与する記法によってプライベートであることを示しています。

example.Hello.prototype.say = function(element)
{
  goog.dom.setTextContent(element, this.word_);
};

12行目からのセクションではインスタンスメソッドを宣言しています。引数のHTML要素に対して「goog.dom.setTextContent」メソッドを用いてインスタンス変数「word_」の値を文字列として設定しています。3行目の「goog.require」メソッド呼び出しは、ここで「goog.dom.setTextContent」メソッドを利用する為に必要だったのです。

試しにHelloクラスをJavaで表現すると次のような感じでしょうか。

Hello.java

package example;

import static goog.dom;

public class Hello {
    public Hello(String word) {
        this.word = (word != null) ? word : "Hello, Closure!";
    }

    private String word = null;
	
    public void say(Element element) {
        setTextContent(element, word);
    }
}

それでは、このように定義されたクラスをどのように利用するのでしょうか?
そこでClosure Libraryで用意されている「依存性管理」機能の出番となります。

依存性管理

ファイルの準備

まず、Closure Libraryの依存性管理を利用するにはPythonのランタイムが必要となるので、下記のサイトから最新のPython「2.x.x」系をダウンロードしインストールします。インストールはデフォルトの設定のまま進めていただいて問題ありません。

それでは、新たに「静的 Web プロジェクト」を「closure-example02」として作成し、「WebContent」フォルダ内に「index.html」およびリンクフォルダ「goog」を用意します。作成方法は前回と同様です。

次に、JSファイルを配置するフォルダを用意します。一般的には「scripts」等とするのですが、名前空間「example」にクラス群を定義するので「example」というフォルダを作成します。そしてその中に前述の「Hello.js」を配置します。

すると、JavaやActionScriptのパッケージ管理と似たような趣になるので、クラスファイル数が増加しても、見通しよく整理することができます。実際には、「example.Hello」クラスだからといって「example」フォルダ内の「Hello」クラスである必要はありません。

次に、Webアプリケーションを実行した際、JavaScriptへのエントリーポイントとなる「example.App」クラスを用意します。

「example.App」クラスの内容は以下の通りです。

App.js

goog.provide('example.App');

goog.require('goog.dom');
goog.require('example.Hello');

example.App = function()
{
  var hello = new example.Hello();
  hello.say(goog.dom.getElement('hello'));
};

new example.App();

6行目からのコンストラクタで、「example.Hello」クラスのインスタンスを作成し、idが「hello」の要素を引数としてその「say」メソッドを呼んでいます。

エントリーポイントであるこのクラスは、スクリプトファイルが読み込まれた後にコンストラクタが呼ばれる必要があるため最終行で自身のインスタンス化を行います。

これでJavaScriptのクラス群が用意できました。次にHTMLファイルを編集します。

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

body要素内を見ていきます。

  <h1 id="hello"></h1>

8行目は、テキストを挿入する予定のh1要素です。

  <script type="text/javascript" src="goog/base.js"></script>

9行目では、Closure Libraryを利用する為の基底スクリプトファイルを読み込んでいます。

  <script type="text/javascript" src="deps.js"></script>

10行目では、依存性管理ファイル「deps.js」を読み込んでいます。このファイルは後ほど生成するので現段階では存在していません。

  <script type="text/javascript">
    goog.require('example.App');
  </script>

11行目からのセクションではJavaScriptへのエントリーポイントとなる「example.App」クラスをrequireしています。

以上で、自前で作成するファイル群の準備は整いました。最後に、依存性管理ファイル「deps.js」をClosure Library内にあるPythonスクリプトで生成します。Pythonスクリプトを外部ツールとして実行できるように「外部ツールの構成」を行います。

外部ツールの構成

外部ツールボタン右の「▼」をクリックします。

プルダウンメニューから「外部ツールの構成」を選択します。

「プログラム」を選択し、「新規」ボタンをクリックします。

新規構成を以下のように設定します。

名前

構成の名前を指定します。

closure-example02 依存性管理ファイルの生成

ロケーション

Pythonスクリプトを実行するので、Pythonランタイムを絶対パスで指定します。Pythonのインストール場所を変更した場合はそれに合わせて指定してください。

C:\Python27\python.exe

作業ディレクトリー

ファイルの読み取りや書き出しを行うディレクトリを指定します。ここではプロジェクトフォルダ内のWebContentフォルダを指定します。

${project_loc:closure-example02}/WebContent/

引数

Pythonランタイムの引数を指定します。

${project_loc:closure-library}/closure/bin/build/depswriter.py
--root_with_prefix="example ../example"
--output_file=deps.js

引数の内訳は以下の通りです。

1行目
Pythonスクリプトファイルを指定します。依存性管理ファイルの生成には「depswriter.py」というPythonスクリプトを用います。
2行目「root_with_prefix」
依存性を管理するスクリプトファイルが収められているフォルダを指定します。スペースを挟んで前半はフォルダの名称、後半は「base.js」から見た「example」フォルダの相対パスになります。
3行目「output_file」
依存性管理ファイルの出力先を指定します。

「リフレッシュ」タブをクリックし表示される画面にて「完了時にリソースをリフレッシュ」にチェックを入れます。この設定により、依存性管理ファイルが生成された後にEclipse上のファイルシステムが更新され、プロジェクト内に正しく表示されます。それでは、ここまで完了したら「実行」ボタンをクリックして依存性管理ファイルを生成しましょう。

依存性管理ファイル「deps.js」が「WebContent」内に生成されます。それではindex.htmlを実行してみましょう。

HTMLファイルでは「base.js」と「deps.js」しか読み込んでいないのに、きちんと「Hello, Closure!」が表示されました。「Hello, Closure!」を表示する為に必要な「App.js」と「Hello.js」はどのように読み込まれているのでしょうか?

依存性管理の種明かし

その秘密は、生成した依存性管理ファイル「deps.js」に隠されています。Pythonスクリプト「depswriter.py」は、指定されたフォルダ内を探索、見つけたJavaScriptファイルの「パス」「provideしているクラス名称」「requireしている他クラス名称」を抽出し、依存性管理ファイルに書き出します。その結果が以下の「deps.js」となります。

deps.js

// This file was autogenerated by F:\Users\inage.toru\Workspaces\workspace-e3.7-x64-js\closure-library/closure/bin/build/depswriter.py.
// Please do not edit.
goog.addDependency('../example/App.js', ['example.App'], ['example.Hello', 'goog.dom']);
goog.addDependency('../example/Hello.js', ['example.Hello'], ['goog.dom']);

読み込まれた依存性管理ファイルの情報は「base.js」内で適切に処理され、必要とされるJavaScriptファイルを読み込む為のscript要素としてbody要素に追加されます。

<script type="text/javascript" src="http://localhost:8080/closure-example02/goog/../example/Hello.js"></script>
<script type="text/javascript" src="http://localhost:8080/closure-example02/goog/../example/App.js"></script>

Closure Libraryの依存性管理はこのように働きます。「goog.provide」や「goog.require」に追加や変更があった場合には必ず依存性管理ファイルを再生成する必要がありますが、再生成さえすればJavaScriptファイルが増減しても依存性が適切に管理されて読み込まれる為、JavaやActionScriptのようにJavaScriptファイルをクラスファイルとして管理することが容易になります。

まとめ

Closure Libraryの依存性管理によってクラスファイルの管理が促進されていることがお分かり頂けたでしょうか。
実際には、この依存性管理の状態はアプリケーション開発時の形態であり、サーバへデプロイする際にはClosure Compilerを用いてJavaScriptファイルを統合および最適化することになります。Closure Compilerについても以後の連載で取り上げますのでお楽しみに。