檜山正幸のキマイラ飼育記 このページをアンテナに追加 RSSフィード Twitter

キマイラ・サイトは http://www.chimaira.org/です。
トラックバック/コメントは日付を気にせずにどうぞ。
連絡は hiyama{at}chimaira{dot}org へ。
蒸し返し歓迎!
このブログの更新は、Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama
ところで、アーカイブってけっこう便利ですよ。

2005-09-30 (金)

JS:JSANモジュールシステムを使おう

| 13:35 | JS:JSANモジュールシステムを使おうを含むブックマーク

行きがかり上、JSANモジュールシステムの説明をしておきます。

[追記 dateTime="夕方"]: typoと、本文の趣旨とサンプルコードがずれているところがあったので修正しました。修正の内容は、このエントリーの最後にあります。[/追記]

JSAN(JavaScript Archive Network)は、JavaScriptライブラリ群を集積したアーカイブ・サイトです。そして、JSANがベースとしているモジュール(あるいはパッケージ)システムがJSAN.jsというスクリプトで提供されています。

僕は、モジュールシステムの必要性を強く感じていたので、試しに自作してみたのですが、既に使用されているJSAN.jsがあるので、こっちを使うことに決めました。

この記事と一緒に、次のbrazilさんのエントリーを参照することをお勧めします。

内容:

  1. こんな事例を考える
  2. モジュールとモジュールパス
  3. JSANモジュールシステムを導入しよう
  4. 利用するときの注意事項
  5. モジュール側のお約束
  6. まとめ

●こんな事例を考える

JSANモジュールシステムは、Perlのモジュールシステムの影響を強く受けています。ですから、Perl使いにはお馴染みの仕掛けでしょう。ここでは、Perlの知識を仮定せず、用語/概念も単純化(一部は省略)して説明します。

まずは事例から。Webサーバーに次のようなディレクトリ構造があったとします。

 [.]
  +---mypage.html
  +---myscript.js
  +---[lib]
        +--Util.js
  +---[Search]
        +--Fast.js

mypage.htmlでmyscript.jsを使います。つまり:

 <script src="myscript.js"></script>

という記述が、mypage.htmlに必要ですね。

さらに、myscript.jsがSearch/Fast.jsを必要として、Search/Fast.jsがlib/Util.jsを必要としているとしましょう。すべてのJavaScriptプログラムをキチンと揃えるために、次のタグを並べる必要があります。

 <script src="lib/Util.js"></script>
 <script src="Search/Fast.js"></script>
 <script src="myscript.js"></script>

別にどうってことない? いやいや、".js"ファイル数が増えたり、プログラム(スクリプト)の依存関係が複雑になると、「キチンとscriptタグを並べる」管理作業は悪夢になり得ます。

●モジュールとモジュールパス

他のプログラムから使われることを意図したJavaScriptソースファイルをモジュールと呼びます。わかりやすいように、またJSANの習慣に従い、モジュールのファイル名は大文字からはじめることにします(メカニズム上は、命名は自由だが)。

モジュールの、“ある特定のディレクトリ”を基準とした相対パスをモジュールパスといいます。例えば、先の図の[.]を基準ディレクトリとするなら、Search/Fast.js はモジュールパスです。

では、lib/Util.js もモジュールパスでしょうか? (JSAN.jsの通常の使用法では)違います。./lib/Util.jsファイルのモジュールパスはUitl.jsです。なぜかというと、基準ディレクトリが前もって複数設定されているからです。デフォルトでは、"." と "lib"が基準ディレクトリです。

 // JSANソースコード抜粋
 JSAN.includePath   = ['.', 'lib']; //必要なら変更する

このため、Util.jsは、./Util.js → ./lib/Uitl.js の順で探すことになるので、Util.jsをモジュールパスとしてもOKなのです。ちなみに、Search/Fast.js は、./Search/Fast.js → ./lib/Search/Fast.js と探すので、libの下に置いてもかまいません。

JSANモジュールシステムを導入しよう

モジュールパスの概念はわかりましたか? モジュールパスから拡張子「.js」を取り除いて、「/」を「.」に置き換えた名前をモジュール名と呼びます。例えば、Search.Fastはモジュール名です。

JSANの習慣では、モジュール名+バージョンがモジュールを特定するグローバル識別子として使われます。配布ファイルの命名は、Search.Fast-0.21.tar.gz またはSearch-Fast-0.21.tar.gz です。

さて、JSANとDebug.Logger(実在のモジュール)を次のように配置したと仮定して話を進めます。

[..]
 +-- [jsan]
      +---JSAN.js
      +---[Debug]
            +---Logger.js
 +-- [.]
      +---mypage.html
      +---myscript.js
      +---[lib]
            +--Util.js
      +---[Search]
            +--Fast.js

こうすると、mypage.html内の記述は次のようになります。

 <script src="../jsan/JSAN.js"></script>
 <script src="myscript.js"></script>

他のモジュールへの依存関係は、myscript.js内に次のように書きます。必要なモジュールUtilは、Search.Fastからロードされます。

/* myscript.js */
 JSAN.use("Search.Fast");

使うモジュールを変更するときも、もはやHTMLファイルを修正する必要はありません。myscript.jsを変更するだけです。例えば:

/* myscript.js */
 JSAN.use("Search.Fast");
 JSAN.use("Debug.Logger");

アレレッ? Debug.Loggerもちゃんとロードできるの? 大丈夫です。JSAN.jsが存在するディレクトリ(この例では ../jsan/)は自動的にJSAN.includePathに追加されます。つまり、Debug.Loggerのファイルは次の順で検索されます。

  1. ./Debug/Logger.js
  2. ./lib/Debug/Logger.js
  3. ../jsan/Debug/Logger.js

