webpack 团队于北京时间 10 月 12 日凌晨发布了 v5.0.0-beta.0 版本,本文译自 webpack/changelog-v5。此部分主要面向非插件开发的 webpack 使用者。

简要说明

此版本重点关注以下内容:

  • 我们尝试通过持久化存储优化构建性能。
  • 我们尝试采用更好的算法与 defalut 来改善长效缓存。
  • 我们尝试通过更好的 Tree Shaking 和代码生成来改善 bundle 的大小。
  • 我们尝试清除内部结构中奇怪的代码,同时在不影响 v4 功能基础上实现了新特性。
  • 我们目前尝试通过引入破坏性更改来为新特性做准备,以便于我们能尽可能长期地使用 v5。

迁移指南

=> 查阅迁移指南[1] <=

主要更改

移除废弃的代码

v4 中所有废弃的代码均已删除。

迁移:以确保你的 webapck 4 不打印弃用警告。

以下是已删除但在 v4 中没有弃用警告的内容:

  • 现在必须为 IgnorePlugin 和 BannerPlugin 传递一个 options 对象。

自动移除 Node.js Polyfills

早期,webpack 的目的是允许在浏览器中运行大多数 node.js 模块,但是模块整体格局发生了变化,现在许多模块的主要用途是以编写前端为目的。webpack <= 4 附带了许多 Node.js 核心模块的 polyfil,一旦模块中使用了任何核心模块(即 ”crypto“ 模块),这些模块就会被自动启用。

虽然这使得为 Node.js 编写模块变得简单,但它会将超大的 polyfill 添加到 package 中。在许多情况下,这些 polyfill 并非必要。

webpack 5 会停止自动 polyfill 这些核心模块,并专注于与前端兼容的模块。

迁移:

  • 尽可能尝试使用与前端兼容的模块。
  • 可以为 Node.js 核心模块手动添加 polyfill。错误信息将提示如何进行此操作。
  • package 作者:在 package.json 中使用 browser 字段,以使得 package 与前端代码兼容。为 borwser 提供可选的 implementations/dependencies。

反馈:无论是否喜欢上述修改,请都向我们提出反馈。我们并不确定是否会纳入最终版本。

采用新算法生成 chunk ID 以及 module ID

添加了用于长效缓存的新算法。在生产模式下,默认启用这些功能。

chunkIds: "deterministic", moduleIds: "deterministic"

此算法采用确定性的方式将短数字 ID(3 或 4 个字符)分配给 modules 和 chunks。这是基于 bundle 大小和长效缓存间的折中方案。

迁移:最好使用 chunkIds 和 moduleIds 的默认值。你还可以选择使用旧的默认值,chunkIds: "size", modules: "size",这将生成较小的 bundle,但这会使得它们频繁地进行缓存。

以新算法混淆 export 名称

添加了新算法来处理 export 的名称。默认情况下启用。

如果可能,它将以确定性方式破坏 export 的名称。

迁移:不需要进行任何操作。

为 chunk IDs 命名

在开发模式下默认启用,以新的算法为 chunk id 命名,给 chunk(以及文件名)提供易于理解的名称。module ID 由其相对于 context 的路径决定。chunk ID 由 chunk 的内容决定。

因此,你不再需要使用 import(/* webpackChunkName: "name" */ "module") 进行调试。但是,如果你要控制生产环境的文件名,那仍可使用。

可以在生产中使用 chunkIds: "named",但要确保在使用时不会意外地泄露有关模块名称的敏感信息。

迁移:如果你不喜欢在开发中更改文件名,则可以传递 chunkIds: "natural" 以使用旧的数字模式。

JSON 模块

JSON 模块现在符合规范,并会在使用非默认导出时发出警告。

迁移:使用默认导出。

(自 alpha.16 起)

嵌套 tree-shaking

webpack 现在可以追踪对 exports 嵌套属性的访问。重新导出 namespace 对象,这可以改善 Tree Shaking 操作(未使用 export elimination 和 export mangling)。

// inner.js
export const a = 1;
export const b = 2;

// module.js
import * as inner from "./inner";
export { inner }

// user.js
import * as module from "./module";
console.log(module.inner.a);

在此示例中,可以在生成模式下移除 export b

(从 alpha.15 起)

内部模块(inner-module) tree-shaking

webpack 4 没有分析模块 export 与 import 之间的依赖关系。webpack 5 有一个新的选项 optimization.innerGraph,该选项在生产模式下默认启用,它对模块中的符号进行分析以找出从 export 到 import 的依赖关系。

如下述模块所示:

import { something } from "./something";

function usingSomething() {
  return something;
}

export function test() {
  return usingSomething();
}

内部图算法将确定仅在使用 export 的 test 时使用 something。这样可以将更多 export 标记为未使用,并从 bundle 中删除更多的代码。

