JavaScript スクリプトの読み込み

最終更新: 2025-09-19 20:13:39 +0800 +0800
原文は英語で書かれています。あなたはAI翻訳版を読んでいます。

HTML パーサー、スクリプトフェッチャー、JavaScript エンジンは並行して動作できますが、実行時の制約はスクリプトの種類や属性によって異なります。パフォーマンス上のヒントとして単に async や defer を追加するのではなく、依存関係グラフと準備完了の契約に基づいてモードを選択してください。

パーサーをブロックするクラシックスクリプトを理解する #

async も defer も指定されていない外部クラシックスクリプトは、その要素の位置でパーサーをブロックします。ブラウザーは後続のマークアップを解析する前に、スクリプトを取得して実行します。

html
<script src="/library.js"></script>
<script src="/application.js"></script>

2 番目のスクリプトは、1 番目のスクリプトが作成したグローバル変数を読み取れます。これは、ドキュメントの順序が実行順序になるためです。インラインのクラシックスクリプトもパーサー上の位置で直ちに実行されるため、その時点までに解析された DOM だけにアクセスできます。

スクリプトが document.write を呼び出す必要がある場合や、後続のマークアップを解釈する前に状態を確立する必要がある場合、パーサーのブロックは意図的なものになり得ます。アプリケーションコードでは、このような依存関係を避けるべきです。依存すると、ネットワーク遅延によって DOM の構築と初回レンダリングの両方が遅れるためです。

順序付きクラシックスクリプトを遅延実行する #

defer は外部クラシックスクリプトに適用されます。HTML の解析と並行して取得が進み、その後、解析完了後かつ DOMContentLoaded の前に、遅延されたスクリプトがドキュメント順に実行されます。

html
<script defer src="/library.js"></script>
<script defer src="/application.js"></script>

2 番目のスクリプトは 1 番目のスクリプトへの依存関係を維持しながら、パーサーをブロックしません。defer 属性はインラインのクラシックスクリプトには影響しません。代わりにモジュールまたは明示的な準備完了コールバックを使用してください。

遅延スクリプトは、取得に時間がかかったり同期実行が行われたりすると、DOMContentLoaded を遅延させる可能性があります。スクリプトが必要とするスタイルシートも、スクリプトコードが計算済みスタイルに依存する可能性があるため、実行を遅延させることがあります。

独立したスクリプトを非同期で読み込む #

async を指定した外部クラシックスクリプトは、準備ができ次第実行されます。取得によって解析がブロックされることはありませんが、解析中にレスポンスが到着すると、実行時にパーサーが一時停止します。

html
<script async src="https://analytics.example/sdk.js"></script>

2 つの非同期スクリプトの実行順序は保証されません。相互に依存したり、完全な DOM に依存したり、DOMContentLoaded より前に実行されることを前提にしたりしてはいけません。DOMContentLoaded はこれらのスクリプトを待ちません。一方、ウィンドウの load イベントは、ドキュメントの未完了の読み込み処理に含まれるスクリプトを待ちます。

後続のコードが非同期初期化に依存する場合は、統合側が提供する明示的なコールバックまたは Promise を使用してください。あるネットワークでグローバル変数が存在することを確認しても、正しい順序の契約が成立するわけではありません。

ES モジュールを依存関係グラフとして読み込む #

type="module" は読み込みと実行の両方を変更します。

html
<script type="module" src="/main.js"></script>

モジュールスクリプトは、インラインモジュールを含め、デフォルトで遅延実行されます。静的な import 宣言によってグラフが形成され、その依存関係はモジュールが評価される前に取得されます。モジュールは strict モードで実行され、独自のトップレベルスコープを持ち、<script> 要素ごとではなく、解決されたモジュール識別子ごとに一度だけ評価されます。

モジュールの取得には CORS が使用されます。クロスオリジンのモジュールと、クロスオリジンにあるすべての依存関係は、適切な Access-Control-Allow-Origin レスポンスを返す必要があり、JavaScript 互換のメディアタイプも必要です。モジュールの取得に失敗した場合、クラシックスクリプトの動作にフォールバックすることはありません。

モジュールスクリプトに async を追加すると、準備ができ次第そのグラフを評価できるため、通常のドキュメント順の動作がなくなります。

html
<script type="module" async src="/independent-widget.js"></script>

トップレベルの await は、待機中のモジュールに依存するモジュールの評価を一時停止します。DOM の準備完了をアプリケーションの初期化完了の証拠として扱わないでください。非同期のモジュール処理が完了するまで利用側が待機する必要がある場合は、初期化 Promise をエクスポートまたは公開してください。

スクリプトを動的に挿入する #

JavaScript によって作成されたスクリプト要素は、デフォルトで強制的に非同期になります。

js
function loadScript(src) {
  return new Promise((resolve, reject) => {
    const script = document.createElement("script");
    script.src = src;
    script.addEventListener("load", resolve, { once: true });
    script.addEventListener("error", reject, { once: true });
    document.head.append(script);
  });
}

返される Promise によって、利用可能な完了境界が定義されます。

js
await loadScript("/library.js");
await loadScript("/application.js");

ローダーは挿入前に script.async = false を設定することで、動的に挿入されたクラシックスクリプトを順序付きキューに入れられます。失敗時に依存する読み込みを停止する必要がある場合は、明示的な Promise の順序付けの方が明確です。動的な import() は対応するモジュールの仕組みであり、要求されたモジュールグラフの評価が完了した後にのみ解決します。

DOMContentLoaded の後にスクリプトを動的に追加しても、そのイベントは再度発生しません。ウィンドウの load イベント前に読み込みを遅延させる処理を追加すると load を遅延させる可能性がありますが、イベント発生後に追加してもイベントを再発生させることはできません。

適切な準備完了イベントを選択する #

DOMContentLoaded は、解析が完了し、パーサーによって遅延されたスクリプトが実行されたことを意味します。画像、非同期スクリプト、動的に開始されたアプリケーション処理、またはすべてのカスタム要素の準備が完了したことを意味するわけではありません。

js
function start() {
  // The parsed DOM is available here.
}

if (document.readyState === "loading") {
  document.addEventListener("DOMContentLoaded", start, { once: true });
} else {
  start();
}

readyState の分岐は、イベント後に開始される可能性があるモジュールや動的に読み込まれるコードで重要です。画像やフレームなど、読み込み完了したリソースがレイアウトや動作に必要な場合にだけ load を使用してください。依存関係が 1 つだけの場合は、image.decode()、document.fonts.ready、またはモジュール独自の初期化 Promise など、リソース固有の Promise を優先してください。

パーサーをブロックするスクリプトは即時の順序付けを提供し、defer は解析後の順序付き実行を提供し、async は独立した機会的実行を提供し、モジュールは CORS で検証された依存関係グラフを提供します。仕組みを依存関係の契約に合わせることで、キャッシュやネットワークが異なると失われるタイミングの仮定を排除できます。

コメント

GitHub で閲覧・コメントする(投稿にはログインが必要です)