Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Versioning Optimizations

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

Thanks to the [hash], under the output filename:

162 lines webpack.config.js
... 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'):

173 lines webpack.config.js
... 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:

173 lines webpack.config.js
... 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]:

173 lines webpack.config.js
... 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.

Named Module Ids

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():

177 lines webpack.config.js
... 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!

Cleaning out the build Directory

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:

180 lines webpack.config.js
... 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/**/*.*:

180 lines webpack.config.js
... 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:

180 lines webpack.config.js
... 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.

Leave a comment!

6
Login or Register to join the conversation

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

2 Reply

Hey Skylar

Thanks for sharing your solution :)

Cheers!

Reply

The last version of CleanWebpackPlugin (for webpack 4) does not require any parameter to work (it automatically detects each entrypoint).

1 Reply

Hey Pietrino

Thank you for this tip! I added a note to the video here: https://github.com/knpunive...

Cheers!

Reply
Default user avatar
Default user avatar Cristóbal Rejdych | posted 3 years ago

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');

Reply

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!

Reply
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
    }
}
userVoice