如果设置了 "sideEffects": false,则可以省略更多模块。在此示例中,当未使用 export 的 test 时,将忽略 ./something

如需获取有关未使用的 export 的信息,需使用 optimization.unusedExports。如需删除无副作用的模块,需使用 optimization.sideEffects

此方式可以分析以下符号:

  • 函数声明(function declarations)
  • class 声明(class declarations)
  • 带有 export default 或带有变量声明(variable declarations)的
  • 函数表达式(function expressions)
  • class 语句(class expressions)
  • /*#__PURE__*/ 表达式
  • 局部变量(local variables)
  • imported bindings

反馈:如果您发现此分析中缺少某些内容,请反馈 issues,我们考虑将其添加。

此优化也称为深度作用域分析(Deep Scope Analysis)。

(自 alpha.24 起)

编译器空闲并关闭(idle and close)

现在需要再使用编译器(compilers)后将其关闭。编译器具有 enter 和 leave 空闲状态,并具有这些状态的 hook。插件可以使用这些 hook 执行不重要的工作。(即,持久化缓存将延迟存储到磁盘)。在编译器关闭时,所有剩余工作应尽快完成。回调执行时,表明关闭已完成。

插件及其各自的作者应该会期望某些用户可能会忘记关闭编译器。因此,所有工作最终也应该在空闲时完成。当工作完成时,应防止进程退出。

当传递 callback 时,webpack() 实例会自动调用 close

迁移:使用 node.js API 时,请确保在完成后调用 Complier.close

改进代码生成

此版本添加了新的选项 output.ecmaVersion。它允许为 webpack 生成的运行时代码指定最大 EcmaScript 版本。

webpack 4 仅能于生成 ES5 的代码。webpack 5 现支持 ES5 或 ES2015 的代码。

默认配置将生成 ES2015 的代码。如果你需要支持旧版浏览器(例如,IE11),则可以将其降为 output.ecmaVersion: 5

设置为 output.ecmaVersion: 2015 将使用箭头函数生成较短的代码,以及更多符合规范的代码,使用 const 声明(TDZ)作为 export default

(自 alpha.23 起)

生产模式中的默认压缩(default minimizing)也使用 ecmaVersion 选项生成较小的代码。(自 alpha.31 起)

chunk 分割以及 module size

与之前展示单个数值相比,模块现在以更好的方式展示其 size。除此之外,现在也拥有了不同类型的 size。

目前,SplitChunksPlugin 已知道如何处理这些不同的 size,并将它们应用于 minSize 和 maxSize。默认情况下,仅处理 javascript 的 size,但你可以传递多个参数来管理它们:

minSize: {
	javascript: 30000,
	style: 50000,
}

迁移:检查构建中使用了哪些类型的 size,并在 splitChunks.minSize和可选的 splitChunks.maxSize 中进行配置。

持久化缓存

目前包含文件系统缓存。它是可选的,可以通过以下配置启用:

cache: {
  // 1. 设置缓存类型为 filesystem
  type: "filesystem",

  buildDependencies: {
    // 2. 将你的配置添加为 buildDependency 以在更改配置时,使得缓存失效。
    config: [__filename]

    // 3. 如果你还有其他需要构建的内容,可以在此处添加它们
    // 请注意,loader 和所有模块中配置中引用的内容会自动添加
  }
}

重要内容

默认情况下,webpack 会假定其所处的 node_modules 目录由包管理器修改。针对 node_modules 目录,将跳过哈希和时间戳处理。出于性能方面考虑,仅使用 package 的名称和版本。symlinks(例如,npm/yarn link)很友好。除非你使用 cache.managedPaths: [] 选项取消此优化,否则请不要直接在 node_modules 中编辑文件。

默认情况下,缓存将分别存储在 node_modules/.cache/webpack 中(当使用 node_modules 时)和 .pnp/.cache/webpack(当使用 Yarn PnP 时,自 alpha.21 起)。你可能永远不必手动删除它。

(自 alpha.20 起)

当使用 Yarn PnP webpack 时,如果 yarn 的缓存不可变(通常不会发生变化)。你可以通过 cache.immutablePaths: [] 退出此优化。

(自 alpha.21 起)

用于 single-file-target 的 chunk 分割

目前,仅允许启动单个文件 target(如 node,WebWorker,electron main)支持在运行时自动加载引导程序所需的相关代码片段。

这允许对带有 chunks: "all" 的 target 使用 splitChunks

值得注意的是,由于 chunk 加载是异步的,因此这也会使初始估算也为异步操作。当使用 output.library 时,这可能会出现问题,因为导出的值的类型目前为 Promise。从 alpha.14 开始,这将不适用于 target: "node",因为 chunk 加载在此 target 下为同步。

