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 SubscribeDuring development, we are not minifying our assets: yep, they're full of comments and spaces. And that's perfect for development! I like spaces! And those comments might be useful! Also... minification takes extra time, so we don't want to slow down our builds unnecessarily.
In other words, while developing, no minification! But for production, yes minification! That means that - somehow - we need to be able to pass a flag to webpack.config.js
that tells it to compile in dev or production mode.
Tip
If you are using Webpack 4 or higher, use this to configure the Webpack mode:
// webpack.config.js
//...
const webpackConfig = {
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
//...
}
In Node applications, there's a standard way to signal the environment to your apps: by setting an environment variable called NODE_ENV
. To read this, you can say process.env.NODE_ENV
:
... lines 1 - 8 | |
console.log(process.env.NODE_ENV); | |
... lines 10 - 135 |
Let's log that. Run webpack like normal:
./node_modules/.bin/webpack --watch
Ok! It prints undefined. So... how do we set that? On a UNIX system, prefix the command:
NODE_ENV=production ./node_modules/.bin/webpack --watch
Yes! It prints production
. If you're on Windows, there's a library called cross-env that can help do this.
The point is: we can now send a flag into Webpack to tell it the environment.
Awesome! Let's use this flag to minify our JavaScript first, via a plugin.
Start by replacing module.exports
with a new variable: const webpackConfig =
:
... lines 1 - 33 | |
const webpackConfig = { | |
... lines 35 - 131 | |
}; | |
... lines 133 - 140 |
Then, all the way at the bottom, export this: module.exports = webpackConfig
:
... lines 1 - 33 | |
const webpackConfig = { | |
... lines 35 - 131 | |
}; | |
... lines 133 - 139 | |
module.exports = webpackConfig; |
Before that, add an if statement: if process.env.NODE_ENV === 'production')
, then we will add a new plugin. So, webpackConfig.plugins.push()
then new webpack.optimize.UglifyJsPlugin
:
... lines 1 - 33 | |
const webpackConfig = { | |
... lines 35 - 131 | |
}; | |
if (process.env.NODE_ENV === 'production') { | |
webpackConfig.plugins.push( | |
new webpack.optimize.UglifyJsPlugin() | |
); | |
} | |
module.exports = webpackConfig; |
Tip
This is for Webpack 4 or higher. At first, install terser-webpack-plugin
and then use
the following configuration
// webpack.config.js
//...
const TerserPlugin = require('terser-webpack-plugin');
//...
if (process.env.NODE_ENV === 'production') {
webpackConfig.optimization.minimizer = [new TerserPlugin()];
}
And... that's it!
Try it! Run webpack without the NODE_ENV flag first:
./node_modules/.bin/webpack --watch
Ok cool. The un-uglified layout.js
file is 1.62 megabytes. Stop and re-run in production:
NODE_ENV=production ./node_modules/.bin/webpack --watch
Ahh... this takes longer to run! But, the JavaScript files are way, way smaller!
Open up the built login.js
. Ah, yes, one beautiful, single line.
Tip
License comments from outside libraries are not removed from the Uglified files for legal reasons. To remove them, see the extractComments option.
But, remembering this long command is a bummer. Heck, the command was already long, before adding the NODE_ENV
stuff! My fingers are so tired...
There's a great way to improve this. Open package.json
. Add a new key called scripts
set to a hash. Inside, you can put something like dev
set to NODE_ENV=dev webpack
:
{ | |
... lines 2 - 7 | |
"scripts": { | |
"dev": "NODE_ENV=dev webpack", | |
... lines 10 - 11 | |
}, | |
... lines 13 - 34 | |
} |
Thanks to that, we have a shortcut! Just run:
yarn dev
Yep, it runs NODE_ENV=dev webpack
! And we don't even need to say node_module/.bin/webpack
: the scripts
know to look there already for webpack
.
Let's add two more: watch
set to the same thing with --watch
on the end. And finally, production
, with NODE_ENV=production
:
{ | |
... lines 2 - 7 | |
"scripts": { | |
"dev": "NODE_ENV=dev webpack", | |
"watch": "NODE_ENV=dev webpack --watch", | |
"production": "NODE_ENV=production webpack" | |
}, | |
... lines 13 - 34 | |
} |
I love it! Try them out:
yarn watch
Nice! Stop that, and try:
yarn production
The command looks right... and the final JavaScript files are super small.
But! Our work is not done yet: we still need to minify the CSS files... and handle a few other things.
There is one mistake where you say - "If you are on windows" - answer should be - switch to linux based system :)
I myself like windows, things work better, except for development - it causes too much trouble, so I have xubuntu and windows on my computer. I actually still develop my side small projects on windows which do not make any money, but nothing fancy like webpack in the technology stack.
Haha, yea, it does usually make life more difficult. The new Unix subsystem is helping things - but it's still the "odd" operating system for development :).
Cheers!
Looks like webpack 4 is:
if ( process.env.NODE_ENV === 'production' ) {
webpackConfig.optimization.minimize = true;
}
Hi Ryan,
On 2:04 you change `module.exports` to `const webpackConfig`, and then before exporting it you push to it, ... but it was const! Why does this work?
Coming from the PHP world this _sounds_ wrong.
Thanks.
Hey Jose,
Haha, because "const" keyword when you declare variables in JS means a bit different than in PHP ;) When you declare variable via "const", i.e. assign an object, it means that you cannot reassign to that variable another object (or any other value). But you can use and even modify the object you assigned when declare it. This is a new feature of ES6, check our screencast "const vs let" for more details: https://knpuniversity.com/s...
Cheers!
Hi Ryan,
What about the option "--env.production" for example ? It seems to be easier than NODE_ENV.
Yo Thomas Talbot!
Great questions! And actually, I don't have a good answer for you! I've looked at the --env
flag once before... and I thought I remembered there being some issue or shortcoming with it. But now, I can't find any issue with it: it seems like a perfectly fine way to pass this "flag" into your code! Of course, you need to return a callback now.. but that's not really an issue :).
Actually, I may have had a problem with it in the context of Webpack Encore: where we (in that library) need to know what environment you're in (with --env
, the environment is passed to your webpack.config.js callback, but wouldn't be automatically accessible internally by Encore). That may have been why I completely neglected --env here.
Obviously, both work, but --env is obviously a bit more "official" :).
Cheers!
Thanks for your answer.
I think I'll keep the --env. :D
For Webpack Encore, I've tried it but there is lesser "granularity" IMHO.
We can't configure all that we want.... or it is not yet mature enough.
P.S. sorry, i'm pretty bad in english :(
Yo Thomas Talbot!
If you have some specific things that weren't flexible enough, we would love to hear from them. Please open an issue - real-world feedback is really helpful! https://github.com/symfony/... Mostly, my philosophy was to create a library that made 80% of the use-cases VERY easy out-of-the-box. Now, when people have real-world requirements that aren't possible (or at least aren't easy), we're adding more configuration methods. For example, this PR (which I'll probably merge today) https://github.com/symfony/... will add about 5 new methods to allow you to control the options of 5 plugins used internally.
Cheers!
// 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
}
}
// 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
}
}
In the Windows Powershell the following command works:
Or alternatively, as already described as video comment