2006-11-04

JavaScript で外部ライブラリの load

分かっている人には今さらなネタです。自分用に整理してみました。

Ecmascript そのものには外部ファイルを読み込む機能はない。ホスト環境の機能に依存する。ブラウザで動く JavaScript は HTML を解釈するブラウザの機能を利用し、

  1. <script> 要素を書き出す
  2. XMLHttpRequest

のどちらかの方法で外部ファイルを load する。(HTML の中に script 要素を予め書いておく方法は何も難しいことはないのですっぱり割愛 。)

※ ここに書いたものよりもう少し詳しい内容を「JavaScript の動的ロードで(結果的に)Classic Mac を除外する」、「XMLHttpRequest を使ったスクリプトのロードは意外と面倒」に書きました。よければそちらも読んでいただければと思います。

1. の方法の場合、どのドメインにある JavaScript でも load できる。この方法は外部のドメインにあるデータの読み込みによく使われるが、HTML の読み込みが完了した文章からこれを実行してもスクリプトの load がいつ完了したか知る方法がない。1また、他のドメインに置いてあるスクリプトは常に読み取れるとは限らないので自分のページのロード時間が外部の要因に左右されることになる。2

2. の方法の場合は XMLHttpRequest の特性上読み込めるスクリプトが同一ドメインのものに限定されるが、スクリプトのロード完了をイベントで知ることができるし、1 の場合よりユーザーを不用意に待たせてしまう可能性も低くなるだろう。

XMLHttpRequest を備えているブラウザしか相手にしないのであればこの方法は安全だし分かりやすいのでオススメだと思う。

以下、注意事項がいろいろある 1 の場合について。

user javascript での load、あるいはユーザーのアクションによる load

この場合、すでに HTML 文書そのものは load し終わっているので、スクリプトの load の完了は window.onload イベントでは補足できない。したがって何らかの手を加える必要がある。

具体的には 最速 ma.la 方式に手を加えるのが恐らくいちばん楽。

cf. JSAN DEMO

function onloaded( objname, func, thisobj ) {
  var obj = undefined;
  try {
    eval( "obj = " + objname );
  } catch ( e ) {
    ;
  }
  if ( typeof obj != 'undefined' ) {
    func.call( thisobj );
  } else {
    setTimeout( function() {
                  onloaded( objname, func, thisobj );
               }, 100 );
  }
}

一応オブジェクトを作っていても動きますよバージョン3。呼び出しは

onloaded( ObjectName, function() {
                        function();
                      }, this );

という形になる。4

ただしこれは読めば分かるが終了条件が危うい。目的とするオブジェクトが永遠に生成されない場合5は onloaded の処理は終了しない。

ということで本当はこれは

  • 外部ライブラリの load などをまかなうオブジェクトの中に綴じ込め、その中のメンバ変数とかを頼りに再帰回数を制限しておく
  • 外部ライブラリの名前はフルのファイル名ではなくオブジェクト名を与えると
ObjeactName + ".js"

に展開されてロードされるとか、命名規則を作っておく

などの工夫をした方がよいだろう。

そうすればすでに読み込み済みのライブラリを多重 load してしまうこともない。なんか書いてるうちに段々 DoJo や JSAN に似てくるな。

JSAN はこの bootstrap の部分だけ利用するのもアリかもしれない。Perl 界隈向けっぽいがおいしいところは利用すべきかな、と。

GreaseMonkey

どうも名前空間と言うか、セキュリティを考えて何か細工が入っているっぽい。(全然ちゃんと調べてない。)Module か greasemonkeyService の中で実行されるのかな? <script>要素書き出し方式で読み込んだライブラリが GreaseMonkey 上では動かずに Firebug 上では動くので恐らくそう。

  • gm script の中で script 要素を書き出してスクリプトを読む場合は unsafeWindow を利用するとよさげ
  • gmXHR を利用すると unsafeWindow は不要っぽい

上に書いたモジュールの load などを行うものをオブジェクトとして作っておいて、実行環境を判別して gm と通常のページ、bookmarklet などの環境を気にせずに load() が呼べたりするとステキっぽい。

参考

JSAN

JSAN も JSModule も中で XMLHttpRequest でスクリプトを取得するようになってるのね。Array#push も使ってるし、MacIE は無視、っと。

※ まぁ今さら MacIE だけ特別に気を使わなきゃいけないのもどうかとは思うんだけど。MacIE も MacOS 9 もとっくの昔にメーカーがサポート打ち切ってるんだから。どうしてもという人は本当に真剣に iCab の支援をすべきだろう。あれが唯一の希望なんだから。

  1. Firefox の場合は、文書の読み込みと同時に実行すれば window.onload の瞬間にはすべてのスクリプトが揃っているので難しいことを考える必要はないっぽい。localhost でわざと setTimeout() で時間を遅らせて load してみたけど大丈夫だった。でも他のブラウザは全滅なんだよな。あと iframe 使えばその辺の問題は回避できるのかもしれないけど、試してない。iframe 好きじゃないしなぁ。 

  2. 404 の場合はまだしも、外部ドメインのサーバが落ちているなどの場合はその判断が確定されるまでの間、ブラウザが待たされてしまう。 

  3. ただし MacIE では Function#call メソッドがないので動かない。 

  4. もとの wait() って関数名が、どうも待ったあとに関数を実行するイメージが湧かないので名前は変えてあります。 

  5. オブジェクトの名前を間違って書いてしまったとか呼び出し先のサーバが落ちている場合、もっと言えばオブジェクトの構成が変わってしまった場合なんかもそう。 

About

例によって個人のなんちゃらです