Production Stylesheets with MiniCSSExtractPlugin

In this article we're going to cover the MiniCSSExtractPlugin for Webpack and dive into the best strategies for CSS in a Production Setting.

We're going to start where we let off in heroku. If you need to catch up:

git clone https://github.com/lawwantsin/webpack-course.git
cd webpack-course
git checkout prod-css
npm install

So, currently, we're using the style-loader to add our css to the head of our document at runtime. There's a slight flash as webpack loads and executes the JS before the line that injects the CSS, then the renderer rebuilds the page. Fine for development, not ideal for production. We want the first paint to happen almost immediately.

To do that we're going to export the css as a separate file using MiniCSSExtractPlugin.

In terminal:

npm install mini-css-extract-plugin

Let's duplicate the webpack config to make some production optimizations.

cd config
cp webpack.dev.js webpack.prod.js

In webpack.prod.js at the top:

const MiniCSSExtractPlugin = require("mini-css-extract-plugin")

Change the css rule to:

{
  test: /\.css$/,
  use: [
    {
      loader: MiniCSSExtractPlugin.loader
    },
    {
      loader: "css-loader"
    }
  ]
}

Finally add the plugin to the plugins array at the bottom of config/webpack.prod.js:

plugins: [
    ...
    new MiniCSSExtractPlugin({
      name: "[name].css",
      chunkName: "[name.css]"
    }),
    ...
  ]

Now let's change the build script in our package.json to point to the new config.

"build": "webpack --config=config/webpack.prod.js"
"build:dev": "webpack --config=config/webpack.dev.js"

Now when we run npm run build we get a new file dist/main.css.

Style sheet output as seperate file

Let's compare that to the dev build, where the css is included in the main-bundle.js. Run npm run build:dev:

Style sheet output as in JS file

44.5Kb vs 28.4Kb. A difference of 16.1Kb, which is the style-loader code used to build the style tags in development. Because inject: true is specified in the HTMLWebpackPlugin, main.css is automatically added as a <link>.

Optimizing CSS

There are 2 ways to compress CSS, the first is pretty simple. You could add an option to the css-loader. In config/webpack.prod.js

use: {
  loader: "css-loader",
  options: {
    minimize: true
  }
}

This is simple, but it only minimizes on a per file, basis, even though they are combined in the end into main.css. Thus they are denied any cross file optimizations possible. Of course, there's a plugin to solve this.

touch src/nav.css
npm install optimize-css-assets-webpack-plugin

In main.js add nav.css to the list of requires:

require("./nav.css")

Now add a duplicate rule to both main.css and nav.css.

a {
  color: white;
  padding: 10px 20px;
  cursor: pointer;
}

Finally in webpack.prod.js, import the plugin and add it to the array of plugins. At the top:

const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin")

At the bottom.

new OptimizeCssAssetsPlugin(),

When we run npm run build with and without OptimizeCssAssetPlugin commented out we see that the addition of this plugin does remove the duplicate anchor tag rules and the results, though less than spectacular, are real and they'll only increase as the project becomes more and more complex.

Without OptimizeCssAssetPlugin: Without OptimizeCssAssetPlugin

With OptimizeCssAssetPlugin: With OptimizeCssAssetPlugin

Difference: 35 Bytes (whoohoo, but still).

In Sum

In this article we created a new config file for the production build process. We separated the CSS from the JS for the first time and minimized it using both the loaders and a plugin. Webpack has a few more tricks for Optimizing CSS, which we'll discuss in a future article.

Test Locally

In the terminal:

heroku local

Open http://localhost:5000 in your browser. You should see our hero.

Here's our final code if heroku local doesn't work and you don't want to figure out why:

git checkout prod-css-final

Ship It!

Let's take a little time after every article to push our code to production. In the terminal:

In the terminal:

git commit -am "done"
git push heroku master:prod-css

Double click on the url it gives you at the end of the build.

Heorku Build output

In my case we can Command Double-Click on https://fast-brushlands-63702.herokuapp.com in the terminal to visit the live production site.