新参の超軽量JavaScript非同期ローダー3種を徹底比較

JavaScriptの読み込み・実行は、それより下にある画像など外部リソースの読み込みと表示をブロックするため、「ページ読み込み時間を短縮するJavaScriptローダー:LABjs」 や 「CSS/JavaScriptのAsynchronous Loadingをめぐる熱い論議」 で紹介したように、多数の JavaScript ローダーが開発されてきました。
これらのローダーは、「非同期スニペットのスタート ガイド – Google Analytics」 に代表される、script 要素を直接 DOM に挿入する方法が主流となっています。またそのタイプは、単一のスクリプト・ファイルを対象とした小規模なものと、Head JS、LABjs、ControlJS などに代表される、複数ファイルの依存関係をハンドリングする機能をもつものに大別されるかと思います。
後者のタイプは最小でも3KBytes以上のサイズがありましたが、今回紹介する新ローダー3種は、同様の機能を持ちながらも、そのサイズが高々1〜2KBytes程度という、超軽量のローダーです。
ただし実際に使ってみると、ちょっとしたクセがあることが分かりました。
そこで本エントリーでは、これらを徹底的に比較してみた結果を報告したいと思います。
$script.js
| サイズ | 1.34KB(コメントなし gzip 時 673bytes) |
|---|---|
| ライセンス | MIT License or Creative Commons Attribution 3.0 |
LABjs は、自分自身を読み込むための簡易ローダー を紹介しています。$script.js は、この簡易ローダーをベースにしており、性能は LABjs ほどではないが、そこそこである (LABjs はさらに XHR を駆使している) とのことです。まぁ、何をもって性能というか分かりませんが、DOM 挿入を原理とする、今回の3種すべてに当てはまることでしょう。
$script.js の特徴は、複雑な依存関係を見通し良く記述するために、スクリプト・ファイルにユニークな名前を結びつけて付けてインターフェースとする、という点です。この名前が使えるメソッドとしては、読み込み完了時にコールバック関数を実行する ready だけなのですが、実に強力かつ多彩な記述が可能となっています。以下に使い方の例を示します。
スクリプトを非同期に読み込み、実行する
$script('script.js', function() { myscript(); });
2つのスクリプトを非同期・並列に読み込み、揃ったところで実行する
$script(['script1.js', 'script2.js'], function() { myscript(); });
あるいは
$script('script1.js', 'script1');
$script('script2.js', 'script2');
$sccript.ready(['script1', 'script2'], function() { myscript(); });
あるいは
$script(['script1.js', 'script2.js'], 'all');
$script.ready('all', function() { myscript(); });
2つのスクリプトを直列に読み込み、実行する
$script('script1.js', function() {
$script('script2.js', function() { myscript(); });
});
依存関係が管理しきれない大量のスクリプトを読み込む
ちょっと乱暴ですが、途中で … is not defined
のエラーが発生しても、何とか最後まで行く(ハズの)スクリプトです。
var dependencyList = {
jquery: 'jquery.js',
plugin: 'plugin.js',
others: ['script1.js', 'script2.js']
};
$script('jquery.js', 'jquery');
$script('plugin.js', 'plugin');
$script.ready(['jquery', 'plugin', 'others'],
// 読み込みが全て完了すると、実行される
function() { myscript(); },
// 依存関係が未解決だと、エラーが発生し、ここで読み込まれる
function(depsNotFound) {
depsNotFound.forEach(function(dep) {
$script(dependencyList[dep], dep);
});
}
);
上の例に出てくる forEach() というメソッドは、ECMA-262 標準の拡張であるため、サポートされていないブラウザ (例えば IE8) があります。そのため、次のスクリプト (Firefox および SpiderMonkey で使われているアルゴリズムと全く同じだそう) を入れておく必要があります。
if (!Array.prototype.forEach) {
Array.prototype.forEach = function(fun /*, thisp*/) {
var len = this.length;
if (typeof fun != "function")
throw new TypeError();
var thisp = arguments[1];
for (var i = 0; i < len; i++) {
if (i in this)
fun.call(thisp, this[i], i, this);
}
};
}
また他にはない特徴として、DOM 構築完了を示す domReady メソッドがサポートされています。ちょっとだけ嬉しい場合もあるでしょう。
参考情報
load.js
| サイズ | 2.00KB(コメントなし gzip 時 896bytes) |
|---|---|
| ライセンス | MIT License |
JavaScript 関数を非同期(並列)に実行させたり同期(直列)に実行させたり、といったことをコントロールするフレームワーク chain.js に、DOM 挿入法を組み合わせたローダーです。JavaScript 関数の非同期・並列実行といってもブラウザでは並列実行が出来ないので、おそらくサーバーサイド JavaScript 用のライブラリを流用なのでしょうか? (サーバーサイド JavaScript は詳しくないので、間違えていたらごめんなさい)。今のところ load.js 専用ですが、こういうエレガントな作り方、私は好きです (オブジェクト指向で実装してあれば、もっとなんですけどネ)。
さて、フレームワークといってもそのサイズは極小で、ローダーと組み合わせても2KB程度に収まっています。また読み込み失敗に対するエラー処理もちゃんと実装されています。
各メソッドが自分自身を表すオブジェクトを返すので、メソッドをチェーンのようにつなげることが出来ます。このあたりは Labjs 同様の非常に分かりやすいインターフェースとなっています。
準備されているメソッドは load、run、then (1つ前のメソッドと同じメソッドを実行する)、defer (ディレイを入れる)、onError です。また可読性を上げるため、thenLoad (load のエイリアス) や thenRun (run のエイリアス) といったものも用意されています。
スクリプトを非同期に読み込み、実行する
load('script.js').thenRun(function() { myscript(); });
2つのスクリプトを非同期・並列に読み込み、揃ったところで実行する
load('script1.js', 'script2.js').thenRun(function() { myscript(); });
2つのスクリプトを直列に読み込み、実行する
load('script1.js').thenLoad('script2.js').thenRun(function() { myscript(); });
0.5秒後にスクリプトを読み込む
defer(500).thenLoad('script.js');
エラーを処理する
load('script.js').onError(function (err) {
//Handle any errors here..
});
NBL.js 2.0
| サイズ | 0.95KB(コメントなし gzip 時 585bytes) |
|---|---|
| ライセンス | MIT License |
HTML5 のデータ属性を用いているため、<script> タグの属性内で全てを指定し、1行で記述できるという特徴があります。エラーを含むタイムアウトの設定も出来ます。また nbl.plus.js を使えば、CSS や画像ファイルも全く同様に扱えるという、他にはない特徴も持っています。
ただし前2つのローダーが、同期(直列)/非同期(並列)のシンプルなインターフェースを提供し、それらの組み合わせで複雑な依存関係に対応するというコンセプトに対し、NBL.js は次のような規則で動作します。
- 複数のスクリプトが指定された場合、それらは並列に読み込まれる
- 複数のスクリプトが配列で指定された場合、最初のスクリプトを読み込んだ後、残りが並列に読み込まれる
- コールバック関数は、各スクリプトの読み込み完了後に起動される
NBL.js のユースケースとして、最初のスクリプトが jquery、残りがそのプラグインやアプリケーションという想定だと思いますが、「スクリプトが全て読み込み完了したらコールバック関数を起動」 という記述が出来ません。
以下の例は、DOCTYPE として HTML5 を想定しています (type="text/javascript" が省略可)。
2つのスクリプトを非同期・並列に読み込み、最初のスクリプトの読み込みが完了したら実行する
<script src="nbl.js" data-nbl="[
'script1.js', 'script2.js', function() { myscript(); }
]"></script>
2つのスクリプトを直列に読み込み、最初のスクリプトの読み込みが完了したら実行する
<script src="nbl.js" data-nbl="[
['script1.js', ['script2.js']], function() { myscript(); }
]"></script>
上の2つの例で、myscript() が script2.js を参照している場合、undefined のエラーが発生しますが、タイムアウト後 (デフォルト2500msec) に再び最初のコールバック関数が起動されるため、運が良ければ動きます
。
ちゃんと動かすには、NBL.js を単なるローダーと割り切り、起動は例えば次のようにするしかないでしょう。
<script>
window.onload = function() { myscript(); }
</script>
まとめ
私は記事に JavaScript のデモを埋め込む事がちょくちょくあるので、この種のローダーにはこだわっています。結果、現時点ではサイズと機能のバランスの良い $script.js を採用しています。例えば 「jQueryで被災前後の衛星写真を比較」 では、次のようにして jQuery の衝突を避けるため、jQuery オブジェクトのあり/なしを判定して、動的に読み込むようにしています。スクリプト・ファイルを配列で指定できるので、スッキリとしたコードになるんだと思います。
var s = ["/blog/slow/wp-content/uploads/beforeafter/beforeafter.js"];
if (typeof jQuery == "undefined") {
s.unshift("http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js");
}
$script(s, function() { beforeafter() });
また実は、今回紹介したうちの後者2つは、LABjs 作者の gist.github で作者自身が注目株としてピックアップしていたものです。いずれのローダーも、ここ1〜2ヶ月で頻繁に更新されているので、スタイルシートも読み込める NBL.js のインターフェースがもっとこなれてくれることを期待、という感じですネ。
@tokkonoPapa
こちら に各ローダーの詳細な比較がありました。参考にどうぞ。
yepnope.js というのも出て来ましたネ。サイズは3.3kb(圧縮後で1.6kb)で、CSSも行けるとの事。 を評価した結果が、真(yep)か偽(nope)によって、条件付きローディングが出来るようです。例えば、ローディングに失敗したら、url を変えるとか、未対応の機能あれば追加でローディングするとか。Modernizr 2.0(現時点ではBeta版)で採用される模様。
に計19個のローダーがピックアップされています。今回取り上げた3つは、それらの中でもサイズが最も小さい部類に入りますネ。コメントには「全部をテストしたけど、HeadJS が最も良かった」という意見が書き込まれていますが、果たして…