Extract the critical path in webpack

In this article I’ll explain how critical-plugin makes your page render even faster.

Shlomi Levi
4 min readMar 4, 2018

More to come! Please stay tuned

Today, when you build a website you may choose to use a framework/library such Angular, React, Vue or just write in a html and css files.

Those modern frameworks are inlined your styles in JS bundle files (usually with Webpack). that’s okay for development but not so much for production because you need to wait for JavaScript to render your styles meanwhile your website stay without any style.

So does Webpack. When you use webpack to bundle your files, your styles are inlined in the JS bundle.

You can use a loader of course. but you have to create a another style file that load before the all your site style. the pros are you need to handle this thing and it’s missing the purpose of bundling the files.

What about if your styles are no longer inlined into the JS bundle, but in a separate CSS file (like styles.css)? If your total stylesheet volume is big, it will be faster because the CSS bundle is loaded in parallel to the JS bundle.

To do that we use ExtractTextPlugin or MiniCssExtractPlugin (in version 4) to extract the styles from JS bundle and save to css file.

webpack.config.prod.js

const ExtractTextPlugin = require("extract-text-webpack-plugin");const extractSass = new ExtractTextPlugin({
filename: "[name].[contenthash].css"
});
module.exports = {
mode: 'production',
module: {
rules: [{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}, {
test: /\.scss$/,
use: extractSass.extract({
use: [
"css-loader",
"sass-loader"
],
// use style-loader in development
fallback: "style-loader"
})
},
// more rules here......
plugins: [
new HtmlWebPackPlugin(),
extractSass // more plugins down below if you need...
]}

First, we set the output file [name].[contenthash].css

If we have scss files (Which is very recommend), We use ExtractTextPlugin rule to tell webpack that ExtractTextPlugin is handling scss files.

finally in plugins section we tell webpack to use ExtractTextPlugin to create for us the css file.

Now, the styles and JavaScript is loaded in parallel.

One problem solved. 👏 👏 👏

Critical Plugin

Let’s look at the html:

<html>
<head>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>I'm h1</h1>
</body>
</html>

When a browser receives the HTML response for a page from the server, there are a lot of steps to be taken before we see something in the screen. This steps the browsers needs to run through for the initial page which called the “Critical Rendering Path”.

The first thing the browser does is building the DOM Tree. He got into Parse phase that iterate all html tags and parse them. when the tag is link he stop the iteration, send a request to get the file and wait for the response. After the response is complete the rendering path is continue.

You can see down below that the browser gets the HTML and send the request for the css file, it’s take a ten seconds to get response and a meanwhile the rest of the tags are ready to get process but the browser is still wait for the response and do nothing!

So what we can do? first of put all our css file into style tag. because Style tag is not blocking the rendering phase.

But how we can do that? Those styles are creating by HtmlWebPackPlugin and ExtractTextPlugin. It’s different each time we start webpack because the hash right?

This is why I created the critical-plugin. This plugin inline your css files into style tag in your html file.
Yeah! BUT! not all the styles are inlined, Just html, body, fonts rules (that are critical for the initial view) are extract from the css bundle and inject into style tag in html file. all rest rules and styles are in the css which load after the dom is parsed aka preload mode. like so:

<html>
<head>
<style type="text/css">
@charset "UTF-8";@import url(//fonts.googleapis.com/css?family=Varela+Round|Merienda+One|Indie+Flower|Open+Sans:400italic,700italic,400,700);html{background-color:#000;}body{background-color:#212529;}
</style>

<noscript><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/animate.css@3.5.2/animate.min.css" integrity="sha384-OHBBOqpYHNsIqQy8hL1U+8OXf9hH6QRxi0+EODezv82DfnZoV7qoHAZDwMwEJvSw" crossorigin="anonymous"></noscript>
<link href="main.016271bd74aecb473864185e78c4b673.css" rel="preload" as="style" onload="this.onload=null;this.rel='stylesheet'"><noscript><link href="main.016271bd74aecb473864185e78c4b673.css" rel="stylesheet"></noscript><script>!function(t){"use strict";t.loadCSS||(t.loadCSS=function(){});var e=loadCSS.relpreload={};if(e.support=function(){var e;try{e=t.document.createElement("link").relList.supports("preload")}catch(t){e=!1}return function(){return e}}(),e.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)},e.poly=function(){if(!e.support())for(var a=t.document.getElementsByTagName("link"),n=0;n<a.length;n++){var o=a[n];"preload"!==o.rel||"style"!==o.getAttribute("as")||o.getAttribute("data-loadcss")||(o.setAttribute("data-loadcss",!0),e.bindMediaToggle(o))}},!e.support()){e.poly();var a=t.setInterval(e.poly,500);t.addEventListener?t.addEventListener("load",function(){e.poly(),t.clearInterval(a)}):t.attachEvent&&t.attachEvent("onload",function(){e.poly(),t.clearInterval(a)})}"undefined"!=typeof exports?exports.loadCSS=loadCSS:t.loadCSS=loadCSS}("undefined"!=typeof global?global:this);</script>
</head>
</html>

Use in your project

$ npm install critical-plugin --save-dev

# if you want you can use dependencies:

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

webpack.config.prod.js

const HtmlWebpackCriticalPlugin = require('html-webpack-critical-plugin');
...
plugins: [

new CriticalPlugin(),

// // or with options from https://github.com/addyosmani/critical#options
// new CriticalPlugin({
// critical: {
// inline: true
// }
// })


new HtmlWebPackPlugin(),

new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: "[name].css",
chunkFilename: "[id].css"
}),

// only in the next version for webpack4
// new ExtractTextPlugin({
// filename: "[name].[contenthash].css"
// }),

// if you use HtmlWebpackInlineSVGPlugin you should pass runPreEmit.
// new HtmlWebpackInlineSVGPlugin({
// runPreEmit: true
// }),
]

This plugin I wrote for Webpack version 4 and I wrap the critical library by addyosmani.

Advantages

you can use it in your bundling process. and you can use the configuration from critical library from this plugin! configuration options in here

Source Code

🎉🎉🎉 If you like this post, please 1. ❤❤❤ it below on Medium and 2. please share it on Twitter. You may retweet the below card🎉🎉🎉

Thank you for you the time! 🙏

--

--

Shlomi Levi

I'm independent consultant, mentor, coder, make some magic and providing Angular/Node.JS consulting to ambitious teams.