Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Minify CSS & DefinePlugin

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Compile webpack in production mode with:

yarn production

This minifies the JavaScript files... but does nothing to our CSS. Yep, everything is still fully expanded. That makes sense: Webpack handles JavaScript... but it doesn't handle CSS. That's done with the loaders.

But! The css-loader has a minimize option. If we set it to true, it will use a library called cssnano to make things... well... nano!

css-loader, minimize & LoaderOptionsPlugin

Setting an option on css-loader is easy! But... first, I need to show you a different way to set this option...

Go down to the production if statement. Add a new plugin: webpackConfig.plugins.push() with new webpack.LoaderOptionsPlugin(). Pass this two options: minimize: true and debug: false:

149 lines webpack.config.js
... lines 1 - 133
if (process.env.NODE_ENV === 'production') {
... lines 135 - 138
// passes these options to all loaders
// but we should really pass these ourselves
new webpack.LoaderOptionsPlugin({
minimize: true,
debug: false
... lines 148 - 149

Ok, what the heck does this do?

Basically... this plugin will pass these two options to all loaders. Wait... why do we need that? I mean, if we want to pass minimize: true to css-loader... can't we just scroll up and add that option? Yes! The plugin exists to help some older, legacy loaders work with new versions of Webpack. And honestly... it may not even be needed anymore. But, I still add it just in case.

The minimize: true option is important for css-loader. And in a few minutes, we're going to pass that option manually to that loader anyways. The debug flag toggles "debug" mode on each loader... which I'm pretty sure doesn't even do anything anymore.

But thanks to this, we're ready to see our minified CSS! Re-run:

yarn production

Scroll up! Yes! login.css is only 5 kb! It is definitely minified! Of course... it still contains a giant sourcemap! Heck, that's bigger than the CSS itself!

Disabling sourcemaps

We should probably remove the sourcemaps in the production build, right? Well, there are two opinions on this. Some people say that you should continue to output the sourcemaps in production... just in case you need to debug something. But, they use a different sourcemap option that outputs to a separate file. We want our CSS and JS files to just contain CSS and JS.

Personally, I typically turn sourcemaps off entirely for production. Go to the top of webpack.config.js. Let's move our settings variables above the loaders:

149 lines webpack.config.js
... lines 1 - 3
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const useDevServer = false;
const publicPath = useDevServer ? 'http://localhost:8080/build/' : '/build/';
... lines 8 - 149

Add two new variables: const isProduction = ... then go copy the inside of our if statement, and paste it here:

151 lines webpack.config.js
... lines 1 - 5
const useDevServer = false;
const publicPath = useDevServer ? 'http://localhost:8080/build/' : '/build/';
const isProduction = process.env.NODE_ENV === 'production';
... lines 9 - 151

Next, add const useSourcemaps = !isProduction:

151 lines webpack.config.js
... lines 1 - 5
const useDevServer = false;
const publicPath = useDevServer ? 'http://localhost:8080/build/' : '/build/';
const isProduction = process.env.NODE_ENV === 'production';
const useSourcemaps = !isProduction;
... lines 10 - 151

Use that below! Set the sourceMap option for each loader to useSourcemaps:

151 lines webpack.config.js
... lines 1 - 8
const useSourcemaps = !isProduction;
const styleLoader = {
... line 12
options: {
sourceMap: useSourcemaps
const cssLoader = {
... line 18
options: {
sourceMap: useSourcemaps
... lines 23 - 28
const resolveUrlLoader = {
... line 30
options: {
sourceMap: useSourcemaps
... lines 35 - 151

Oh, except for sassLoader - keep that true always... even though I just messed that up!

151 lines webpack.config.js
... lines 1 - 22
const sassLoader = {
... line 24
options: {
sourceMap: true
... lines 29 - 151

Remember, resolve-url-loader needs this. Don't worry, the sourcemaps won't actually be rendered... since that option is set to false on css-loader.

Yep, we have now disabled sourcemaps for CSS in production.

Next, near the bottom, find the devtool option. Change this: if useSourcemaps, then set it to inline-source-map. Else set this to false:

151 lines webpack.config.js
... lines 1 - 35
const webpackConfig = {
... lines 37 - 128
devtool: useSourcemaps ? 'inline-source-map' : false,
... lines 130 - 133
... lines 135 - 151

Oh, and down below in the if statement, we can use the new isProduction variable!

151 lines webpack.config.js
... lines 1 - 135
if (isProduction) {
... lines 137 - 148
... lines 150 - 151

Let's try it. Right now, layout.js is 211 kilobytes and layout.css is 712 kilobytes. Run the production build!

yarn production

Ok! Scroll up. Yes! layout.css is much smaller. But, layout.js is the same. Ah, that's because Uglify was already removing the extra sourcemap comments.

Adding minify to css-loader directly

We're in great shape. Just two more small things. First, Google for css-loader to find its GitHub page. Search for minimize. Cool! These are the options we can pass to the loader. The important one is minimize. Right now, we're setting this... but in a weird way: by using the LoaderOptionsPlugin. To be more explicit, let's set it for real: minimize: isProduction:

152 lines webpack.config.js
... lines 1 - 16
const cssLoader = {
... line 18
options: {
... line 20
minimize: true
... lines 24 - 152

If we decide the LoaderOptionsPlugin isn't needed some day, our CSS will stay minified.

The DefinePlugin

Ok! Our code is minified and sourcemaps are gone. There's just one more thing we need to do to fully optimize our assets! Google for the Webpack DefinePlugin. This is a very cool plugin.

It allows you to define constants in your code. Here's a good example. Imagine you want to know in your JS code whether or not you're in production... maybe because you do some extra logging when not in production.

In your code, you would use a constant called PRODUCTION. But instead of defining that somewhere in your JS, you can let the DefinePlugin do that for you. With this config, Webpack actually rewrites your code! If it sees !PRODUCTION, the final built code will say if !true. It literally replaces the PRODUCTION constant with the word true.

DefinePlugin: process.env.NODE_ENV

The plugin is perfect for something like this, or even feature flags. But it's also important for your production build. Down in our production section, add the plugin: new webpack.DefinePlugin(). Pass it process.env.NODE_ENV set to JSON.stringify('production'):

159 lines webpack.config.js
... lines 1 - 136
if (isProduction) {
... lines 138 - 150
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
... lines 157 - 159

There are a few things happening. First, you must always wrap the values to this plugin with JSON.stringify(). The plugin does a literal find and replace... so if the value is a string, it needs to be wrapped in quotes. JSON.stringify is an easy way to do that.

But why do we need this at all? Sometimes, a third-party library will try to determine whether or not it is being used in production or development... maybe so that it can add more helpful error messages. To do this, it will often do the same thing we're doing: check to see if process.env.NODE_ENV === 'production'.

But... that won't work! In a browser environment - which is where all of our actual JavaScript runs - there is no process.env.NODE_ENV! This causes that code to always run in development mode.

Thanks to the plugin, if any code checks for process.env.NODE_ENV, it will be replaced with the string "production". Yep, the final JS would literally contain code that looked like if ("production" === "production")!

In our app, this won't make any noticeable difference. But, our production build is ready-to-go.

Now, let's turn to asset versioning!

Leave a comment!

Login or Register to join the conversation

Update: I was wrong. Option minimize was removed from version 1. The last version with the option is 0.28.11.

Hey guys,
I am having a problem with the css minimize function. LoaderOptionsPlugin does not seem to add it to css-loader, and when I add it directly to the cssLoader config I get an invalid options error:

ERROR in ./node_modules/css-loader/dist/cjs.js??ref--1-2!./assets/css/login.css
Module build failed: ValidationError: CSS Loader Invalid Options

options should NOT have additional properties

I cut and pasted all the `yarn add` statements to make sure I used the correct versions for this tutorial.
I think its a version issue relating to this issue https://github.com/webpack-contrib/css-loader/issues/863

When I changed this yarn comand with @0.28, then everything works. yarn add css-loader@0.28 style-loader --dev

Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial explains the concepts of an old version of Webpack using an old version of Symfony. The most important concepts are still the same, but you should expect significant differences in new versions.

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": "^7.2.0",
        "symfony/symfony": "3.3.*", // v3.3.16
        "twig/twig": "2.10.*", // v2.10.0
        "doctrine/orm": "^2.5", // v2.7.0
        "doctrine/doctrine-bundle": "^1.6", // 1.10.3
        "doctrine/doctrine-cache-bundle": "^1.2", // 1.3.5
        "symfony/swiftmailer-bundle": "^2.3", // v2.6.3
        "symfony/monolog-bundle": "^2.8", // v2.12.1
        "symfony/polyfill-apcu": "^1.0", // v1.4.0
        "sensio/distribution-bundle": "^5.0", // v5.0.22
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.26
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "friendsofsymfony/user-bundle": "^2.0", // v2.1.2
        "doctrine/doctrine-fixtures-bundle": "~2.3", // v2.4.1
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.3.2
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "friendsofsymfony/jsrouting-bundle": "^1.6" // 1.6.0
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.6
        "symfony/phpunit-bridge": "^3.0" // v3.3.5

What JavaScript libraries does this tutorial use?

// package.json
    "dependencies": [],
    "devDependencies": {
        "babel-core": "^6.25.0", // 6.25.0
        "babel-loader": "^7.1.1", // 7.1.1
        "babel-plugin-syntax-dynamic-import": "^6.18.0", // 6.18.0
        "babel-preset-env": "^1.6.0", // 1.6.0
        "bootstrap-sass": "^3.3.7", // 3.3.7
        "clean-webpack-plugin": "^0.1.16", // 0.1.16
        "copy-webpack-plugin": "^4.0.1", // 4.0.1
        "core-js": "^2.4.1", // 2.4.1
        "css-loader": "^0.28.4", // 0.28.4
        "extract-text-webpack-plugin": "^3.0.0", // 3.0.0
        "file-loader": "^0.11.2", // 0.11.2
        "font-awesome": "^4.7.0", // 4.7.0
        "jquery": "^3.2.1", // 3.2.1
        "lodash": "^4.17.4", // 4.17.4
        "node-sass": "^4.5.3", // 4.5.3
        "resolve-url-loader": "^2.1.0", // 2.1.0
        "sass-loader": "^6.0.6", // 6.0.6
        "style-loader": "^0.18.2", // 0.18.2
        "sweetalert2": "^6.6.6", // 6.6.6
        "webpack": "^3.4.1", // 3.4.1
        "webpack-chunk-hash": "^0.4.0", // 0.4.0
        "webpack-dev-server": "^2.6.1", // 2.6.1
        "webpack-manifest-plugin": "^1.2.1" // 1.2.1