Above the fold（ファーストビュー）に含まれる CSS を webpack を使ってインライン化する

本投稿のサンプルコードを GitHub に用意しました。
[im36-123/critical_css | GitHub](https://github.com/im36-123/critical_css)


# 概要
前回は、 `Critical CSS` のインライン化までのプロセスを手順を追って説明しました。
詳しくは下記を御覧ください。
[Above the fold（ファーストビュー）に含まれる CSS をインライン化してパフォーマンスの改善を図る](https://qiita.com/im36-123/items/274fe09c3dad94f9c37b)

`Critical CSS` を開発者が手動で抽出しインライン化するのは現実的ではありません。
そこで、今回は `webpack`, と `HTML Critical Webpack Plugin` を使ってインライン化してみたいと思います。

# 環境構築
`HTML Critical Webpack Plugin` が動作する環境を構築します。
今回作る環境は下記に用意してあります。
[im36-123/critical_css | GitHub](https://github.com/im36-123/critical_css)

ところどころ説明を省略していますので、合わせて御覧ください。

## インストール
`HTML Critical Webpack Plugin` の他に `Mini CSS Extract Plugin` と `HTML Webpack Plugin` をインストールします。

```shell
$ npm install --save-dev html-critical-webpack-plugin html-webpack-plugin mini-css-extract-plugin
```

> `HTML Critical Webpack Plugin` は `Mini CSS Extract Plugin` と `HTML Webpack Plugin` が実行されたあとに動作します.


## webpack.config.js を書く
今回は `src` ディレクトリ直下に `html`, `css`, `js` の各ファイルを設置し、 `Critical CSS` がインライン化されたものを `dist` ディレクトリに書き出すようにします。

>`Mini CSS Extract Plugin` と `HTML Webpack Plugin` の設定に関しては各プラグインのドキュメントを参考にしてください。

`webpack.config.js` の全体は [こちら](https://github.com/im36-123/critical_css/blob/master/webpack.config.js) にあります。

```js:webpack.config.js
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const HtmlCriticalWebpackPlugin = require("html-critical-webpack-plugin");

module.exports = {
  ・・・
  plugins: [
    new HtmlWebpackPlugin({・・・}),
    new MiniCssExtractPlugin({・・・}),
    new HtmlCriticalWebpackPlugin({
      base: path.join(path.resolve(__dirname), "dist/"),
      src: "index.html",
      dest: "index.html",
      inline: true,
      width: 375,
      height: 565,
      penthouse: {
        blockJSRequests: false
      }
    })
  ],
  ・・・
};
```
Above the fold（ファーストビュー） の領域を `width`, `height` で指定してください。

## HTML, CSS を用意する
HTML と CSS を `src` ディレクトリ内に用意します。
今回は HTML は下記のように、 CSSは `Bootstrap` を使用しました

```html:index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>criticalCss</title>
</head>
    <body>
        <h1>クリティカル CSS を抽出する</h1>
    </body>
</html>
```

## バンドルする
環境を作り終わったらバンドルして HTML ファイルの中身を見比べてみましょう。
バンドル前は下記でしたね。

```html:バンドル前のindex.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>criticalCss</title>
</head>
    <body>
        <h1>クリティカル CSS を抽出する</h1>
    </body>
</html>
```

バンドル後のファイルは下記のようになっていると思います。

```html:バンドル後のindex.html
<!DOCTYPE html>
<html><head><style>
    :root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;-ms-overflow-style:scrollbar}@-ms-viewport{width:device-width}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}h1{margin-top:0;margin-bottom:.5rem}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}h1{margin-bottom:.5rem;font-family:inherit;font-weight:500;line-height:1.2;color:inherit}h1{font-size:2.5rem}
</style>
<link href="main.css" rel="preload" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link href="main.css" rel="stylesheet"></noscript>
<script>!function(n){"use strict";n.loadCSS||(n.loadCSS=function(){});var o=loadCSS.relpreload={};if(o.support=function(){var e;try{e=n.document.createElement("link").relList.supports("preload")}catch(t){e=!1}return function(){return e}}(),o.bindMediaToggle=function(t){var e=t.media||"all";function a(){t.media=e}t.addEventListener?t.addEventListener("load",a):t.attachEvent&&t.attachEvent("onload",a),setTimeout(function(){t.rel="stylesheet",t.media="only x"}),setTimeout(a,3e3)},o.poly=function(){if(!o.support())for(var t=n.document.getElementsByTagName("link"),e=0;e<t.length;e++){var a=t[e];"preload"!==a.rel||"style"!==a.getAttribute("as")||a.getAttribute("data-loadcss")||(a.setAttribute("data-loadcss",!0),o.bindMediaToggle(a))}},!o.support()){o.poly();var t=n.setInterval(o.poly,500);n.addEventListener?n.addEventListener("load",function(){o.poly(),n.clearInterval(t)}):n.attachEvent&&n.attachEvent("onload",function(){o.poly(),n.clearInterval(t)})}"undefined"!=typeof exports?exports.loadCSS=loadCSS:n.loadCSS=loadCSS}("undefined"!=typeof global?global:this);</script></head>

<head>
    <meta charset="UTF-8">
    <title>criticalCss</title>

    </head><body>
        <h1>クリティカル CSS を抽出する</h1>
    <script type="text/javascript" src="main.js"></script></body>
</html>
```

バンドル後のファイルを見ると下記のことがわかります。

* `<style></stype>` 内に Above the fold に必要なルールセットが書き出されている。
* CSS ファイルは非同期で読み込まれている （`rel="preload"`）。
  * `loadCSS` を使って preload が動かないブラウザでも非同期で CSS ファイルを読み込めるようになっている。


# まとめ
自社サイトを Chrome の audit で解析しているときに、 `Critical CSS` について知ったので書きました。

# 参考
[anthonygore/html-critical-webpack-plugin](https://github.com/anthonygore/html-critical-webpack-plugin)
[CSS の配信を最適化する | PageSpeed Insights | Google Developers](https://developers.google.com/speed/docs/insights/OptimizeCSSDelivery)