ModuleConcatenationPlugin

过去,webpack 在打包时的一个权衡是,你的包中的每个模块都会被包裹在单独的函数闭包中。这些包装函数使得你的 JavaScript 在浏览器中执行速度变慢。相比之下,Closure Compiler 和 RollupJS 等工具会将所有模块的作用域“提升”(hoist)或串联(concatenate)到一个闭包中,从而使你的代码在浏览器中获得更快的执行时间。

此插件将在 webpack 中启用相同的模块串联行为。默认情况下,此插件已在 production 模式中启用,否则禁用。如果你需要覆盖 production 模式下的优化,请将 optimization.concatenateModules 选项设置为 false。要在其他模式下启用串联行为,你可以手动添加 ModuleConcatenationPlugin 或使用 optimization.concatenateModules 选项

new webpack.optimize.ModuleConcatenationPlugin();

这种串联行为被称为“作用域提升”(scope hoisting)。

作用域提升是 ECMAScript 模块语法实现的一个特定功能。因此,webpack 可能会根据你使用的模块类型和其他条件回退到正常的打包方式。

优化提前退出(Optimization Bailouts)

正如文章所解释的,webpack 试图实现部分作用域提升。它会将模块合并到一个作用域中,但并非在所有情况下都能做到。如果 webpack 无法合并模块,则有两种替代方案:Prevent 和 Root。Prevent 意味着模块必须在它自己的作用域中。Root 意味着将创建一个新的模块组。以下条件决定了结果:

条件结果
非 ES6 模块阻止
非 import 导入
从其他 Chunk 导入
被多个其他模块组导入
使用 import() 导入
ProvidePlugin 影响或使用 module阻止
HMR 已接受
使用 eval()阻止
在多个 Chunk 中阻止
export * from "cjs-module"阻止

模块分组算法(Module Grouping Algorithm)

以下伪 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;
}

调试优化提前退出(Debugging Optimization Bailouts)

当使用 webpack CLI 时,--stats-optimization-bailout 标志将显示提前退出原因。当使用 webpack 配置时,将以下内容添加到 stats 对象中:

module.exports = {
  //...
  stats: {
    // Display bailout reasons
    optimizationBailout: true,
  },
};

3 贡献者

skipjackTheLarkInnbyzyk