●利用するときの注意事項

ほとんどの言語では、include, import, use, require, consult, loadなどのキーワードにより、他のモジュールの使用/取り込みを指示できます。JSAN.jsは、JavaScriptでもモジュールの取り込み機能を利用できるようにしたものです。

ただし、JSAN.useは宣言ではなくて実行文になります。失敗することもあるので、次のように書くのが無難でしょう。(デバッグ中はthrowではなくて、alertやprintで表示したほうがみやすいね。)

/* myscript.js */
 try {
  JSAN.use("Search.Fast");
 } catch(e) {
  throw new Error("This script requires JSAN.js" +
                  " and modules: Uitl" +
                  " (" + e.message + ")" );
 }

宣言ではないので、ソースの先頭ではなくても任意の場所で(たとえループのなかでも)JSAN.useを使用できます。

JSAN.use以外にJSAN.requireもありますが、これについてはbrazilさんの記事などを参考にしてください。

●モジュール側のお約束

モジュールのファイルは正しい場所(モジュールパス)に置かなくてはなりませんが、書き方に若干のお約束(コンベンション)があります。Search.Fast-0.21(これ、架空のモジュールだけど)なら次のような感じです。

/* Search/Fast.js (Search.Fast-0.21)
 */

try {
 JSAN.use("Util");
} catch(e) {
  throw new Error("Search.Fast requires JSAN to be loaded");
}

// 親の名前空間オブジェクトSearchがなければ作る
if (typeof Search == 'undefined') {
 Search = {};
}

// 当該の名前空間オブジェクトを生成する
Search.Fast = {};

/* Search.Fastがクラス(もどき)なら、
  Search.Fast = function(mayHaveArgs) { コンストラクタコード }
 のようになる。
*/

// モジュールのメタ情報:
// それほど律儀に書かなくてもいいが、VERSIONは入れよう。
Search.Fast.NAME = 'Search.Fast';
Search.Fast.VERSION = '0.21';
Search.Fast.EXPORT = ['searchFast'];
Search.Fast.EXPORT_OK = ['searchOptions'];
Search.Fast.EXPORT_TAGS = {
    ':all': (Search.Fast.EXPORT).concat(Search.Fast.EXPORT_OK),
    ':common': Search.Fast.EXPORT
};

EXPORT配列に並べた名前(記号)は、JSAN.useが自動的に大域スコープに登録します。したがって、Search.Fast.searchFastは単にsearchFastとしてもアクセスできます。EXPORT_OK配列のなかの名前は要求があれば大域スコープに登録可能です。EXPORT_TAGSは、大域スコープへの登録(export)を便利にする仕掛けです。

これ以上のことは、直接 http://openjsan.org/ にあたってみてください。

●まとめ

  1. JSANは、JavaScriptライブラリのアーカイブサイトである。
  2. JSANのモジュールシステムがJSAN.jsとして公開されている。
  3. モジュールとは、他のスクリプトから使えるJavaScriptソースファイルである。
  4. モジュールパスとは、モジュールファイルの基準ディレクトリからの相対パスのこと。
  5. モジュール名は、モジュールパスから拡張子を除き、「/」(パスセパレータ)を「.」に置き換えた名前。
  6. JSAN.useとJSAN.requireは、複数の基準ディレクトリから、モジュール名で指定したモジュールを探してロードしてくれる。
  7. モジュールを書く際に、若干のお約束ごとがある。
  8. JSANモジュールシステムは便利だから、みんな使おう。

[追記 dateTime="夕方"]: 修正した内容:

まず、Farstのrはいらない、Fastですね。

myscript.jsサンプルコードに、JSAN.use("Util") ってのがあったのですが、UtilはSearch.Fastが使うので、myscript.jsではなくて、Search/Fast.js内でJSAN.use("Util")を行うべきです。そのように直しました。[/追記]

brazilbrazil 2005/10/01 04:28 JSAN.globalScopeのすり替えは行なっていますか?Rhinoなどで動かす場合は、ここはポイントになると思います。またglobalScopeを細かく変更しながらuseしていくことで、関数がグローバルスコープへ直接ロードされるのを防げます。globalScopeに、クラスのprototypeを設定してuseすることで、クラスのミックスができると思います。requireして、手動で関数を付け替えて行けばいいんですが...。

m-hiyamam-hiyama 2005/10/01 15:37 > JSAN.globalScopeのすり替えは行なっていますか?
> Rhinoなどで動かす場合は、ここはポイントになると思います。
もとはglobalScopeにselfを設定してますよね。当初はこれを手直ししていました。が、JSAN.js側に一切手を加えないことにしました。したがって、selfとかwindowとかはRhino側で定義します。で、window.documentも必要なので、MiniDOMを作った次第。

>またglobalScopeを細かく変更しながらuseしていくことで、関数がグローバルスコープへ直接ロードされるのを防げます。
確かにそうですね。でも今回は、Rhino上でなるべくブラウザに近い環境を準備したいので、JSAN.globalScopeは本物のglobalを指していている状況にしようかと。

> globalScopeに、クラスのprototypeを設定してuseすることで、クラスのミックスができると思います。
最近、ごく一部で流行(?)のmixin風構成ですかね。
mixinやfeature-injection、delegationはもちろん、closureもcontinuation(これは一部の実装限定)、higher-order techniqueも使えるから、なんかもう、やりたい放題ですな。