CommonsChunkPlugin 的正确姿势

原文:webpack bits: Getting the most out of the CommonsChunkPlugin()


webpack核心团队不时喜欢参与社区在Twitter中的讨论,并以有趣和翔实的方式分享一些知识

这一次,“游戏规则”很简单。 安装webpack-bundle-analyzer,生成所有bundles的炫彩图片,并与我分享。 作为回报,webpack团队会对我们发现的任何潜在问题提供帮助!

我们发现了什么?

最常见的问题是代码重复:库,组件,代码在多个bundle[同步或异步]中重复出现!

实例一

许多具有重复代码的vendor bundles

Swizec Teller 分享了他的一个项目,这个项目是由8-9个单页面应用程序组成的。我选择了这个项目作为例子,是因为我们可通过它了解许多技术的运用。所以让我们来看的更仔细一些:

我们可以从这里推断出不少东西(不需要看实际配置)。

每一个单页应用程序使用了一个新的CommonsChunkPlugin ,它只针对入口文件以及vendor代码。这将创建一个只包含来自node_modules文件夹的模块的bundle,另外一个bundle只包含应用程序代码。 配置部分:

Object.keys(activeApps)
  .map(app => new webpack.optimize.CommonsChunkPlugin({
    name: `${app}_vendor`,
    chunks: [app],
    minChunks: isVendor
  }))

activeApps变量代表每个单独的入口项目(entry point)。

机会领域

以下是我圈出的一些可以改进的区域。

“Meta”缓存

我们上面看到的是许多大型库,例如在6个或更多的 vendor bundle中使用到的momentjs,lodash和jquery。 单独打包第三方库(将所有 vendor添加到单独的 bundle 中)的策略是好的,但是我们也应该在所有的 vendor bundle中应用相同的策略。

我建议Swizec在他的插件数组的末尾添加以下内容:

new webpack.optimize.CommonsChunkPlugin({
  children: true, 
  minChunks: 6
})

通过这行代码,我们告诉webpack :

嗨 webpack,查看所有的 chunks(包括生成的 vendor),并将任何包含在至少6个chunks 的模块移动到一个单独的文件中。

正如你看到的,所有这些模块都被提取到一个单独的文件中,除此之外,Swizec报告说,这使整个应用程序的大小减少了17%!

实例二

Case Two: Duplicate vendors across async chunks:

在这个例子中,模块数量的重复对于整体代码大小的影响并没有那么严重,然而,当你观察下面的大图时,可以看到每个异步块中都存在完全相同的3个模块。

如同你所看到的,同样的两三个模块在40-50个异步bundle中都出现了。我们应该怎么利用CommonsChunkPlugin解决它呢?

创建一个异步公共chunk

和第一个例子中用到的技术非常相似,然而,我们需要在配置选项中将async属性设置为true:

new webpack.optimize.CommonsChunkPlugin({
  async: true, 
  children: true, 
  filename: "commonlazy.js"
});

同样的,webpack会扫描所有的chunk并寻找公共模块,由于指定了async: true,只会扫描通过code split分割的bundle。由于我们没有指定minChunks,所以会采用默认值3。通过这段代码,我们告诉webpack:

嘿webpack,查看所有的chunks,如果你发现同一个模块出现在3个或更多的chunks,将其分离成一个单独的异步公共chunk

优化后的结果如下:

现在异步chunks的体积变得非常小,所有代码已经被打包成一个名为commonlazy.js的文件。 由于这些bundle 已经很小,直到第二次访问之后,尺寸的影响才不是很明显。 现在,每个代码分割bundle的数据量远远少于数据量,我们通过将这些通用模块放入单独的可高速缓存的chunk中来节省用户加载时间和数据的消耗。

更多配置项

minChunks

那么如果你想要更多的设置项呢? 有些情况下,你不希望有一个公共的bundle,因为不是每个chunk都可以使用它。 minChunks属性也有一个功能! 你可以在这里设置什么模块添加到新创建的bundle中。 以下是一个例子

new webpack.optimize.CommonsChunkPlugin({
  filename: "lodash-moment-shared-bundle.js", 
  minChunks: function(module, count) { 
    return module.resource && /lodash|moment/.test(module.resource) && count >= 3
  }
})

这段代码的意思是:

嘿 webpack , 当你遇到绝对路径匹配lodash或momentjs的模块,并包含在至少3个单独的chunks中时,将这些模块提取到单独的chunk中。

你也可以在这里设置 async: true 使其变为异步bundle

更进一步

通过minChunks,您可以为特定bundlesentries创建较小的可缓存的vendors。 最后,你的项目可能会看起来像这样:

function lodashMomentModuleFilter(module, count) {
  return module.resource && /lodash|moment/.test(module.resource) && count >= 2;
}
function immutableReactModuleFilter(module, count) {
  return module.resource && /immutable|react/.test(module.resource) && count >=4
}
new webpack.optimize.CommonsChunkPlugin({
  filename: "lodash-moment-shared-bundle.js", 
  minChunks: lodashMomentModuleFilter
})
new webpack.optimize.CommonsChunkPlugin({
  filename: "immutable-react-shared-bundle.js", 
  minChunks: immutableReactModuleFilter
})

(4月1日更新):你可以在使用minChunks的同时使用filename ,但我们在webpack 2.3.2+ 后取消了这个用法

更多例子

这里介绍了运用 CommonsChunkPlugin()时的一些配置项,更多例子可以查看wbpack/webpack core的GitHub repo中的 /examples。如果你有更好的想法,请随时提交一个Pull Request!