• 译 文 : Critical CSS and Webpack: Automatically Minimize Render-Blocking CSS
  • By Anthony Gore
  • Translation: The snail (GivenCui)
  • Proofread by: Veizz

“Remove CSS and JavaScript that block rendering”. This piece of advice from Google Page Speed Insights has always puzzled me.

When a web page is visited, Google expects it to load only what is useful to the initial view and use idle time to load the rest. This allows the user to see the page as early as possible.

There are a lot of things we can do to reduce JavaScript blocking rendering, such as Code Splitting, tree shaking, caching, etc.

But how do you reduce the CSS that blocks rendering? To do this, you can split and load the CSS required for the first rendering first (critical CSS) before loading the rest.

You can screen out the key CSS programmatically, and in this article, I’ll show you how to do that through Webpack’s automated process.

What is blocking rendering

If the resource is “blocking rendering”, it means that the browser will not display the page until the resource has been downloaded or processed.

Usually, we add a CSS style sheet to the HEAD tag of the HTML, which blocks the rendering, as shown below:

<head>
  <link rel="stylesheet" href="/style.css">... </head> <body> <p> You can't see me until style.css is downloaded!! </p> </body>Copy the code

When the HTML page is loaded by the Web browser, it is parsed line by line from top to bottom. When the browser parses to the Link tag, it immediately starts downloading the CSS stylesheet and does not render the page until it is finished.

For a large site, especially one that uses a massive framework like Bootstrap, the stylesheet is hundreds of kilobytes long, and the user has to wait patiently for it to be fully loaded before they can see the page.

So, should we put the link tag in the body to prevent blocking the rendering? You can do this, but blocking rendering is not without its advantages, and we can actually take advantage of it. If the page is rendered without any CSS loaded, we get ugly “content flashes”.

The perfect solution would be to load the key CSS related to the first screen using blocking rendering, and all the non-key CSS would load after the first screen rendering.

The key of CSS

Here is a simple web page I wrote with Webpack and Bootstrap, and the screenshot below shows the style after the first rendering.

Clicking the Sign Up Today button will bring Up a modal box that looks like this when it pops Up:

Styles needed for the first rendering include navigation bar styles, oversized screen styles, button styles, and common styles for other layouts and fonts. But we don’t need the modal box style, because it doesn’t immediately show up on the page. With that in mind, here are some possible ways we can split critical and non-critical CSS:

critical.css

.nav {
  ...
}

.jumbtron {
  ...
}

.btn {
  ...
}
Copy the code

non_critical.css

.modal {
  ...
}
Copy the code

If you already have this concept, then you might ask two questions:

  1. How do we programmatically distinguish critical CSS from non-critical CSS?
  2. How do I get a page to load critical CSS before the first rendering and non-critical CSS after?

The sample project

I’ll briefly describe the basic configuration of the project so that we can quickly digest the solution as we come across it. First, introduce Bootsrap SASS in the entry file.

main.js

require("bootstrap-sass/assets/stylesheets/_bootstrap.scss");
Copy the code

I use sass-Loader to handle Sass, in conjunction with the Extract Text Plugin, to put the compiled CSS into a separate file.

Use the HTML Webpack Plugin to create an HTML file that introduces compiled CSS. This is required in our solution, as you will see in a moment.

webpack.config.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: ['css-loader'.'sass-loader']})},... ] },... plugins: [ new ExtractTextPlugin({ filename:'style.css' }),
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'index.html',
      inject: true}})];Copy the code

After running the build, here’s what the HTML file looks like. Note that the CSS file is introduced in the head tag, so it will block rendering.

index.html

<! DOCTYPE html> <html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>vuestrap-code-split</title>
    <link href="/style.css" rel="stylesheet"> </head> <body> <! --App content goes here, omittedfor brevity.-->
  <script type="text/javascript" src="/build_main.js"></script>
</body>
</html>
Copy the code

Programming to identify key CSS

Manually differentiating key CSS can be a pain to maintain. To do this programmatically, we can use Addy Osmani’s Critical. This is a Node.js module that reads the HTML document and identifies the key CSS. Critical can do more than that, and you’ll soon find out.

