レンダリングブロックを考慮したリソースの読み込み(JavaScript編)


追記

「打倒レンダリングブロック!(JavaScript編)」として公開していましたが、CSS編の記事追加に伴い共通部分を分割し別記事にしました。

はじめに

こちらの記事はレンダリングブロックを考慮したリソースの読み込み(レンダリングの基本)の続きです。
前述の記事を読んでおくと、当記事の内容も理解しやすいかもしれません。

それでは早速、レンダリングブロックを意識したJavaScriptの読み込みについて3つに分類して紹介していきます。

  • 推奨パターン
  • アンチパターン
  • ケースバイケース

推奨パターン

async・defer属性で読み込み

scriptタグに属性がない場合、パースの途中でリソースを見つけるとパース作業を中断しリソースの読み込み・実行を行います。(これがいわゆるレンダリングブロックです。)
async・defer属性を付けることで非同期で(HTMLのパース/DOM構築を阻害することなく)読み込めます。
ただし非同期で行われるのは読み込みのみで、リソースの実行はパースが中断されます。

async・deferの違いはJavaScriptの実行タイミングにあります。

async:リソースが読み込まれた直後に実行されます。
defer:HTMLパース完了後(DOMContentLoadedの直前)に実行されます。

async属性

<head>
  <script src="foo.js" async></script>
  <script src="bar.js" async></script>
</head>
<body>...</body>

特徴

  • 非同期で読み込まれたあと即座に実行されます。読み込まれたものから実行されるため順番が担保されません。
  • トラッキングコード/計測タグなど、他のJavaScriptに依存しないファイルの読み込みに使用。
  • </body>直前の読み込みでは非同期の利点が活かされないため、<head>内で読み込むこと。

defer属性

<head>
  <script src="foo.js" defer></script>
  <script src="bar.js" defer></script>
</head>
<body>...</body>

特徴

  • 非同期で読み込まれ、HTMLパース完了後(DOMContentLoadedの直前)に実行されます。記載している順番に実行されます。
  • DOMに依存する、他のJSに依存するファイルの読み込みに使用。
  • </body>直前の読み込みでは非同期の利点が活かされないため、<head>内で読み込むこと。

アンチパターン

インラインJS

<head>
  <script>
  setTimeout(function() {document.body.appendChild(document.createElement("div")}, 1000);
  </script>
</head>
<body>
  <p>テキスト1</p>
  <script>
  setTimeout(function() {document.body.appendChild(document.createElement("div")}, 1000);
  </script>
  <p>テキスト2</p>
</body>

推奨しない理由

  • <script>タグ以降のDOMの構築をブロックしてしまう。
  • HTML文書のサイズを肥大化させてしまう。
  • ブラウザのローカルキャッシュが効かない。

属性無しで読み込み

<head>
  <script src="foo.js"></script>
</head>
<body>
  <p>テキスト</p>
</body>

推奨しない理由

  • <script>タグ以降のDOMの構築をブロックしてしまう。

</body>の直前で読み込み

<head>...</head>
<body>
  <p>テキスト</p>
  <script src="foo.js" async></script>
</body>

推奨しない理由

  • <script>タグ以降のDOMの構築をブロックしてしまう。
  • async・defer属性を付けても、パース終了間近に読み込みを開始するため非同期の利点が活かされない。

async・defer属性を両方指定

<head>
  <script src="foo.js" async defer></script>
</head>
<body>...</body>

推奨しない理由

  • syncに対応していないブラウザはdeferで読み込む記述方法だが、現在そんなブラウザはない。

link rel preloadで読み込み

<head>
  <script src="foo.js"></script>
  <link rel="preload" as="script" href="bar.js">
</head>
<body>...</body>

推奨しない理由

  • async・deferで代替できる。
  • ブラウザに依存する記述(IE・FireFoxでは使えない)。

ケースバイケース

Script-injectedでの読み込み

Script-injectedは外部リソース読み込みのソースコードを出力するインラインJSです。(以下の記述を参照)

Script-injected 実行前

<head>
  <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
  new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
  j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
  'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
  })(window,document,'script','dataLayer','GTM-XXXXXX');</script>
</head>
<body>...</body>

Script-injected 実行後

<head>
<script async src="https://www.googletagmanager.com/gtm.js?id=GTM-XXXXXX"></script>
</head>
<body>...</body>

推奨しない理由

  • 非同期読み込みを実現できるが、インラインJSのため基本は使用しない。
  • Google Tag Managerなどはこのような仕組みだが、計測イベントのタイミングがシビアでなければ外部ファイルにしてasync・defer属性での読み込みも検討するとベター。

レンダリングブロック対策のまとめとポイント

  • async・defer属性を使用して<head>内で読み込む。(deferは実行順序を保証するが、asyncは保証しない)
  • インラインJSを記述しない。
  • Google Tag Managerなどトラッキングコード/計測タグはケースバイケース。
  • JavaScriptは複数ファイルに分けずに出来る限り結合・圧縮する。(肥大化しすぎるのも問題なので適宜調整する)

参考URL