(自 alpha.3 起)

更新解析器

enhanced-resolve 已更新至 v5。具体改进如下:

  • 当使用 Yarn PnP 时,解析器将直接处理无需其他插件
  • 此 resolve 可追踪更多的依赖项,例如文件缺失
  • 别名(aliasing)可能包含多种选择
  • 可以设置别名(aliasing)为 false
  • 性能提升

(自 alpha.18 起)

不包含 JS 的 chunk

不包含 JS 代码的 chunk 将不再生成 JS 文件。

(自 alpha.14 起)

实验阶段特性

并非所有特性从开始就文档。在 webpack 4 中,我们添加了实验性功能,并在 changelog 中指出它们是实验性的,但是从配置中并不能很清楚的了解这些功能是实验性的。

在 webpack 5 中,有一个新的 experiments 配置项,允许启用实验性功能。这样可以清楚地了解启用/使用了哪些实验特性。

虽然 webpack 遵循语义版本控制,但是实验性功能将成为例外。它可能包含 webpack 次要版本的破坏性更改。发生这种情况时,我们将在 changelog 中添加清晰的注释。这促使我们可以更快地迭代实验性功能,同时还可以使用我们在主要版本上停留更长时间以获得稳定的功能。

以下实验性功能将随 webpack 5 一同发布:

  • 像 webpack 4 一样对 .mjs 提供支持(experiments.mjs
  • 像 webpack 4 一样对旧版 WebAssembly 提供支持(experiments.syncWebAssembly
  • 根据更新规范[2] 对新版 WebAssembly 提供支持(experiments.asyncWebAssembly
  • 这使得 WebAssembly 模块成为异步模块
  • Top Level Await[3] Stage 3 阶段提案(experiments.topLevelAwait
  • 在顶层使用 await 使模块成为异步模块
  • 使用 import 引入异步模块(experiments.importAsync
  • 使用 import await 引入异步模块(experiments.importAwait
  • asset 模块类似类似于 file-loaderexperiments.asset)(自 alpha.19 起)
  • 导出 bundle 作为模块(experiments.outputModule)(自 alpha.31 起)
  • 这将从 bundle 中移除 IIFE 的包装器,强制执行严格模式,通过 <script type="module"> 进行懒加载,并在 module 模式下将其进行压缩

请注意,这也意味着针对 .mjs 的支持和 WebAssembly 的支持将被默认禁用

(自 alpha.15 起)

Stats

chunk 间关系默认情况下是隐藏的。可以使用 stats.chunkRelations进行切换。

(自 alpha.1 起)

Stats 现阶段可以区分 files 和 auxiliaryFiles

(自 alpha.19 起)

默认情况下,Stats 会隐藏模块和 chunk id。可以使用 stats.ids 进行切换。

所有模块的列表均按照到 entrypoint 的距离排序。可以使用 stats.modulesSort 进行切换。

chunk 模块列表和 chunk 根模块列表分别根据模块名进行排序。可以分别使用 stats.chunkModulesSort 和 stats.chunkRootModulesSort进行更改。

在串联模块中,嵌套模块列表进行拓扑排序。可以通过 stats.nestedModulesSort 进行更改。

chunks 和 assets 会显示 chunk id 的提示。

(自 alpha.31 起)

最低 Node.js 版本

Node.js 的最低支持版本从 6 变更为 8。

迁移:升级到最新的 node.js 可用版本。

配置变更

结构变更

  • 移除 cache: Object:不能设置为内存缓存对象
  • 添加 cache.type:可以设置为 "memory" 或 "filesystem"
  • 为 cache.type = "filesystem" 添加新配置项:
  • cache.cacheDirectory
  • cache.name
  • cache.version
  • cache.store
  • `cache.loglevel`(自 alpha.20 起被移除)
  • cache.hashAlgorithm
  • cache.idleTimeout(自 alpha.8 起)
  • cache.idleTimeoutForIntialStore(自 alpha.8 起)
  • cache.managedPaths(自 alpha.20 起)
  • cache.immutablePaths(自 alpha.21 起)
  • cache.buildDependencies(自 alpha.20 起)
  • 添加 resolve.cache:允许禁用/启用安全 resolve 缓存
  • 移除 resolve.concord
  • 移除用于原生 node.js 模块自动的 polyfill
  • 移除 node.Buffer
  • 移除 node.console
  • 移除 node.process
  • 移除 node.*(node.js 原生模块)
  • 迁移:使用 resolve.alias 和 ProvidePlugin。发生错误会给出提示。
  • output.filename 可以赋值函数(自 alpha.17 起)
  • 添加 output.assetModuleFilename(自 alpha.19 起)
  • resolve.alias 的值可以为数组或 false(自 alpha.18 起)
  • 添加 optimization.chunkIds: "deterministic"
  • 添加 optimization.moduleIds: "deterministic"
  • 添加 optimization.moduleIds: "hashed"
  • 添加 optimization.moduleIds: "total-size"
  • 移除 module 和 chunk id 的相关的弃用选项
  • 移除 optimization.hashedModuleIds
  • 移除 optimization.namedChunksNamedChunksPlugin 与之相同)
  • 移除 optimization.namedModulesNamedModulesPlugin 与之相同)
  • 移除 optimization.occurrenceOrder
  • 迁移:使用 chunkIds 或 moduleIds
  • optimization.splitChunkstest 不在匹配 chunk 名
  • 迁移:使用 test 函数(module, { chunkGraph }) => chunkGraph.getModuleChunks(module).some(chunk => chunk.name === "name")
  • 在 optimization.splitChunks 中添加 minRemainingSize(自 alpha.13 起)
  • optimization.splitChunks filename 可以赋值函数 (since alpha.17)
  • optimization.splitChunks sizes 可以为每个源类型的 size 对象
  • minSize
  • minRemainingSize
  • maxSize
  • maxAsyncSize(自 alpha.13 起)
  • maxInitialSize
  • 在 optimization.splitChunks 中添加 maxAsyncSize 和 maxInitialSize:允许为初始和异步 chunk 指定不同的最大 size。
  • 移除 optimization.splitChunks 中的 name: true:不再支持自动命名
  • 迁移:使用默认值。chunkIds: "named" 将为你的文件提供有用的名称以便于调试
  • 添加 optimization.splitChunks.cacheGroups[].idHint:将提示如何命名 chunk id
  • 移除 optimization.splitChunks 中的 automaticNamePrefix
  • 迁移:使用 idHint 替代
  • optimization.splitChunks 中的 filename 不再局限于初始 chunk(自 alpha.11 起)
  • 添加 optimization.mangleExports(自 alpha.10 起)
  • 移除 output.devtoolLineToLine
  • 迁移:无替代方式
  • output.hotUpdateChunkFilename: Function 现在被禁止:不会生效。
  • output.hotUpdateMainFilename: Function 现在被禁止:不会生效。
  • module.rules 中的 resolve 和 parser 将以不同的方式合并(对象会进行深度合并,数组将采用 "..." 进行展开以获取之前的值)(自 alpha.13 起)
  • module.rules 中的 query 和 loaders 已被移除(自 alpha.13 起)
  • 添加 stats.chunkRootModules:展示 chunk 的根模块
  • 添加 stats.orphanModules:展示未触发的模块。
  • 添加 stats.runtime:展示 runtime 模块
  • 添加 stats.chunkRelations:显示 parent/children/sibling chunk(自 alpha.1 起)
  • 添加 stats.preset:选择 preset(自 alpha.1 起)
  • BannerPlugin.banner 签名变更
  • 移除 data.basename
  • 移除 data.query
  • 迁移:从 filename 中进行提取
  • 移除 SourceMapDevToolPlugin 中的 lineToLine
  • 迁移:无替代方式
  • [hash] 不支持完整编译的 hash
  • 迁移:使用 [fullhash] 或采用更好的 hash 选项
  • [modulehash] 被废弃
  • 迁移:使用 [hash] 代替
  • [moduleid] 被废弃
  • 迁移:使用 [id] 代替
  • 移除 [filebase]
  • 迁移:使用 [base] 代替
  • 基于文件模板的新占位符(即 SourceMapDevToolPlugin)
  • [name]
  • [base]
  • [path]
  • [ext]
  • 当向 externals 传递一个函数时,它将具有不同的函数签名 ({ context, request }, callback)
  • 迁移:更改函数签名
  • 添加 experiments(请参阅上述实验部分,自 alpha.19 起)
  • 添加 watchOptions.followSymlinks(自 alpha.19 起)

默认值变更

  • 默认情况下,仅对 node_modules 启用 module.unsafeCache
  • 在生产模式下,optimization.moduleIds 的默认值从 size 替换为 deterministic
  • 在生产模式下,optimization.chunkIds 的默认值从 total-size 替换为 deterministic
  • 在 none 模式下,optimization.nodeEnv 默认为 false
  • optimization.splitChunks 中的 minRemainingSize 默认为 minSize(自 alpha.13 起)
  • 如果剩余部分过小,这将减少创建 chunk 的数量
  • 当使用 cache 时,resolve(Loader).cache 默认为 true
  • resolve(Loader).cacheWithContext 默认为 false
  • ~node.global 默认为 false~(自 alpha.4 起被移除)
  • resolveLoader.extensions 移除 .json(自 alpha.8 起)
  • 当 node-target 时,node.global 中的 node.__filename 和 node.__dirname默认为 false(自 alpha.14 起)