JavaScriptで外部ライブラリを読み込むためのスクリプトをCodeRepos.orgに上げた。

Sjaxを使わないJavaScript Loader - ヒルズで働く@robarioの技ログJavaScriptから外部JavaScriptを読み込む方法 - ヒルズで働く@robarioの技ログ の改良版です。この二つの記事は忘れてもらって結構です。

前振りとか

ライブラリを読み込みたい(><)
「script要素をappendChildする」でやると読み込み完了を待ってくれないので「setTimeoutで3000ms後に本処理」とか、嫌だ。
そこで「指定したプロパティが存在するか」を監視するスクリプトloader.js.incを書きました。http://coderepos.org/share/に置いてあります。

http://coderepos.org/share/browser/lang/javascript/misc/loader.js.inc
http://coderepos.org/share/browser/lang/javascript/misc/loader-min.js.inc(loader.js.incの圧縮版)

ライブラリ読み込み方あれこれ

  • <script>タグをHTMLに書く
  • <script>タグをdocument.writeする
    • ×documentがcloseされていると使えないので、onloadイベント以降やブックマークレットでは使えない
    • ×UIスレッドが停止する
  • script要素をappendChildする
    • ×読み込み完了したタイミングを検出できない
  • Ajaxで読み込んでeval
    • ×外部ドメインのライブラリが読み込めない
    • △(工夫次第で)後続スクリプトは、確実に読み込み完了していることを期待して良い
  • Sjaxで読み込んでeval
    • ×外部ドメインのライブラリが読み込めない
    • ×UIスレッドが停止する

それらの欠点を踏まえて作ったのがloader.js.inc。

  • loader.js.inc
    • ○onloadイベント以降でもブックマークレットでも使える。
    • ○外部ドメインのライブラリが読み込める
    • △(多少のタイムラグはあるが)後続スクリプトは、確実に読み込み完了していることを期待して良い
    • ○UIスレッドは停止しない

使い方とか

(function(){    //
...             // ローダ(loader.js.incもしくはloader-min.js.incを挿入)
})              //

([     //
...    // ライブラリ(記述方法は後述)
])     //

(function() {    //
...              // コールバック
})               //
();    // ←コールバックへの引数
  • 末尾はブックマークレットなどで良く使われるイディオム(function(){})();の形式になります。つまり、「元のスクリプトを一切変更せずに、直前にコードを挿入するだけで良い」のがポイントです。ブックマークレットでも簡単に外部ライブラリを利用できるようになります。
  • 『コールバック』および『コールバックへの引数』は省略可能です。省略した場合、ライブラリの読み込みのみが行なわれます。
『ライブラリ』の記述方法

例えばjQueryを読み込む場合、以下のように書きます。

(function(){    //
...             // ローダ(loader.js.incもしくはloader-min.js.incを挿入)
})              //
([
     {'jQuery':'http://example.com/js/jquery.js'}
])
(function() {
    alert(jQuery);
})();

こうすると
1. jquery.js を読み込む
2. typeof jQuery が 'undefined' でなくなるまで待つ
3. コールバックを呼び出す。
という順番で処理が進みます。コールバックが呼ばれた時点で、確実に jQuery が定義されていることになります。


次に、 jQuery.iUtil を読み込んでみます。 jQuery.iUtil は jQuery に依存しているので、以下のように書きます。

([
     {'jQuery':'http://example.com/js/jquery.js'},
     {'jQuery.iUtil':'http://example.com/js/iutil.js'}
])

こうすると
1. jquery.js を読み込む
2. typeof jQuery が 'undefined' でなくなるまで待つ
3. iutil.js を読み込む
4. typeof jQuery.iUtil が 'undefined' でなくなるまで待つ
という順番で処理が進みます。iutil.js を読み込む際には確実に jQuery が定義されていることになります。


次に、jQuery.iDrag と jQuery.iDrop も読み込んでみます。
これら2つは jQuery にのみ依存しています。互いに依存しておらず、jQuery.iUtil にも依存していません。
つまり、jQueryを除く3つは並列に読み込んでも問題無いということです。
この場合は以下のように書きます。

([
     {'jQuery':'http://example.com/js/jquery.js'},
     {'jQuery.iUtil':'http://example.com/js/iutil.js',
      'jQuery.iDrag':'http://example.com/js/idrag.js',
      'jQuery.iDrop':'http://example.com/js/idrop.js'}
])

すると
1. jquery.js を読み込む
2. typeof jQuery が 'undefined' でなくなるまで待つ
3. iutil.js, idrag.js, idrop.js を*同時に*読み込む
4. typeof jQuery.iUtil, typeof jQuery.iDrag, typeof jQuery.iDrop の*全て*が 'undefined' でなくなるまで待つ
という順番で処理が進みます。

jQuery.iSort は、 jQuery.iUtil, jQuery.iDrag, jQuery.iDrop の3つに依存してるため、配列の次の要素として以下のように書きます。

([
     {'jQuery':'http://example.com/js/jquery.js'},
     {'jQuery.iUtil':'http://example.com/js/iutil.js',
      'jQuery.iDrag':'http://example.com/js/idrag.js',
      'jQuery.iDrop':'http://example.com/js/idrop.js'},
     {'jQuery.iSort':'http://example.com/js/isortables.js'}
])

isortables.js の読み込みは、 jQuery.iUtil, jQuery.iDrag, jQuery.iDrop が全て定義された後に始まります。



プロパティの定義を行なわないスクリプトは、連想配列の代わりに文字列でURIのみを書きます。

([
     {'jQuery':'http://example.com/js/jquery.js'},
     {'jQuery.iUtil':'http://example.com/js/iutil.js',
      'jQuery.iDrag':'http://example.com/js/idrag.js',
      'jQuery.iDrop':'http://example.com/js/idrop.js'},
     {'jQuery.iSort':'http://example.com/js/isortables.js'},
     'http://example.com/main1.js',
     'http://example.com/main2.js'
])

読み込み完了を待つ術が無いため、 main2.js は main1.js の読み込み如何に関わらず読み込みを開始してしまいます。
つまり、main1.js と main2.js は同時に読み込まれます。



なお「プロパティの定義を行なわない」のに「読み込み完了を待ちたい」というスクリプトには対応していません。
(チェック方法を定義できるようにしたらいけるかも?プロパティ名を指定するところにfunctionを渡せるようにするとか)

その他

  • Firefox2とIE6で動作を確認しています。
  • 一切グローバルを汚染しません。
  • 以前とチェック方法が変わったのは、jQuery.iUtilなどをチェックする場合にwindow['jQuery.iUtil']になってしまって上手く行かなかったから。
  • 以前の記事を読むと「IEでコンテキストがwindowじゃない時、評価をしてくれないからsetTimeoutしなきゃいけない」と書いてあるけど、ホントかどうか良く分からんからやってない。
  • 名前が決まりません。
  • 本記事中に出てきたように、プロパティ名を指定するところにfunctionを渡せるようにするとかやってみるかも。
  • Perlか何かで、ローダのアップデートや、指定ライブラリの自動挿入・除去をするスクリプトを作りたい。
  • 変なことしてる人、
window['42'] = 'the Answer to (the Great Question of ) life the universe and everything';

なんていうスクリプトを読み込みたい場合、

{'42':'http://example.com/js/42.js'}

と書くと

typeof 42 == 'number'

になってしまって読み込みを開始してくれないので、

{'window["42"]':'http://example.com/js/42.js'}

などとしてください。