I have a problem understanding the behaviour of Webpack splitChunks plugin. What I'm working on is rewriting old scripts on existing site into components and using Webpack for bundling. The bundles are just JS, most is loaded at the end of the body. But one small script needs to be in the header of the page as it also creates few global methods used later in the header and in the body, mainly as inline scripts. As it's used for GA tracking, it won't change, the events must be sent ASAP.
The following configuration works for me, but the question is why does it work only this way?
Exact question is in the comment in the code below but I'm also putting it here: I don't understand why is it necessary to also include !isUsedInAppHeader
into the condition for the common
chunk. Without the !isUsedInAppHeader
in the condition no common.header
chunk is created. Then, when I try to fix it via adding higher priority for common.header
chunk, it results in asynchronous initialisation of the scripts in the app.header.js
.
The asynchronous behaviour is something I don't understand at all, as it does never happen in app.js
.
I have another subquestion, actually. Is it possible to export a common chunk which also initialises itself immediately? Or would you propose another solution altogether? The scripts in the header can't be moved and must also initialise synchronously, as it's main role is to create a global methods for GA tracking which are and must be used immediately in the following code.
Thanks!
Webpack configuration:
...
gulp.task('webpack', function(callback) {
var settings = {
...
entry: {
'app.header': './js/app.header.js',
'app': './js/app.js',
... // page specific files etc.
},
...
optimization: {
splitChunks: {
cacheGroups: {
// Node modules used in app.js
vendorsApp: {
test(module, chunks) {
let isInAppEntryPoint = chunks.map(chunk => chunk.name).includes('app');
let isNodeModule = /\/node_modules\//.test(upath.normalize(module.resource));
return isInAppEntryPoint && isNodeModule;
},
name: 'vendors',
chunks: 'all',
enforce: true,
},
// Modules shared between app.header and any other file
commonHeader: {
test(module, chunks) {
let isUsedInHeader = chunks.map(chunk => chunk.name).includes('app.header');
return isUsedInHeader;
},
name: 'common.header',
chunks: 'initial',
minChunks: 2,
minSize: 0,
// priority: 2 // (*)
},
// Modules shared between app.js and any other file
common: {
test(module, chunks) {
// QUESTION I don't understand why is it necessary to also include !isUsedInAppHeader into the condition.
// Without the !isUsedInAppHeader in the condition no common.header chunk is created.
// Then, when I try to fix it via adding priority (*) for common.header, it results
// in asynchronous initialisation of the scripts in the app.header.js
let isUsedInApp = chunks.map(chunk => chunk.name).includes('app');
let isUsedInAppHeader = chunks.map(chunk => chunk.name).includes('app.header');
return isUsedInApp && !isUsedInAppHeader;
},
name: 'common',
chunks: 'initial',
minChunks: 2,
},
}
}
},
};
var bundle = webpack(settings, function(error, stats) {
...
});
return bundle;
});
This is the way the scripts are loaded in the page:
<!DOCTYPE html>
<html lang="en">
<head>
...
<script src="/js/common.header.js"></script>
<script src="/js/app.header.js"></script>
<script>
... // Immediate calling of some of the the global methods defined in app.header
</script>
</head>
<body>
...
<script src="/js/vendors.js"></script>
<script src="/js/common.js"></script>
<script src="/js/app.js"></script>
<script src="..."></script> // page specific files etc.
</body>
</html>