読み込むライブラリファイルに依存コードを同梱する方法
追記:w3cのSCRIPT要素の仕様にみあたら無いということで下記の方法を試したのですが、よく考えたら scriptElement.onload で読み込み終了を確認できるのではないかという事で、次はそれを試します。ただ、同梱方式のメリットは有効かなと思います。
前回は、script要素にasync属性を使い、例えばjqueryなどのライブラリ読み込んだ時の発火タイミングの難しさについて書きました。
async属性は、文字通り非同期な読み込みとなり、ブラウザの描画をロックしないので、ページ読み込み速度が大幅に上がります。
このメリットは読み込み速度で秒を争うWebサイトでは小さくないといえます。
しかし、その読み込みのタイミングがずれると、もしjqueryならそれに依存する例えば$などの付いたコードが全てエラーになるという、極めて不安定な実装ができてしまいます。
そこで一つの対策を考えてみました。
対策
今回やってみるのは、読み込むライブラリファイルの中に、そのライブラリに依存するコードを書いてしまう(同梱する)というものです。
asyncにより、ライブラリの読み込みは非同期ですが、そのライブラリファイル内に同梱されたコードは、通常のコードのように逐次的に読み込まれ通常の順番で同期的に実行されるからです。
注意点はこれだけです。
・ライブラリ読み込み後自動起動するコードは必ずファイルに同梱する
ボタンをクリックしてから発火するような、DOMエレメント構築後のコードなどは、同梱しても良いし、ライブラリファイル内に限らずどこに配置しても良いでしょう。(全部まとめると管理時の切り分けが判りやすくなるかも?)
今回は、ライブラリファイル内にそのライブラリの依存コードを配して、その発火タイミングを確認してみます。
コード
main.html
<script>
let i=0
</script>
<script async src="js/jquery-3.6.1.min_some.js"></script>
<script>
// DOMContentLoaded
document.addEventListener('DOMContentLoaded', function() {
console.log(++i,': DOMContentLoaded')
});
// window.onload
window.onload = function() {
console.log(++i,': window.onload ')
}
// jqueryの $(function(){})
$(function(){
console.log(++i,': $(function(){})')
})
</script>
jquery-3.6.1.min_some.js
jqueryファイルの最後にコードを追記します。
//省略
:
:
&define.amd&&define("jquery",[],function(){return S});var Yt=C.jQuery,Qt=C.$;return S.noConflict=function(e){return C.$===S&&(C.$=Qt),e&&C.jQuery===S&&(C.jQuery=Yt),S},"undefined"==typeof e&&(C.jQuery=C.$=S),S});
// msg of at the jquery file end
console.log(++i,':(in jqf) at the jquery file end')
// DOMContentLoaded
document.addEventListener('DOMContentLoaded', function() {
console.log(++i,':(in jqf) DOMContentLoaded')
});
// window.onload
window.onload = function() {
console.log(++i,':(in jqf) window.onload ')
}
// jqueryの $(function(){})
$(function(){
console.log(++i,':(in jqf) $(function(){})')
})
結果
Uncaught ReferenceError: $ is not defined
1 ':(in jqf) at the jquery file end'
2 ': DOMContentLoaded'
3 ':(in jqf) DOMContentLoaded'
4 ':(in jqf) window.onload '
5 ':(in jqf) $(function(){})'
まず最初にmain.html内で読み込まれた$(function(){})がエラーを起こします。これは前回と同じで、非同期読み込みのjqueryが読み込まれる前ですから当然で予想通りです。
次に、1でjqueryファイル(jquery-3.6.1.min_some.js)が読み込まれました。
その後、jqファイル内(in jqf)のコードはすべて1の後に安全に発火しています。
ざっくり言うと、下図のようにmain.html読込後コンテンツは同期的に順番に描画されていきますが、asyncを付けたファイルは非同期に読み込まれ、次にそのファイル内に同梱されたコードが同期的に通常の順番で処理されていきます。
上記結果は、私のWindowsPCでのものですが前回同様MacBookでどうなるかも見ておきます。
Uncaught ReferenceError: $ is not defined
1 ': DOMContentLoaded'
2 ':(in jqf) at the jquery file end'
3 ':(in jqf) window.onload '
4 ':(in jqf) $(function(){})'
main.html内の「1 ': DOMContentLoaded'」が「2 ':(in jqf) at the jquery file end'」より先に発火していますが、jqファイル内(in jqf)のコードはすべて2の後に発火しています^^
結論
この現象の問題点については、w3cの仕様を読んでもそれらしい明示的な解決が見つけられなかったので、上記のような解決方法を今回は試してみました。(※考えたら scriptElement.onload で読み込み終了を確認できるのじゃないか?という事で、次はそれを試します。)
※w3c https://dev.w3.org/html5/spec-LC/scripting-1.html#attr-script-async
仕様にcallbackやawaitなどが無い以上、これで絶対とは言い切れませんが、ブラウザJavaScriptの動作から見て合理的かなと思えますし、今のところうまく動作しているように思えます。(まぁ、仕様にあっても動かないものも山ほどありますけど^^;)
あと、ライブラリの依存コードを同梱して切り分けることで、ページ内に取っ散らかる傾向のあるjqueryコードなどのライブラリ依存コードをファイル1本にまとめて、管理しやすくできるというメリットもあるかもしれません。
問題などあれば是非ご指摘いただけると幸いです(_ _)
Enjoy fast loading!
関連
前回の、asyncなscript要素で読み込むときの問題点
https://qiita.com/toshirot/items/d87a408f53c0461914a4
次回の、script要素.onloadイベントで読み込み完了を取得する方法
https://qiita.com/toshirot/items/f454c9fdcc78f6d90d01