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.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeCompile 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
:
// ... 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 | |
webpackConfig.plugins.push( | |
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:
// ... 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:
// ... 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
:
// ... 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
:
// ... 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!
// ... 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
:
// ... 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!
// ... 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
:
// ... 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')
:
// ... lines 1 - 136 | |
if (isProduction) { | |
// ... lines 138 - 150 | |
webpackConfig.plugins.push( | |
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!
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:
`
<br />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 <a href="#">https://github.com/webpack-contrib/css-loader/issues/863</a>
When I changed this yarn comand with @0.28, then everything works.
yarn add css-loader@0.28 style-loader --dev<br />