本投稿のサンプルコードを GitHub に用意しました。
im36-123/critical_css | GitHub
概要
前回は、 Critical CSS のインライン化までのプロセスを手順を追って説明しました。
詳しくは下記を御覧ください。
Above the fold(ファーストビュー)に含まれる CSS をインライン化してパフォーマンスの改善を図る
Critical CSS を開発者が手動で抽出しインライン化するのは現実的ではありません。
そこで、今回は webpack, と HTML Critical Webpack Plugin を使ってインライン化してみたいと思います。
環境構築
HTML Critical Webpack Plugin が動作する環境を構築します。
今回作る環境は下記に用意してあります。
im36-123/critical_css | GitHub
ところどころ説明を省略していますので、合わせて御覧ください。
インストール
HTML Critical Webpack Plugin の他に Mini CSS Extract Plugin と HTML Webpack Plugin をインストールします。
$ 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 の全体は こちら にあります。
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 を使用しました
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>criticalCss</title>
</head>
<body>
<h1>クリティカル CSS を抽出する</h1>
</body>
</html>
バンドルする
環境を作り終わったらバンドルして HTML ファイルの中身を見比べてみましょう。
バンドル前は下記でしたね。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>criticalCss</title>
</head>
<body>
<h1>クリティカル CSS を抽出する</h1>
</body>
</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
CSS の配信を最適化する | PageSpeed Insights | Google Developers