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 SubscribeThanks to the [hash]
, under the output filename
:
... lines 1 - 37 | |
const webpackConfig = { | |
... lines 39 - 43 | |
output: { | |
... line 45 | |
filename: useVersioning ? '[name].[hash:6].js' : '[name].js', | |
... line 47 | |
}, | |
... lines 49 - 137 | |
}; | |
... lines 139 - 162 |
When the contents of any JavaScript file changes, the output file will have a new filename. But... there is a slight issue. Sometimes... the filename might change... even when the contents don't change!
Without going into too much detail, it appears that [hash]
is not perfect... though this is one of those spots where nobody in the Webpack world seems to know for sure. To get around this, we'll use a plugin that gives us a more dependable hash.
In your terminal, run:
yarn add webpack-chunk-hash --dev
Copy that library name and - at the top of webpack.config.js
, import it: const WebpackChunkHash = require('webpack-chunk-hash')
:
... lines 1 - 5 | |
const WebpackChunkHash = require('webpack-chunk-hash'); | |
... lines 7 - 173 |
Down in the plugins
config, very simply, say: new WebpackChunkHash()
. I'll add a note above it:
... lines 1 - 39 | |
const webpackConfig = { | |
... lines 41 - 107 | |
plugins: [ | |
... lines 109 - 140 | |
// allows for [chunkhash] | |
new WebpackChunkHash(), | |
], | |
... lines 144 - 148 | |
}; | |
... lines 150 - 173 |
Thanks to this plugin, we have a new wildcard for the filename: [chunkhash]
. Yep, replace [hash:6]
with [chunkhash:6]
:
... lines 1 - 39 | |
const webpackConfig = { | |
... lines 41 - 45 | |
output: { | |
... line 47 | |
filename: useVersioning ? '[name].[chunkhash:6].js' : '[name].js', | |
... line 49 | |
}, | |
... lines 51 - 148 | |
}; | |
... lines 150 - 173 |
Re-run webpack:
yarn dev
Thanks to the new hash algorithm, all the JavaScript files have new hashes. And manifest.json
points to the new one.
There's one other slight problem with versioning. Earlier, we talked about how Webpack assigns an id to each module internally. Yep, you will see funny things like var jquery = __webpack_require__(45)
... because... for this build, 45 apparently represents the jquery
module.
The problem is that sometimes those module ID's change between builds. Because of this, even if we don't change any files that are part of login.js
, some of the module ID's it references could change. Like 45 could become 46 for jQuery! And that means that the filename of the built login.js
file would change... simply because these silly, internal module IDs change! We don't want to bust our user's cache unnecessarily.
This is not a huge deal... but we can do better! Yep, Webpack has a plugin that allows us to control the module IDs. At the bottom of webpack.config.js
, in plugins
, we'll actually use two core plugins. If isProduction
, use a new webpack.HashedModuleIdsPlugin()
. Otherwise, use new webpack.NamedModulesPlugin()
:
... lines 1 - 39 | |
const webpackConfig = { | |
... lines 41 - 107 | |
plugins: [ | |
... lines 109 - 143 | |
// keep module ids consistent between builds | |
// so that hashes doesn't suddenly change | |
isProduction ? new webpack.HashedModuleIdsPlugin() : new webpack.NamedModulesPlugin() | |
], | |
... lines 148 - 152 | |
}; | |
... lines 154 - 177 |
The HashedModuleIdsPlugin
changes the module ID's to be a hash based off the module's name. As long as jquery
is always called jquery
, the hash won't change. On the downside, this makes your final built files just a little bit bigger: these hashed IDs are longer than the numbers.
In development, the NamedModulesPlugin
basically uses the module's name as the ID, instead of a number. That's not usually helpful, but sometimes, like when trying to debug HMR, Webpack will give you an error with the module ID in the message. If the module ID is the module's name, that makes life easier!
Let's see what this looks like! Run the dev build:
yarn dev
Now find the built login.js
. Cool! You can see that the module ids just changed! In production, this would be an unreadable hash.
Ok, we're done! Our assets are truly production-ready. Congrats!
Of course... each time we run Webpack, our build/
directory gets more and more files. It's getting crowded in here! Technically, that's not a problem. But, it can get messy and confusing! I like to clear this directory between builds with a very simple plugin.
Run:
yarn add clean-webpack-plugin --dev
Copy that package name. Inside webpack.config.js
, on top, require it: const CleanWebpackPlugin = require()
and the package name:
... lines 1 - 6 | |
const CleanWebpackPlugin = require('clean-webpack-plugin') | |
... lines 8 - 180 |
Tip
Starting with v3.0.0 of clean-webpack-plugin
you need to import it as:
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
You can probably guess the next step. To the plugins
section! Add new CleanWebpackPlugin()
and pass it the path to clean: web/build/**/*.*
:
... lines 1 - 40 | |
const webpackConfig = { | |
... lines 42 - 108 | |
plugins: [ | |
... lines 110 - 148 | |
new CleanWebpackPlugin('web/build/**/*.*') | |
], | |
... lines 151 - 155 | |
}; | |
... lines 157 - 180 |
That's a glob pattern: the **
says look at web/build
recursively, and the *.*
says "delete all files"... but not directories.
Tip
Version 2 of this plugin doesn't require any arguments: it knows automatically to delete any output files.
Try it! Run:
yarn watch
Boooo! I made a mistake! I'm missing a comma at the end of my previous line:
... lines 1 - 40 | |
const webpackConfig = { | |
... lines 42 - 108 | |
plugins: [ | |
... lines 110 - 144 | |
// keep module ids consistent between builds | |
// so that hashes doesn't suddenly change | |
isProduction ? new webpack.HashedModuleIdsPlugin() : new webpack.NamedModulesPlugin(), | |
... lines 148 - 149 | |
], | |
... lines 151 - 155 | |
}; | |
... lines 157 - 180 |
Come on Ryan! Try it again.
A log message says it's working... and... yea! There is nothing in web/build
. And when it finishes... boom! There is one copy of each file. Perfect!
It's time to celebrate our awesome setup. Next, we get to talk about an amazing, powerful and fun Webpack feature: code splitting.
The last version of CleanWebpackPlugin (for webpack 4) does not require any parameter to work (it automatically detects each entrypoint).
Hey Pietrino
Thank you for this tip! I added a note to the video here: https://github.com/knpunive...
Cheers!
If You use CleanWebpackPlugin v3 and console give You error like this: "CleanWebpackPlugin is not a constructor" try import this plugin that way:
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
Hey Cristobal,
Thank you for this tip. That's exactly how you should require this plugin from their docs: https://github.com/johnagan...
I think we will add a note about it.
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
}
}
If you are following the tutorial with webpack 3, then use this yarn line:
yarn add webpack-chunk-hash@0.6 --dev
Edit:
Changed the version to 0.6 which worked for me.
You will also need this line:
yarn add --dev clean-webpack-plugin@0