【曖昧さ回避】ブラウザレンダリングにおける「ファイルの読み込み」が意味するものとは

## 「ファイルの読み込み」とは
ブラウザレンダリングの仕組みを解説するサイトや書籍には、「ファイルを読み込んで〜」のような説明が多くあります。
自分がレンダリング工程を勉強しているときに、この「読み込み」という言葉がファイルの**Download（転送）**を指すのか、ファイルの**Parse（解析）**を指すのか、はたまた**レンダリング全体**のことを言っているのか、説明する場面によって意味が変わる曖昧な言葉だなーと感じていました。

ここではブラウザレンダリングの仕組みについて、**1.HTMLのみ、2.HTMLとCSS、3.HTMLとJavaScript、4.HTMLとCSSとJavaScript**の4パターンに分けて、レンダリングフローに定義された言葉に当てはめながら説明していきたいと思います。

（検証環境：Google Chrome バージョン: 80.0.3987.87）

## ブラウザレンダリングの仕組みの大枠
ブラウザレンダリングのフローは大きく4つの工程に分けられ、それぞれの工程は更にいくつかの細かい工程に分けられます。
（参考：[Webフロントエンド ハイパフォーマンス チューニング　-久保田 光則 (著)](https://www.amazon.co.jp/dp/4774189677/ref=cm_sw_r_tw_dp_x_.H2WzbZVB428T) ）
<img width="1028" alt="ブラウザレンダリングの流れ.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321338/eb2319df-7f91-897d-47fa-56339e44541a.png">

- **Loading（データのダウンロード・解析）**
    - Download
    - Parse
- **Scripting（JSの実行）**
- **Rendering（スタイルの計算、当て込み）**
    - Calculate Style
    - Layout
- **Painting（描画）**
    - Paint
    - Rasterize
    - Composite Layers

図を見ていると全ての工程がシリアル（直列）に進んでいくように誤解しやすいのですが、実際はそうではありません。
レンダリングエンジンがページ表示を最適化する中で、部分的にでも準備ができた段階で、都度次の工程に進むこともあります。

本記事では主に**Loading（Download、Parse）**と**Scripting**の工程に関して、ファイルごとにどのように影響を及ぼし合い、レンダリングの処理順が決まっているかについて説明します。

**Rendering**や**Painting**の工程を含むブラウザレンダリング全体の仕組みについては以下記事が詳しいです。
[フロントエンジニアなら知っておきたいブラウザレンダリングの仕組みをわかりやすく解説！ | LeapIn](https://leap-in.com/ja/lets-learn-how-to-browser-works/)

## 1.HTMLのみ
はじめに外部ファイル「読み込み」記述が一切ない純粋なHTMLファイルについて、
ブラウザ検索バーにURLを入力し、HTTPプロトコルで通信してページを表示する場合を考えます。
（参考：[ネットワークやTCP/IPやHTTPの基本（初学者向け） - Qiita](https://qiita.com/ryosuketter/items/ba54d595a31f4ad0a58f)）

レンダリングの工程としては、まず**HTML**の**Download**が始まりますが、
ここでのポイントは、サーバからHTMLファイルなどのリソースが転送される手法は0か1の転送ではなく、
セグメントに分割しながら転送されるということです。
（どのくらいまとめて送るのかについてはサーバサイドで制御するようです）

前提として、ブラウザはUX向上のため画面に何も表示されていない時間を短くするように動きます。
よって全ての**HTMLDownload**が完了していなくても、転送されたHTMLセグメントを元に**Parse（DOMツリー構築）**や後続の処理が進み、準備ができたDOMから画面描画が始まります。

![スクリーンショット 2020-03-29 2.12.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321338/d2a3dc4a-0f35-471e-0ded-acc5387c3aaf.png)
上記はChrome DevToolsの**Networkパネル**であり、一つのHTMLファイルをダウンロード完了するまでの解析図です。（テスト用にサーバサイド（PHP）でファイルの転送や解析速度を調整しています）

**Waiting（TTFB：Time To First Byte）**とはファイル転送リクエストを送ってからクライアント側で最初のデータを受け取るまでにかかる時間（主にサーバサイドの処理時間）であり、**Content Download**とは最初のデータを受け取ってから全てのデータを受け取りきるまでにかかる時間です。

解析グラフによると**Content Download**に合計2sかかっていますが、その間も転送されてきているデータを元に**別の処理（Parse、Rendering、Painting）**が都度進んで描画が始まっており、それは同**Performanceパネル**で解析することができます。↓
<img width="854" alt="スクリーンショット 2020-03-29 19.13.47のコピー.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321338/64c3f13f-fe81-c4cf-15f4-84ef8719a886.png">
データを受け取る（**Receive Data**）たびに、**HTMLParse（DOM構築）**のフェーズを経て、**Composite Layers**までの描画工程を完了していることが分かります。

このように準備ができたところから都度描画が行われることで、**First Paint**（画面に最初になにかしらが描画されタイミング）や、**First Meaningful Paint**（画面に最初にユーザーに意味のある表示がされたタイミング）などの表示タイミング差が存在します。

参考：[Ace the Lighthouse Audit: Best Practices for Consistent Interactivity | Lumavate](https://www.lumavate.com/blog/ace-lighthouse-audit-best-practices-consistent-interactivity/)

## 2.HTMLとCSS
`head要素`の中の`link要素`に外部CSS「読み込み」記述がある場合を考えます。

```html:HTML
<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <!-- bodyの中身 -->
  </body>
</html>
```

### CSSのDownload

この場合も、まず**HTML**の**Download、Parse**が始まり、解析途中で`link要素`を見つけた段階で**CSS**の**Download**が始まります。↓
![スクリーンショット 2020-03-29 14.07.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321338/9ee2d48b-0d63-b1e6-9608-0256535e10e2.png)
**CSSのDownloadはHTMLのParseをブロックしないので、CSSDownload中もHTMLParseが並行して進みます。**
そしてその先に再び外部CSS「読み込み」記述ががあれば、**同時に複数のCSSDownloadが始まります。**

ただし、モダンブラウザでは（同じドメインの）TCP接続は同時に6本までという制限があるため、7本目以降の接続は前の接続の終了を待ってからとなります。
![スクリーンショット 2020-03-29 14.45.11のコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321338/74076ff6-118d-6124-b972-58b01af1dfce.png)
見ての通りこれではダウンロードしたいファイルが多いほどページ表示速度が遅くなってしまいます。

そのため、対応策としてファイルを可能な限りまとめてリクエスト必要数を抑えたり、CDNなどを利用してあえて別ドメインから接続することでスループットを上げたり、一つのTCP接続で同時に複数のリクエスト/レスポンスを処理できる**http/2プロトコル**で通信するなどの手法が存在します。

参考：[そろそろ知っておきたいHTTP/2の話 - Qiita](https://qiita.com/mogamin3/items/7698ee3336c70a482843)

### CSSのParse
CSSもHTMLと同様に**Download**の次の工程として、**Parse（CSSOMの構築）**の工程があります。
考慮すべき注意点は以下です。

- **CSSParse**は見かけ上は**HTMLParse**と並行して行われる。
- HTMLは描画工程に進もうとするDOMの、直前までに記載されているCSSの**Loading（Download、Parse）**が完了しない限り、**Rendering**フローに進まない。（描画処理が行われない）

**HTMLParse**と**CSSParse**はどちらもレンダリングエンジンのmainスレッドで行われますが、mainスレッドでは同時に一つの処理しか行えないため、それぞれの処理が同時に走ることはありません。
ですが、**HTMLParse**のアイドル時間などに**CSSParse**が進むため、見かけ上は2つが並行して行われているように見えます。
（そもそも**CSSParse**にかかる時間はブラウザレンダリング全体の時間からすると極めて短く、議論に上がりにくい部分のようです。）

また、**CSSのLoadingが進行中の場合は、たとえHTMLParseが先に完了していてもRenderingなどの次の工程に進まず、結果として画面描画が行われません。**

これはブラウザが**FOUC**（**Flash of Unstyled Content**の略。スタイルがついていないコンテンツが一瞬表示されること）を防ぐために、**CSSParse**の完了を待ってスタイルが適応された画面描画を行おうとするためです。
![スクリーンショット 2020-03-29 15.35.54のコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321338/512d1191-25e2-75cd-7897-d9c11723da3d.png)
上記Performanceパネル解析図を見ても、**Finish Loading**（**CSSParse**の完了）まで、**Calculate Style**などの**Rendering**工程に進んでいない（画面描画が行われていない）ことが分かります。

## 3.HTMLとJavaScript
以下のように`head要素`の中に`script要素`を記述して、外部JavaScriptファイルを「読み込む」場合を考えます。

```html:HTML
<!DOCTYPE html>
<html>
  <head>
    <script src="main.js"></script>
  </head>
  <body>
    <!-- bodyの中身 -->
  </body>
</html>
```
### JSのDownloadとScripting
**HTMLParse**が始まって`script要素`に到達すると**JSのDownload**が始まります。
その時に重要なポイントが、**JSのDownloadとScripting（実行）はHTMLParseをブロックする**ということです。

一度**JSのDownload**が始まると、ダウンロードしたJSの**Scripting**工程が完了しない限り、それ以降の**HTMLParse**が行われません。
これが、JSの記述はbodyの最後に記述するべきと言われる理由の一つです。
<img width="775" alt="スクリーンショット 2020-03-29 20.29.02のコピー.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321338/d87aac02-320b-f342-e108-e22c7b297309.png">
上記図より、**Send Request**で**JSDownload**が始まると、**Evaluate Script**工程が完了するまで**HTMLParse**が行われていないことが分かります。

### async属性とdefer属性
`script要素`によるJSの「読み込み」記述はそれ以降の**HTMLParse**をブロックしますが、`script要素`に`async`や`defer`の属性をつけることによって**JSのDownloadを非同期に行い、HTMLParseと同時に処理することができます。**

```html:HTML
<script src="main.js" async ></script>

<!-- もしくは -->

<script src="main.js" defer ></script>
```

以下は先程と同じ記述で、`defer属性`を使用したときのPerformanceパネルの解析結果です。
<img width="801" alt="スクリーンショット 2020-03-29 20.34.04のコピー.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321338/84039959-fd5a-b2a7-e76e-dc098df0f485.png">
**JSのDownload**が開始（**send Request**）しても、**HTMLParse**がブロックされずに先の工程に進み、最終的に**Composite Layers**まで完了して画面描画が行われているのが分かります。
その後**JSのDownload**が完了した段階で、**Scripting（Evaluate Script）**処理が行われています。

参考：[scriptタグに async / defer を付けた場合のタイミング - Qiita](https://qiita.com/phanect/items/82c85ea4b8f9c373d684)

## 4.HTMLとCSSとJavaScript
CSSとJavaScriptの両方の「読み込み」記述を書く場合です。
以下のように`link要素`の直下に`script要素`を入れてみます。

```html:HTML
<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="style.css" />
    <script src="main.js"></script>
  </head>
  <body>
    <!-- bodyの中身 -->
  </body>
</html>
```
**CSSDownload**は**HTMLParse**をブロックしないため、**HTMLParse**は`script要素`の記述に到達し**JSのDownload**が始まります。
先程「HTMLは直前までの**CSSLoading（Download、Parse）**が完了していない限り、**Rendering**フローに進まない」と説明しましたが、実は**同様にJSも直前までのCSSLoading（Download、Parse）が完了していない限り、Scriptingの工程に進まない**性質があります。

つまりこの場合、CSSよりもJSのほうが速く**Download**が完了したとしても、**CSSParse**が完了するまで**Scripting**が待機状態になるということです。
![スクリーンショット 2020-03-29 16.35.27のコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321338/a9e86480-ce81-48ec-2c22-96a6c4540a55.png)
↑JSのほうがCSSよりも1s速く**Download**が完了していますが、
![スクリーンショット 2020-03-29 16.34.37のコピー2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321338/f90e2862-bb8a-65d5-eae8-a0b3d78c99e5.png)
↑CSSの**Loading（Download、Parse）**完了を待ってから、**Scripting（Evaluate Script）**処理が実行されていることが分かります。

参考：[DOMContentLoaded周りの処理を詳しく調べてみました - Qiita](https://qiita.com/mamosan/items/ff336b5cc0a1a95e03a7)

### ブラウザのプリロード機能
以下のようにJSの「読み込み」記述をCSSよりも前に書いた場合を考えます。


```html:HTML
<!DOCTYPE html>
<html>
  <head>
    <script src="main.js"></script>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <!-- bodyの中身 -->
  </body>
</html>
```
`defer属性`や`async属性`がついていない`script要素`による外部JSファイルの「読み込み」なので、**JSのDownload、Scripting**が完了するまでそれ以下の**HTMLParse**が進まない、つまり**CSSDownload**も進まないはずです。

しかし、モダンブラウザではその限りではありません。
NetWorkパネルを見てみると、JSとCSSの**Download**が同時に行われていることが分かります。
<img width="1065" alt="スクリーンショット 2020-03-29 21.22.31のコピー.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/321338/c6177426-c519-1d4f-cd90-71e8d0f0496e.png">
実はChromeなどのモダンブラウザには、**HTMLParse**が進んでいない部分についても**Download**が必要な記述がないか確認し、もしあれば事前にそのファイルの**Download**を開始する機能があります。（**Preload Scanner**）

よってこの場合も、ブラウザは**JSのDownload中**にその先にあるCSSの「読み込み」記述を読み取り、**CSSDownload**も同時に進めることでレンダリングを高速化しているのです。

※Preload Scanner機能で事前処理できるのは**Download**の工程だけです。**Parse**や**Scripting**の工程は本来のレンダリングフローに沿って行われます。

参考：[rel="preload"を極めるために必要な2種類のプリロード機能 | Raccoon Tech Blog ](https://techblog.raccoon.ne.jp/archives/1575956867.html)

## まとめ

- HTMLはセグメントごとに**Download**が行われ、都度**Parse**などの先の工程に進む
- **CSSのDownload**は**HTMLのParse**をブロックしない
- **CSSのParse**は見かけ上は**HTMLParse**と並行して行われる
- HTMLは直前までの**CSSLoading（Download、Parse）**が完了していない限り、**Rendering**の工程に進まない
- **JSのDownload**と**Scripting（実行）**は**HTMLのParse**をブロックする
- JSも直前までの**CSSLoading（Download、Parse）**が完了しない限り、**Scripting**の工程に進まない

誤った解釈等ございましたら、ご教授お願いいたします。。

## 参考
- [Webフロントエンド ハイパフォーマンス チューニング　-久保田 光則 (著)](https://www.amazon.co.jp/dp/4774189677/ref=cm_sw_r_tw_dp_x_.H2WzbZVB428T) 
- [フロントエンジニアなら知っておきたいブラウザレンダリングの仕組みをわかりやすく解説！ | LeapIn](https://leap-in.com/ja/lets-learn-how-to-browser-works/)
- [ネットワークやTCP/IPやHTTPの基本（初学者向け） - Qiita](https://qiita.com/ryosuketter/items/ba54d595a31f4ad0a58f)
- [Ace the Lighthouse Audit: Best Practices for Consistent Interactivity | Lumavate](https://www.lumavate.com/blog/ace-lighthouse-audit-best-practices-consistent-interactivity/)
- [そろそろ知っておきたいHTTP/2の話 - Qiita](https://qiita.com/mogamin3/items/7698ee3336c70a482843)
- [scriptタグに async / defer を付けた場合のタイミング - Qiita](https://qiita.com/phanect/items/82c85ea4b8f9c373d684)
- [DOMContentLoaded周りの処理を詳しく調べてみました - Qiita](https://qiita.com/mamosan/items/ff336b5cc0a1a95e03a7)
- [rel="preload"を極めるために必要な2種類のプリロード機能 | Raccoon Tech Blog ](https://techblog.raccoon.ne.jp/archives/1575956867.html)
- [フロントエンドのパフォーマンスを徹底解説！ブラウザの気持ちで理解するHTML/Javascript/CSSの話 | Raccoon Tech Blog ](https://techblog.raccoon.ne.jp/archives/53180280.html)