2010年10月6日

非同期タイプの埋め込みタグ

少し前に GoogleAnalytics でのアクセス集計用のタグとして </head> の直前に挿入するタイプの非同期型埋め込みタグが導入された。
それまでのタグは </body> の直前に挿入してページの読み込みをブロッキングしないようにしましょう、という方針だったものの、それなりにいるはずの「開いてみたけど読み込み遅いからやっぱ止めよう」みたいな諦めるのが早いユーザを知るためには若干遅いのではないか、という手法だった。

新しい方法では外部 JavaScript ファイルを読むための script タグを直接 HTML には書かずに JavaScript から挿入するように変わった。この読み込み方法はブロッキングしないように読み込む方法として最近ではわりと有名な方法だとは思うが、読み込んだファイルが持つメソッドを呼び出す方法が面白くていいなあと思った。
  var _gaq = _gaq || [];
  _gaq.push(['_setAccount', 'アカウントID']);
  _gaq.push(['_trackPageview']);

  (function() {
    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
実際の内部での動作を見たわけではないので推測だけど、_gaq が未定義だったらまず配列として初期化して、メソッド呼び出しの代わりにメソッド名とパラメータを配列で渡す。
その後で script タグを動的に生成して、オブジェクトの準備ができ次第 _gaq は push() メソッドを持つ独自のオブジェクトに摩り替わって、今までの _gaq に溜まっていた配列のデータをメソッドを呼び出して消化する。
オブジェクトの準備が整ってからは _gaq.push() が呼ばれるとそのままメソッド呼び出しに変換される、という仕組みになっているはず。
つまり読み込みが完了するまでの間は _gaq はシンプルなキューになっていて、script タグの生成と読み込み完了を待たなくてもメソッド呼び出しが貯めこんでいける。
async = true とか、appendChild じゃなくて insertBefore とかもさり気ない技だけど今回は省く。

自分でも外部の JavaScript ファイルを動的に読むために script タグを JavaScript から挿入する手法で実現したことがある。ただ、読み込み完了を待つために script タグの onload イベントとか onreadystatechange を拾ったりしててブラウザ間の互換性が心配だなあと思っていたりはしていた(一般的なサイトで使うわけではなかったので真面目にチェックしなかったけど、一般的なライブラリでもその方法で読み込んでいることがあるので多分大丈夫だったんだろうとは思う)。
setInterval で読み込み完了したのか待つのも定期的に無駄な処理が入るわけで美しくないし、複雑な動作をさせようと思うと読み込み完了するまでの間の呼び出しを正しい順番で覚えておかないと厄介な目に合うので何かもっと綺麗な解決策がないかなあとモヤモヤとしていたところでこの方法を知ったのでウホウホ付いていくことに。

最近はブックマークレットを実行するためのコードとしてこれを利用するようになりました。
ブートストラップとしてほぼ同じ形でブックマークレットのメイン処理を行うスクリプトを呼び出して、その中で Google の CDN にある jQuery を読み込んだりとか、ブックマークレットが行ないたい処理が実際に書いてあるスクリプトファイルを読み込んだりとか。
以前はブックマークレット呼び出しを連打すると読み込み中のスクリプトがあるせいで処理の順序がおかしくなってぶっ壊れることがしばしばあったものの、その辺の問題も解決。

「連打するな」で解決する問題だったので黙っていたものの、こういう問題が解決するのは気持ちイイ。
Clip to Evernote