Webpack splitChunks plugin - why does setting a priority of a chunk make its initialisation asynchronous?
Asked Answered
C

1

6


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>
Casanova answered 30/1, 2019 at 11:33 Comment(0)
B
0

As the SplitChunks plugin says:

By default it only affects on-demand chunks, because changing initial chunks would affect the script tags the HTML file should include to run the project.

To make things work more as you intend, you want to use the default chunks: async setting, that way the initial chunks will stay in your entry points. The other option in your setup I believe would be to use chunks: all for the common. If you want to go this way, consult this guide.

But I do not recommend this strategy. With webpack4 and HTML2 supported with most cdns, it is better to let webpack split the chunks automatically, and the async, lazy-loaded portions you define via the import syntax..

So you have 1 entry point, and the code-splitting points you specify explicitly. If you don't want to support es6 module imports in your webpack build, there is a require.ensure syntax you can use. I do not recommend it if your code base isn't forcing you to use it.

Preloading via magic comments is the other benefit of the import.then() syntax. You add a comment prefix like /* webpackHint */ to the import call to indicate loading behavior.

Bellhop answered 27/2, 2019 at 4:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.