在过去,webpack 打包时的一个权衡是,打包中的每个模块都会被包裹在单独的函数闭包中。这些包裹的函数使得 JavaScript 在浏览器中执行速度变慢。相比之下,像 Closure Compiler 和 RollupJS 这样的工具会将所有模块的作用域“提升”或合并到一个闭包中,从而使得代码在浏览器中执行速度更快。
此插件将在 webpack 中启用相同的合并行为。默认情况下,此插件在 生产模式 下已启用,在其他情况下则禁用。将 optimization.concatenateModules
选项 设置为 false
即可覆盖在生产模式下的默认优化。手动添加 ModuleConcatenationPlugin
或使用 optimization.concatenateModules
选项可以在其他模式下启用合并行为:
new webpack.optimize.ModuleConcatenationPlugin();
这种合并行为被称为“作用域提升”。
作用域提升是 ECMAScript 模块语法特别支持的一个功能。因此,webpack 可能会根据使用的模块类型和 其他条件 回退到普通的打包方式。
正如文章所解释的,webpack 尝试实现部分作用域提升。它会将模块合并到一个单一的作用域中,但并非在所有情况下都能做到。如果 webpack 无法合并一个模块,则有两种替代方案:阻止与创建新组。阻止意味着该模块必须在自己的作用域中;创建新组意味着会创建一个新的模块组。以下条件决定了不同的输出:
条件 | 输出 |
---|---|
没有 ES6 模块 | 阻止 |
从非导入导入 | 创建新组 |
从其他 chunk 导入 | 创建新组 |
被其他多个模块分组导入 | 创建新组 |
从 import() 语法导入 | 创建新组 |
受 ProvidePlugin 影响或使用 module | 阻止 |
接受 HMR | 创建新组 |
使用 eval() | 阻止 |
在多个 chunk 中 | 阻止 |
export * from "cjs-module" | 阻止 |
下面的伪 JavaScript 代码解释了该算法:
modules.forEach((module) => {
const group = new ModuleGroup({
root: module,
});
module.dependencies.forEach((dependency) => {
tryToAdd(group, dependency);
});
if (group.modules.length > 1) {
orderedModules = topologicalSort(group.modules);
concatenatedModule = new ConcatenatedModule(orderedModules);
chunk.add(concatenatedModule);
orderedModules.forEach((groupModule) => {
chunk.remove(groupModule);
});
}
});
function tryToAdd(group, module) {
if (group.has(module)) {
return true;
}
if (!hasPreconditions(module)) {
return false;
}
const nextGroup = group;
const result = module.dependents.reduce((check, dependent) => {
return check && tryToAdd(nextGroup, dependent);
}, true);
if (!result) {
return false;
}
module.dependencies.forEach((dependency) => {
tryToAdd(group, dependency);
});
group.merge(nextGroup);
return true;
}
如果使用的是 webpack CLI,使用 --stats-optimization-bailout
标志将显示出现 bailout 的原因;如果使用的是 webpack 配置,则需要向 stats
对象添加以下内容:
module.exports = {
//……
stats: {
// 此配置将显示出现 bailout 的原因
optimizationBailout: true,
},
};