Webpack splitChunks 插件 - 为啥设置块的优先级使其初始化异步?

Posted

技术标签:

【中文标题】Webpack splitChunks 插件 - 为啥设置块的优先级使其初始化异步?【英文标题】:Webpack splitChunks plugin - why does setting a priority of a chunk make its initialisation asynchronous?Webpack splitChunks 插件 - 为什么设置块的优先级使其初始化异步? 【发布时间】:2019-06-23 16:16:42 【问题描述】:

我在理解 Webpack splitChunks 插件的行为时遇到问题。我正在做的是将现有站点上的旧脚本重写为组件并使用 Webpack 进行捆绑。捆绑包只是 JS,大部分是在正文的末尾加载的。但是一个小脚本需要在页面的页眉中,因为它还创建了一些稍后在页眉和正文中使用的全局方法,主要作为内联脚本。由于它用于 GA 跟踪,因此不会更改,必须尽快发送事件。

以下配置对我有效,但问题是为什么它只能这样工作

确切的问题在下面代码的注释中,但我也把它放在这里:我不明白为什么还要在common 块的条件中包含!isUsedInAppHeader。如果条件中没有!isUsedInAppHeader,则不会创建common.header 块。然后,当我尝试通过为common.header 块添加更高优先级来修复它时,它会导致app.header.js 中的脚本异步初始化。

我完全不理解异步行为,因为它在app.js 中从未发生过。

实际上,我还有另一个子问题。是否可以导出一个也立即初始化自身的公共块?或者您会完全提出另一种解决方案吗?标头中的脚本不能移动,还必须同步初始化,因为它的主要作用是为 GA 跟踪创建全局方法,这些方法必须立即在以下代码中使用。

谢谢!

Webpack 配置:

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

这是脚本在页面中的加载方式:

<!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>

【问题讨论】:

【参考方案1】:

正如SplitChunks plugin 所说:

默认情况下,它只影响按需块,因为更改初始块会影响 HTML 文件运行项目时应包含的脚本标签。

为了让事情更符合您的预期,您希望使用默认的chunks: async 设置,这样初始块将保留在您的入口点中。我相信您设置中的另一个选项是使用chunks: all 来表示通用。如果你想走这条路,consult this guide.

但我不推荐这种策略。由于大多数 cdn 都支持 webpack4 和 HTML2,最好让 webpack 自动拆分块,以及您通过 import syntax. 定义的异步、延迟加载部分。

所以您有 1 个入口点,以及您明确指定的 code-splitting 点。如果您不想在 webpack 构建中支持 es6 模块导入,可以使用 require.ensure 语法。如果您的代码库没有强迫您使用它,我不推荐它。

通过magic comments 预加载是import.then() 语法的另一个好处。您可以在 import 调用中添加像 /* webpackHint */ 这样的注释前缀来指示加载行为。

【讨论】:

以上是关于Webpack splitChunks 插件 - 为啥设置块的优先级使其初始化异步?的主要内容,如果未能解决你的问题,请参考以下文章

迁移到webpack4:从webpack.optimize.CommonsChunkPlugin到config.optimization.splitChunk,以及有个搜出来的中文解决办法是错的

理解webpack4.splitChunks之其余要点

Webpack:optimization.splitChunks.chunks 中的“all”和“initial”选项有啥区别

webpack4.0各个击破—— Javascript & splitChunk

错误:webpack.optimize.CommonsChunkPlugin 已被移除,请改用 config.optimization.splitChunks

webpack中splitChunk的使用方法