Critical identifies the Critical CSS by specifying the screen size and loading the page with PhantomJS to extract all the CSS rules used in rendering the page.

The following are the Settings for the project:

const critical = require("critical"); Critical. Generate ({/* Webpack package output path */ base: path.join(path.resolve(__dirname),'dist/'),
  src: 'index.html',
  dest: 'index.html',
  inline: true,
  extract: true/* iPhone6 size you can change as needed */ width: 375, height: 565, /* Make sure to call wrapped JS files */ penthouse: {blockJSRequests:false,}});Copy the code

When executed, the HTML in the Webpack output file is updated to:

<! DOCTYPE html> <html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>Bootstrap Critical</title>
  <style type="text/css"> / * key through internal CSS stylesheets way to introduce * / body {the font-family: Helvetica Neue, Helvetica, Arial, sans-serif. font-size: 14px; The line - height: 1.42857; color:# 333;
      background-color: #fff;}... </style> <link href="/style.96106fab.css" rel="preload" as="style" onload="this.rel='stylesheet'">
  <noscript>
      <link href="/style.96106fab.css" rel="stylesheet"> </noscript> <script> /* Scripts used to load non-critical CSS */ </script> </head> <body> <! -- Here is the contents of App --> <scripttype="text/javascript" src="/build_main.js"></script>
</body>
</html>
Copy the code

It will also output a new CSS file, such as style.96106fab. CSS (the file is automatically hashed). This CSS file is the same as the original stylesheet, except that it does not contain the key CSS.

Embed key CSS styles inline

You’ll notice that the key CSS is embedded in the header of the document. This is optimal because the page does not have to load it from the server.

Preloading non-critical CSS

You’ll also notice that non-critical CSS uses a link tag that looks more complicated to load. Rel =”preload” tells the browser to start fetching non-critical CSS for later use. The key is that Preload does not block rendering, and the browser continues to draw the page regardless of whether the resource has loaded.

The onLoad attribute in the Link tag allows us to run the script when non-critical CSS loads are complete. The Critical module can automatically embed this script into the document, which provides a cross-browser compatible way to load non-critical CSS onto the page.

<link href="/style.96106fab.css" rel="preload" as="style" onload="this.rel='stylesheet'">
Copy the code

Add the Critical component to the WebPack packaging process

I created a plug-in called HTML Critical Webpack Plugin, which is just a wrapper around the Critical module. It will run after the HTML Webpack Plugin output file.

You can introduce this in a Webpack project:

const HtmlCriticalPlugin = require("html-critical-webpack-plugin");

module.export = {
  ...
  plugins: [
    new HtmlWebpackPlugin({ ... }),
    new ExtractTextPlugin({ ... }),
    new HtmlCriticalPlugin({
      base: path.join(path.resolve(__dirname), 'dist/'),
      src: 'index.html',
      dest: 'index.html',
      inline: true,
      minify: true,
      extract: true,
      width: 375,
      height: 565,
      penthouse: {
        blockJSRequests: false,}})]};Copy the code

Note: You should only use it in production builds, as it will make your development environment slow to build

The performance results

Now that you’ve removed the critical CSS and put the loading of the non-critical CSS into idle time, what is the performance gain?

I tested it using the Chrome Lighthouse extension. Keep in mind that the metric we’re trying to optimize for is “first effective rendering,” which is how long it takes a user to see a page that is actually viewable.

Do not use differentiating key CSS techniques for performance

Use differentiating key CSS techniques for performance

As you can see, my First Meaningful Paint app took nearly a second less time and saved 0.5 seconds to get to the interactive state. In practice, your application might not get such a dramatic improvement because my CSS is clunky (I include the entire Bootstrap library) and I don’t have many key CSS rules in such a simple application.

IKcamp’s original new book “Mobile Web Front-end Efficient Development Combat” has been sold on Amazon, JD.com and Dangdang.

IKcamp website: www.ikcamp.com


In 2019, iKcamp’s original new book Koa and Node.js Development Actual Combat has been sold on JD.com, Tmall, Amazon and Dangdang!