Vue や React などのフレームワークで開発されたシングルページアプリケーションでは、特定のページで特殊な機能を開発する際には、しばしばサードパーティの JS ファイルに依存する必要があります。グローバルに CDN リソースをインポートすると、冗長なファイルが読み込まれる可能性があるため、動的な読み込み方法を使用することが最適です。
動的な JS スクリプトの読み込みとは、依存ファイルを特定のページにのみインポートすることであり、グローバルにインポートすることを避けることで、これらのページが開かれていない場合に不要なリソースの読み込みを防ぎ、ページの読み込み速度を向上させ、プロジェクト全体をモジュール化します。
ドキュメントオブジェクトモデル(DOM)では、JavaScript を使用して HTML を動的に作成することができます。<script>
要素も同様であり、他のページの要素とは何も変わりません。そのため、JS ファイルを読み込むために手動で<script>
を作成することができます。
defer と async#
<script>
要素には、defer
とasync
という 2 つの属性があり、それぞれ異なる JS スクリプトの読み込みと実行モードを表します。
defer:このブール属性は、スクリプトがドキュメントの解析後に実行されることをブラウザに指示します。
async:このブール属性を設定すると、ブラウザにスクリプトを非同期に実行するよう指示します。
defer
については、外部の JS をページの末尾に配置すると考えることができます。JS の読み込みはページのレンダリングやリソースの読み込みをブロックしません。defer
は元の JS の順序で実行されます。
async
については、スクリプトを非同期に読み込み、実行することができるため、ページのレンダリングやリソースの読み込みをブロックしません。async
がある場合、JS ファイルがダウンロードされるとすぐに実行されます。そのため、元の順序で実行されるとは限りません。複数のスクリプトファイルが相互に依存している場合、async
を使用するとエラーが発生する可能性があります。
つまり、ブラウザはまず HTML ドキュメントをリクエストし、各種リソースを非同期にネットワークリクエストし、DOM のレンダリングを行います。その後、<script>
タグに到達すると、メインプロセスはレンダリングを停止し、このリソースの読み込みが完了してから実行し、その後に DOM の解析を続けます。async
属性が追加されると、独立したプロセスで読み込みと実行が行われるため、defer
と<script>
を<body>
の最後に配置するのと同じ効果が得られます。
defer と async
上図の青い線はネットワーク読み込みを表し、赤い線は実行時間を表し、緑の線は HTML の解析を表します。
const loadJS = (url, defer) => {
const recaptchaScript = document.createElement('script')
recaptchaScript.setAttribute('src', url)
if (defer) {
recaptchaScript.defer = true
} else {
recaptchaScript.async = true
}
recaptchaScript.onload = () => {
console.log('読み込み完了', url)
}
document.head.appendChild(recaptchaScript)
}
以下に例を示します。ここでは、5 つの JS スクリプトを読み込んでいますが、jquery-ui
とfullcalendar
はjquery
に依存しており、locale
はfullcalendar
に依存しています。このような場合、JS ファイルを特定の依存関係の順序で読み込む必要があります。
const assets = [
'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.0/jquery.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.9.0/moment.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.10.0/fullcalendar.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.10.0/locale/zh-cn.js',
]
assets.forEach((url) => loadJS(url, true))
現実は厳しい#
しかし、現実の環境では、ブラウザは遅延スクリプトを必ずしも順序通りに実行するわけではなく、DOMContentLoaded イベントが発生する前に実行するわけでもありません。そのため、スクリプトファイルの実行順序を制御するために単にdefer
を使用するだけでは大きなリスクがあります。ただし、onload
イベントを監視してファイルの読み込みが完了したかどうかを判断し、Promise
を使用して前のスクリプトファイルの読み込みが完了するのを待ってから次のファイルを読み込むことで、順序通りにスクリプトを読み込むことができます。
const loadJS = (url) => {
return new Promise((resolve) => {
const recaptchaScript = document.createElement('script')
recaptchaScript.setAttribute('src', url)
recaptchaScript.defer = true
recaptchaScript.onload = () => {
resolve()
}
document.head.appendChild(recaptchaScript)
}).catch(console.error)
}
// 順序通りにJSファイルを読み込む
const loadAssets = async () => {
for (const url of assets) {
await loadJS(url, true)
}
}
楽しんでください ฅ●ω●ฅ