最后一个支持 ES5 的浏览器 IE 11 在 2022 年被微软停止支持,那么今天 Web 上的 ES5 现状如何?在构建生产代码时,Web 开发者的最佳实践是什么?
本文将通过数据来回答这些问题,并基于这些数据为网站开发者和库作者提供一些具体的建议,帮助他们在未来处理旧版浏览器的支持问题。
简要声明
在深入探讨 ES5 使用的实际数据之前,本文需要澄清一点,编写或发布 ES5 代码本身并没有什么错。
JavaScript 引擎对 ES5 代码的优化时间比对现代代码的优化时间长得多,所以如果你有旧的 ES5 代码仍在工作,没有必要仅仅为了使其“现代化”而更新它。
然而,如果你使用 ES6 语法编写代码,然后使用构建工具将其转译为 ES5,这通常会导致大量的 polyfill 和转译器膨胀,显著增加最终包的大小。
为了说明这一点,下面是一个例子:
代码语言:javascript复制console.log([1, 2, 3].at(-1));
如果你手动将这段代码转译为 ES5,它可能看起来像这样:
代码语言:javascript复制var arr = [1, 2, 3];
console.log(arr[arr.length - 1]);
然而,如果你使用Babel转译这段代码,并配置它以添加 polyfills——即使你仅限于根据源代码中的使用情况添加所需的 polyfills——它会包含71 个 core-js 依赖项,并从 31 字节增加到11,217 字节的最小化代码!
这个例子的重点不是要说 Babel 或 core-js 不好。这些工具需要支持所有可能的 ES6 代码,这要求它们考虑各种边缘情况(尽管这个特定的例子没有任何边缘情况)。
相反,重点是强调选择支持旧版浏览器是有代价的,而且这个代价可能非常高。
不幸的是,问题实际上比代码膨胀更糟糕。如果查看下面的数据,了解今天流行的网站实际上是如何转译和部署他们的代码到生产环境,你会发现大多数网站在互联网上发布的代码是转译为 ES5 的,但仍然无法在 IE 11 中工作——这意味着转译器和 polyfill 膨胀被 100%的用户下载,但没有一个用户受益。
数据分析
要了解 ES5 在 Web 上的现状,需要关注以下三个方面,因为它们都在我们作为 Web 用户接收到的最终代码输出中起着关键作用:
- 流行的打包器和构建工具的默认配置
- 流行 JavaScript 库中的代码状态
- 网站所有者部署的代码状态
默认打包器和构建工具配置
大多数打包器和构建工具都具有极高的可配置性,几乎可以对最终输出的代码进行无限控制。然而,在实际操作中,大多数开发者只是使用默认配置,因此默认配置非常重要。
那么这些默认配置是什么?具体来说,这些默认配置是否会将代码转译为 ES5?
可以通过State of JS 调查(2023 年)看到最受欢迎的构建工具,按使用量大致排序如下:
工具 | 默认转译为 ES5? | 备注 |
---|---|---|
Browserslist | 否 | 本身不是构建工具,但被许多构建工具内部使用,是配置浏览器支持目标的最流行开源工具。defaults设置不再包括任何 ES5 浏览器。最后一个是 IE 11,它在 4.21 版本中被标记为已废弃。 |
Babel | 是 | Babel 的文档推荐设置targets选项(使用 Browserslist),但如果未指定,它将转译所有代码为 ES5。 |
webpack | 否 | 默认情况下,webpack 不会转译任何代码。大多数 webpack 用户包括babel-loader,而 webpack 的使用示例建议设置targets: "defaults"。 |
TypeScript (tsc) | 是 | TypeScript 的默认target选项是 ES5。 |
Next.js | 否 | Next.js使用 Babel 进行转译,默认设置一个 Browserslist 配置,目标是"现代浏览器"(即支持 ES 模块的浏览器)。 |
esbuild | 否 | esbuild默认不进行转译。你可以设置自定义目标以启用转译,但 ES5 不支持作为转译目标。 |
Vite | 否 | Vite 使用 esbuild,默认设置自定义目标为"现代浏览器"(即支持 ES 模块的浏览器)。如果需要支持旧版浏览器,Vite 允许用户安装一个插件。 |
Rollup | 否 | Rollup 默认不进行转译。许多 Rollup 用户安装@rollup/plugin-babel,在这种情况下使用 Babel 的默认配置。 |
Parcel | 否 | Parcel自动应用差异化服务,并具有可自定义的目标。 |
Closure Compiler | 否 | 默认设置为ECMASCRIPT_NEXT,即最新的一组稳定的 ES 特性。 |
如上表所示,绝大多数打包器和构建工具默认不再转译为 ES5。值得注意的是,较新的工具根本不支持 ES5,这表明趋势正在向这个方向发展。
尽管如此,Babel 仍然是最流行的 JavaScript 转译工具,因此在 Web 上转译为 ES5 仍然相当普遍(详见野外的 ES5 使用情况)。
流行的 JavaScript 库
除了查看流行的构建工具外,还查看了一些当今最流行的库(同样基于State of JS 调查,按受欢迎程度大致排序):
为了测试这些库中的每一个,我创建了一个仅导入该特定库的打包入口点,使用库文档中的一个代码示例。然后,我使用 Rollup 和 Webpack 打包代码,测试输出并查看是否包含任何 ES6 语法(特别是任何IE 11 不支持的 ES6 语法)。
结果:
库 | 包含 ES6 语法? | 备注 |
---|---|---|
Lodash | 否 | 仅 ES5 |
React | 否 | 仅 ES5 |
date-fns | 是 | 箭头函数 |
three.js | 是 | async/await,箭头函数,展开运算符,解构赋值 |
d3 | 是 | 箭头函数,展开运算符,解构赋值 |
Framer-motion | 是 | 箭头函数,展开运算符,解构赋值 |
greensock | 否 | 仅 ES5 |
dayjs | 否 | 仅 ES5 |
Zod | 是 | async/await,箭头函数,展开运算符,解构赋值 |
RxJS | 是 | 箭头函数 |
immer | 是 | 箭头函数,展开运算符,解构赋值 |
luxon | 是 | async/await,箭头函数,展开运算符,解构赋值 |
react-query | 否 | 仅 ES5(打包了 Babel 助手) |
如上所示,许多流行的 JavaScript 库现在发布的是 ES6 语法。
这很值得注意,因为正如我之前提到的,大多数使用 Babel 转译源文件的开发者在打包时,明确配置他们的打包器不转译node_modules
目录中的任何内容——这是库作者历史上觉得需要继续转译为 ES5 的主要原因。
截至 2024 年 9 月:
- Webpack 的
babel-loader
文档推荐的配置排除了node_modules
。 - Rollup 的
plugin-babel
文档建议排除node_modules
,并且建议库作者不要发布 ES6 代码。
而 TypeScript(tsc
),作为仅次于 Babel 的第二大转译工具,只会转译项目自己的代码文件。它不会转译node_modules
中的项目依赖项。
这就为任何希望支持 ES5 并使用 Babel 或tsc
转译代码的网站带来了问题。除非他们对构建管道的各个部分如何相互作用有深刻的理解,并且知道如何正确配置每一个部分,否则他们可能会在不知不觉中将 ES6 代码与 ES5 代码一起打包。
那么,这是否真的对实际网站造成了问题,还是大多数网站正确配置了他们的工具?下一节将通过HTTP Archive的数据来回答这个问题。
注意: 上表中的一些库发布了 ES5 和 ES6 版本,通常 ES5 版本设置在package.main
字段,而 ES6 版本设置在package.module
或package.exports
字段。在这些情况下,我只查看了使用默认配置时打包器拉取的脚本版本(因为这是大多数人使用的),而今天的打包器默认使用package.module
或package.exports
而不是package.main
(参见:[1],[2],[3])。
野外的 ES5 使用情况
开发者用来将 ES6 代码转译为 ES5 的三大主要工具是:
- Babel
- TypeScript(tsc)
- Closure Compiler(即 Google 内部的 JSCompiler)
这三种工具都包括某种形式的 polyfills 和所谓的 ES5“助手”函数,以避免在最终输出中重复。最常用的 ES5 助手函数库是:babel-helpers,core-js,regenerator-runtime,tslib,和$jscomp。
这些助手库中的许多函数都足够独特,可以通过查询 HTTP Archive 来检测(即使在最小化代码中)哪些网站在使用它们。搜索这些助手函数的存在——而不是标准的 ES5 语法(如var
或非箭头function
)——有助于区分手写的旧 ES5 代码(通常相当优化)和由转译器生成的新 ES5 代码(通常相当臃肿)。
我在 HTTP Archive 上进行了搜索,看看流行网站(基于CrUX 受欢迎度排名的前 10,000 个网站)在他们部署到生产环境的脚本包中包含这些助手的情况有多普遍。我还想看看网站提供未转译的 ES6 语法的情况有多普遍。
以下是我发现的结果(完整结果):
- 89% 的网站提供至少一个包含未转译 ES6 语法的 JavaScript 文件。
- 79% 的网站提供至少一个包含 ES5 助手代码的 JavaScript 文件。
- 68% 的网站提供至少一个同时包含 ES5 助手代码和未转译 ES6 语法的 JavaScript 文件。
重申一下本文的观点——如果浏览器不支持 ES6 语法(如 IE 11),那么它在尝试加载包含 ES6 语法的脚本文件时会出错。而如果浏览器确实支持 ES6 语法,那么它不需要任何 ES5 助手代码或任何旧版 polyfills。绝对没有理由同时包含两者。
为了确认这个查询结果的准确性,手动测试了列表中的 20 个随机网站,确认它们确实在某些脚本包中同时包含 ES5 助手代码和 ES6 语法。还手动在 IE 11 中访问了这些网站,确认这些脚本包确实无法加载。
请记住,这些不仅仅是互联网上的随机网站。这些是全球最受欢迎的 10,000 个网站
这意味着什么?
对于一个网站来说,提供包含 ES5 助手和未转译 ES6 语法的代码,实际上只有两种可能的解释:
- 该网站不需要支持 ES5 浏览器,但他们的一些依赖项转译为 ES5,因此 ES5 代码出现在他们的输出中。
- 该网站打算支持 ES5 浏览器,但他们没有意识到一些依赖项发布了未转译的 ES6 语法,并且他们没有配置打包器来转译
node_modules
中的代码。
无论是哪种解释,全球许多最受欢迎的网站都在提供大量不必要的代码,这强烈表明我们当前工具推荐的默认配置并不起作用。
如果从这些数据中能找到一丝安慰,那就是显而易见的是,放弃对 IE 的支持不会对大多数企业产生明显影响。如果所有这些大公司显然没有受到这些 IE 体验破坏的影响,那么你的公司也可能不会。
建议
对于库作者
库作者应将代码转译为 ES5 的最初理由是大多数网站需要转译为 ES5。然而,鉴于目前前 10,000 个网站中有 89%发布了一些未转译的 ES6 语法,这一理由已不再有效。
根据本文提供的数据,JavaScript 库作者不再需要将代码转译为 ES5。
实际上,库作者对导入它们的网站的浏览器支持需求没有信息,因此不应该为其所有用户做出这个决定。同时,库作者也不应该假设所有用户都能够通过复杂的构建过程运行它们的库,因此发布的代码应使用完全标准的 JavaScript,并在当前广泛使用的浏览器中工作。
那么库作者应该选择什么目标?在我看来,库作者的最佳解决方案是使用Baseline——具体来说,只包括Baseline Widely Available特性在任何发布的代码中。
如果你不熟悉 Baseline,这是 W3C 内的WebDX 社区组的一项努力,旨在帮助开发者轻松识别所有主要浏览器和浏览器渲染引擎在桌面和移动设备上稳定且广泛支持的特性。如果某个特性在所有四个主要浏览器的稳定版本中至少存在 30 个月,则被认为是Baseline Widely Available。
针对Baseline Widely Available的主要好处是它是一个动态目标,这意味着它不会像针对 ES5 那样被困在过去(这也是 Next.js、Vite 和 Parcel 使用的esmodule
目标目前正在发生的情况)。
库作者可以通过以下Browserslist查询配置他们的构建系统,以现在针对Baseline Widely Available特性(适用于任何支持 Browserslist 的工具):
代码语言:javascript复制targets: [
"chrome >0 and last 2.5 years",
"edge >0 and last 2.5 years",
"safari >0 and last 2.5 years",
"firefox >0 and last 2.5 years",
"and_chr >0 and last 2.5 years",
"and_ff >0 and last 2.5 years",
"ios >0 and last 2.5 years",
];
注意: 有一个开放的功能请求,希望将 Baseline 支持添加到 Browserslist,这将使上述查询简化为“baseline widely available”。
如果某个网站需要支持比Baseline Widely Available覆盖的更多浏览器,这完全没问题。该网站可以始终配置其构建系统以进一步转译任何导入的库。关键是这个决定最好由网站开发者做出,而不是库作者。
对于网站开发者
许多网站开发在同一个脚本包中同时提供未转译的 ES6 语法和 ES5 助手代码,这清楚地表明排除node_modules
目录不进行转译的做法并不是一个好做法。
然而,如今构建工具已经变得显著更快。此外,网站可以配置他们的构建,只在生产环境中处理node_modules
中的代码。在开发中,代码应该在开发者使用的任何浏览器上运行良好,特别是如果库作者遵循我上面给出的建议并针对Baseline Widely Available。
主要观点
- ES5 不再是构建工具或 JavaScript 库应该默认针对的目标。 如果工具仍然希望提供 ES5 支持,这应该是有特定支持需求的单个网站可以选择的。
- 构建工具和库不应该使用固定的浏览器支持策略。 这些策略很快就会过时,这导致了本文数据中突出的问题。浏览器支持决策应该由网站本身做出,而不是它使用的工具。一个好的浏览器支持策略是Baseline Widely Available。
- 导入第三方库的网站开发者应该将这些库作为其构建的一部分进行处理。 不能假设所有库作者都有与你相同的浏览器支持需求。正如本文数据所示,在许多情况下,网站开发者可能比他们导入的库有更广泛的浏览器支持需求(因此需要进一步转译它们)。
- 跨浏览器支持不应该完全依赖于你的构建工具来处理。 如果需要支持特定的一组浏览器,那么你需要测试你的网站以确保它在这些浏览器中正常工作。
参考
The State of ES5 on the Web