レンダリングブロックを考慮したリソースの読み込み（CSS編）

# はじめに

こちらの記事は[レンダリングブロックを考慮したリソースの読み込み（レンダリングの基本）](https://qiita.com/mt877/items/6f9311a1d66921019877)の続きです。
前述の記事を読んでおくと、当記事の内容も理解しやすいかもしれません。


# やるべきこと

CSSファイルが読み込まれるとパース後にCSSOM（CSS版のDOMのようなもの）の構築が行われます。
CSSOMの構築中はJavaScriptの実行やページのレンダリングがブロックされます。（DOMの構築はブロックしません。）
しかし、以下の対策を行うことでこのような問題が解消されます。

**1. CSSを非同期で読み込み**
**2. クリティカルCSSをインラインで記述**

具体的にどのような記述をすれば良いのか紹介していきます。


## CSSを非同期で読み込み

CSSを非同期で読み込むとCSSOMの構築も非同期になり、JavaScriptの実行やレンダリングがブロックされるといった問題が解消されます。
CSSの非同期読み込みには2パターンあります。

**注意**
非同期の特性上CSSの適用遅れが発生し、スタイルが適用されていないコンテンツが表示されることがあります。（いわゆるFOUC）
またJavaScriptにはCSSOMを取得・変更する機能があるため、原則としてCSSOMの構築が完了してから実行されるようになっています。
CSSやJavaScriptのどちらか、または両方を非同期で読み込むと実行順序が担保できなくなってしまい、JavaScriptでスタイルの制御をしている場合などは処理に影響が出る可能性があります。
レンダリングブロック対策に注力するあまり正しい処理が行われないのは本末転倒なので状況によって使い分けてください。


### rel=preload属性を使った非同期読み込み

```html
<head>
  <link rel="preload" href="foo.css" as="style">
</head>
<body>...</body>
```
**特徴**

- IEなど一部ブラウザは非対応のためpolyfillが必要になります。


### media・onload属性を使った非同期読み込み

```html
<head>
  <link rel="stylesheet" href="foo.css" media="print" onload="this.media='all'">
</head>
<body>...</body>
```
**特徴**

- media属性をprintに設定することで非同期でロードするようになります。
- そのままでは印刷の際にしかスタイルが適用されないためonload属性を使い、読み込み完了後にmedia属性をallに変更します。
- IEなどでも使用可能です。


## クリティカルCSSをインラインで記述

ファーストビューに必要なCSSをインラインで、それ以外のCSSを非同期で読み込むようにします。
これによってFOUCを抑えつつ、CSSOM構築に伴うレンダリングブロックの影響を最小限にすることができます。

```html
<head>
  <style>/* クリティカルCSSを記述 */</style>
  <link rel="preload" href="foo.css" as="style">
</head>
<body>...</body>
```
**特徴**

- クリティカルCSSの抽出は[Critical Path CSS Generator](https://jonassebastianohlsson.com/criticalpathcssgenerator/)やnpmパッケージの[critical](https://www.npmjs.com/package/critical)などのツールを使うと便利です。

<br>
以上がレンダリングブロックを考慮したCSSの読み込み方法になります。
また、レンダリングブロックの視点から考えたアンチパターン・おすすめできない方法についてもまとめました。


# アンチパターン


## サイズの大きいCSSファイルをそのまま読み込み

```html
<head>
  <link href="foo.css" rel="stylesheet">
  <script src="foo.js"></script>
</head>
<body>...</body>
```
**推奨しない理由**

- ファイルの読み込みからCSSOMの構築が完了するまでレンダリングがブロックされます。（DOMの構築はブロックしません。）
- 読み込みが完了するまでは何も表示されないため、クライアントの回線状況によってはUXに影響が出ます。
- CSSの直後に読み込んでいるJavaScriptは直前までのCSSOM構築が完了してから実行されます。（scriptタグにasync・defer属性を付与した場合はこの影響を受けず非同期で実行されます。）
- インラインCSSでも同様です。


## `@import`で読み込み

```html
<head>
  <style>@import url(...)</style>
  <link href="foo.css" rel="stylesheet"> // 外部リソース内で@importを使用
  <script src="foo.js"></script>
</head>
<body>...</body>
```
**推奨しない理由**

- 別ファイルをダウンロードするため別途リクエストが発生し、同期的に読み込まれるためレンダリングがブロックされます。


## `<body>`内で読み込み

HTML5.2からbody内でも外部CSSを読み込めるようになりました。
※特に有用な場面が思いつきませんでしたがピックアップしました。

```html
<head>...</head>
<body>
  <p>テキスト1</p>
  <link href="foo.css" rel="stylesheet">
</body>
```
**推奨しない理由**

- ファイルの読み込みからCSSOMの構築が完了するまでレンダリングがブロックされます。（DOMの構築はブロックしません。）
- 読み込みが完了（CSSOM構築）するまでは何も表示されないため、クライアントの回線状況によってはUXに影響が出ます。


## CSS-injectedでの読み込み

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

**CSS-injected 実行前**

```html
<head>
  <script>
    const link = document.createElement('link');
    link.href = 'foo.css';
    link.rel = 'stylesheet';
    const head = document.getElementsByTagName('head')[0];
    head.appendChild(link);
  </script>
</head>
<body>...</body>
```

**CSS-injected 実行後**

```html
<head>
<link href="foo.css" rel="stylesheet">
</head>
<body>...</body>
```
**推奨しない理由**

- 読み込みとCSSOM構築が非同期で行われますが、JavaScriptの実行からCSSの読み込み・CSSOM構築が完了するまではスタイルが適用されていないページが表示されます。


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

- 外部CSSは非同期で読み込む。
- サイズの大きいCSSファイルを同期的に読み込まない。
- クリティカルなCSSはその他のCSSと切り離してインラインで記述する。
- 非同期の読み込みはJavaScriptの処理も考慮して指定する。


# 参考URL

- ページの生成：ブラウザーはどのように動作するか
  https://developer.mozilla.org/ja/docs/Web/Performance/How_browsers_work

- フロントエンドのパフォーマンスを徹底解説！ブラウザの気持ちで理解するHTML/Javascript/CSSの話
  https://techblog.raccoon.ne.jp/archives/53180280.html

- rel=”preload”を極めるために必要な2種類のプリロード機能
  https://techblog.raccoon.ne.jp/archives/1575956867.html