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 SubscribeHere's how I like to configure the CommonsChunkPlugin. Set minChunks
to Infinity
. Then change the name
option to layout
:
... lines 1 - 30 | |
module.exports = { | |
... lines 32 - 93 | |
plugins: [ | |
... lines 95 - 105 | |
new webpack.optimize.CommonsChunkPlugin({ | |
// "layout" is an entry file | |
// anything included in layout, is not included in other output files | |
name: 'layout', | |
minChunks: Infinity | |
}) | |
], | |
... line 113 | |
}; |
That name is important. Scroll up: layout
is the name of one of our entries!
... lines 1 - 30 | |
module.exports = { | |
entry: { | |
... lines 33 - 34 | |
layout: './assets/js/layout.js', | |
}, | |
... lines 37 - 93 | |
plugins: [ | |
... lines 95 - 105 | |
new webpack.optimize.CommonsChunkPlugin({ | |
// "layout" is an entry file | |
// anything included in layout, is not included in other output files | |
name: 'layout', | |
minChunks: Infinity | |
}) | |
], | |
... line 113 | |
}; |
Because of this, anything that is required in layout.js
will not be included in other files. Yep, since layout.js
requires jquery
, it will live in the compiled layout.js
, but not in login.js
or rep_log.js
:
... lines 1 - 2 | |
import $ from 'jquery'; | |
import 'bootstrap-sass'; | |
... lines 5 - 12 |
And since bootstrap-sass
is used here, it also won't be output in any other built file.
Our layout.js
file now serves two purposes. First, to collect all the common modules that we don't want duplicated across multiple entries. And second, as a way for us to execute any global JavaScript we might have.
Ok, find your Webpack terminal and stop it. Then, clear out the build directory:
rm -rf web/build/*
to delete that vendor.js
file. Now, Restart webpack:
./node_modules/.bin/webpack --watch
Yep, no vendor.js
. In base.html.twig
, we can remove the extra vendor.js
script tag:
... lines 1 - 94 | |
{% block javascripts %} | |
... lines 96 - 97 | |
<script src="{{ asset('build/vendor.js') }}"></script> | |
... line 99 | |
{% endblock %} | |
... lines 101 - 104 |
Try it out: refresh! Yes! Everything works!
Look inside the built layout.js
file. Right on top, you can see the Webpack bootstrap: a collection of functions that the rest of the built code use to help organize all the Webpack module-loading magic. Before we used CommonsChunkPlugin, this appeared at the top of every output file. But now - as you can see in the built login.js
- it's not there anymore. Webpack is already smart enough to know that since layout.js
is our common chunk and will be included on every page, the Webpack bootstrap code doesn't need to be repeated. Clever Bootstrap!
But... there's one small catch. Isn't there always one small catch? The Webpack bootstrap code includes something called the manifest. Internally, Webpack gives each module a number id, and the manifest contains those ids. Sometimes, those ids change.
Normally, we don't care! These are all internal Webpack details! But... if the module ids change... then the manifest changes... and that means that the contents of layout.js
change.
Let me say it a different way: because of the module ids in the manifest, if I make a change to, say, login.js
, it may cause the built layout.js
file to change. Why is that a problem? Caching.
We're going to talk more about caching and versioning later. But for now, let me just say this: you don't want the contents of a built file to change unless it actually needs to change. Why make your user re-download a fresh layout.js
file... if nothing important changed inside it?
Anyways, the fix is to move that Webpack bootstrap code out of layout.js
... so it can happily remain unchanged even when the internal module ids change. To do that, open webpack.config.js
and go to the CommonsChunkPlugin
. Change the name
option to an array, with layout
and a new entry called manifest
:
... lines 1 - 30 | |
module.exports = { | |
... lines 32 - 93 | |
plugins: [ | |
... lines 95 - 105 | |
new webpack.optimize.CommonsChunkPlugin({ | |
name: [ | |
... lines 108 - 109 | |
'layout', | |
... line 111 | |
'manifest' | |
], | |
... line 114 | |
}) | |
], | |
... line 117 | |
}; |
I'll move my first comment down a bit, and then add a new comment above manifest
:
Dumps the manifest in a separate file.
... lines 1 - 30 | |
module.exports = { | |
... lines 32 - 93 | |
plugins: [ | |
... lines 95 - 105 | |
new webpack.optimize.CommonsChunkPlugin({ | |
name: [ | |
// "layout" is an entry file | |
// anything included in layout, is not included in other output files | |
'layout', | |
// dumps the manifest into a separate file | |
'manifest' | |
], | |
... line 114 | |
}) | |
], | |
... line 117 | |
}; |
Let's see what this does! Restart Webpack:
./node_modules/.bin/webpack --watch
Ok, we still have a layout.js
file... but now we also have a tiny manifest.js
! Thanks to this change, layout.js
is just our code. And the tiny manifest.js
contains the Webpack bootstrap. If the ids change now, the user will only need to re-download that small file.
Of course, to get this to work... we need to add another script tag! Open base.html.twig
. Add a second script
tag pointing to manifest.js
:
... lines 1 - 94 | |
{% block javascripts %} | |
... lines 96 - 97 | |
<script src="{{ asset('build/manifest.js') }}"></script> | |
<script src="{{ asset('build/layout.js') }}"></script> | |
{% endblock %} | |
... lines 101 - 104 |
Phew! I know, that was confusing. But our setup rocks! Thanks to CommonsChunkPlugin
, anything in layout.js
, will not be duplicated in the other files.
Next! Let's learn about the webpack-dev-server.
Hey Agnes!
Hmm. When you execute webpack, what CSS files does it create? Webpack will split both your Js files *and* your CSS files, which means you will need multiple script tags *and* multiple links tags. So, you may just be missing some link tags because of the CSS splitting.
Cheers!
Hey Ryan, you're right, it was not generating the css in build. I wound up adding a section to the group. Not sure if this is correct, by it works :)
`
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
commons: {
name: 'layout',
test: 'layout',
enforce: true
},
css: {
name: 'css',
test: '/\.css$/',
enforce: true
}
}
},
// dumps the manifest into a separate file
runtimeChunk: {
name: "manifest",
}
}
`
Hey Agnes!
If it works, that's something :). This stuff can be confusing. On Webpack Encore, we use a pretty simple configuration:
splitChunks: {
chunks: 'all'
},
runtimeChunk: 'single'
We basically let Webpack to the splitting for us. We then need to handle figuring out which script/link tags to render (as things are split dynamically).
Cheers!
How would this be done on webpack 4?
Edit: I think I got it? Maybe?
Looks like (per comments in the previous step) this feature was moved out of plugins an into an optimization tag. To generate a manifest you have to add the runtimeChunk key with a name. Like this:
optimization: {
splitChunks: {
name: 'layout',
chunks: 'all',
minChunks: 2,
},
runtimeChunk: {
name: 'manifest'
}
},
Hey GDIBass!
Hmm, yes, well, at least, *almost*. In updating Encore for Webpack 4, here is our code that does this: https://github.com/symfony/...
The key things are:
a) Create a cache group whose name is the same as your entry file that you want to be the "commons" entry
b) I needed enforce: true as an option and also test, set to the entry name.
c) And yes, good job with the runtimeChunk stuff - you got that right.
I need to get an update ready for this chapter anyways for Webpack 4. So if you have any other questions, let me know!
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
}
}
Here is my config for Webpack 4.
This reduces the file sizes but the css can't be found when I go to the login page. And it does not find the css after logging in. Remove the optimization and everything returns to normal. What am I missing?