2017 年,我写了一篇文章,向 Web 开发人员展示如何将 ES6+ 代码(即 ES2015)部署到生产环境中,而无需将其转换为 ES5。这项技术让网站开发人员可以自由地编写现代代码,而不必担心转译器或 polyfill 膨胀。
不幸的是,虽然许多网站开发人员能够使用这种技术,但大多数 JavaScript 库作者却不能。
库作者面临的限制比网站开发人员多得多,因为他们无法控制代码的部署方式。此外,由于许多流行的构建工具都建议开发人员将其目录排除node_modules
在转译之外,库作者被迫采取极其保守的做法——通常会一直转译回 ES5,以避免可能破坏网站。
但那是七年前的事了,从那时起,JavaScript 工具领域取得了巨大的进步。浏览器格局也发生了巨大变化。最值得注意的是,最后一款 ES5 浏览器 IE 11 将于 2022 年停止获得微软的支持,这意味着许多企业最终也可能停止支持它。
那么,目前 ES5 在网络上的现状如何?Web 开发人员在为生产环境编写代码时,最佳实践是什么?
本文将研究我们掌握的数据,以回答这些问题。它还针对网站开发人员和库作者今后应如何处理旧版浏览器支持提出了一些具体建议(基于这些数据)。
快速免责声明
在深入研究 ES5 使用情况的实际数据之前,我想澄清一下,编写或发布 ES5 代码本身没有任何问题。
JavaScript 引擎针对 ES5 代码的优化时间比对现代代码的优化时间长得多,因此如果您有仍在运行的旧 ES5 代码,则没有理由仅仅为了使其“现代”而更新它。
但是,如果您使用 ES6+ 语法编写代码,然后使用构建工具将其转换为 ES5,则通常会导致大量的 polyfill 和转译器膨胀,从而显著增加最终捆绑包的大小。
为了说明这一点,这里有一个例子:
console.log([1, 2, 3].at(-1));
如果你手动将此代码转换为 ES5,它可能看起来像这样:
var arr = [1, 2, 3];
console.log(arr[arr.length - 1]);
但是,如果您使用 Babel转译此单行代码并将其配置为添加 polyfill — — 即使您根据源代码中的使用情况将其限制为仅需要 polyfill — — 它包含71 个 core-js 依赖项,并且从 31 字节缩小到11,217 字节!
此示例的目的不是要羞辱 Babel 或 core-js。这些工具需要能够支持所有可能的 ES6+ 代码,这要求它们考虑各种边缘情况(尽管此特定示例没有任何边缘情况)。
相反,重点是强调选择支持旧版浏览器确实需要付出代价,而且这个代价可能是巨大的。
不幸的是,问题实际上比代码膨胀更严重。如果你看看下面关于当今流行网站如何实际转译和部署其代码到生产环境的数据,就会发现互联网上大多数网站都发布转译为 ES5 的代码,但仍然无法在 IE 11 中使用——这意味着 100% 的用户都在下载转译器和 polyfill 膨胀,但这些用户却一无所获。
数据
要了解 ES5 在 Web 上的现状,你必须关注三件事,因为它们对于我们作为 Web 用户收到的最终代码输出都起着至关重要的作用:
- 流行捆绑器和构建工具的默认配置
- 流行 JavaScript 库中的代码状态
- 网站所有者部署的代码状态
默认打包工具和构建工具配置
大多数打包工具和构建工具都具有极高的可配置性,几乎可以无限控制最终的代码输出。然而,在实践中,大多数开发人员只使用默认设置,因此默认设置非常重要。
这些默认值是什么?具体来说,默认值是否会导致代码被转译为 ES5?
为了回答这个问题,根据最新的 JS 状态调查(2023) ,我查看了一些最流行的构建工具生成的输出,大致按受欢迎程度排序:
工具 | 默认为 ES5? | 笔记 |
---|---|---|
Browserslist | 不 | 它本身不是构建工具,但被许多构建工具内部使用,并且是配置浏览器支持目标的最流行的开源工具。该[defaults](https://browsersl.ist/#q=defaults) 设置不再包括任何 ES5 浏览器。最后一个是 IE 11,它在 4.21 版中被标记为已死 。 |
Babel | 是的 | Babel 的文档建议设置一个[targets](https://babeljs.io/docs/options#targets) 选项(使用 Browserslist),但如果未指定,它会将所有代码转换为 ES5 。 |
webpack | 不 | 默认情况下,webpack 不会转译任何代码。大多数 webpack 用户都包含[babel-loader](https://webpack.js.org/loaders/babel-loader/) ,而 webpack 的使用示例 建议设置 targets: "defaults" 。 |
TypeScript (tsc) | 是的 | TypeScript 的默认[target](https://www.typescriptlang.org/tsconfig/#target) 选项是 ES5。 |
Next.js | 不 | Next.js使用 Babel 进行转译 ,并默认设置针对“现代浏览器” (即支持 ES 模块的浏览器)的 Browserslist 配置。 |
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 转译工具,因此在网络上转译为 ES5 仍然相当普遍(有关更多详细信息,请参阅下面的ES5 在野外使用情况)。
流行的JavaScript 库
除了研究流行的构建工具之外,我还研究了目前使用的一些最流行的库(同样基于JS 状态调查,大致按流行度排序):
为了测试每个库,我使用库文档中的一个代码示例创建了一个仅导入该特定库的包入口点。然后,我使用 Rollup 和 Webpack 打包代码以测试输出,看看它是否包含任何 ES6+ 语法(具体来说,IE 11 不支持的任何 ES6+ 语法)。
以下是我发现的内容:
图书馆 | 包含 ES6+ 语法? | 笔记 |
---|---|---|
Lodash | 不 | 仅限 ES5 |
React | 不 | 仅限 ES5 |
date-fns | 是的 | 箭头函数 |
three.js | 是的 | async/await、箭头函数、spread、解构 |
d3 | 是的 | 箭头函数、扩展、解构 |
Framer-motion | 是的 | 箭头函数、扩展、解构 |
greensock | 不 | 仅限 ES5 |
dayjs | 不 | 仅限 ES5 |
Zod | 是的 | async/await、箭头函数、spread、解构 |
RxJS | 是的 | 箭头函数 |
Immer | 是的 | 箭头函数、扩展、解构 |
luxon | 是的 | async/await、箭头函数、spread、解构 |
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
ES5 的实际应用
开发人员用于将 ES6+ 代码转换为 ES5 的三个主要工具是:
- Babel
- TypeScript (tsc)
- Closure Compiler(Google 内部称为 JSCompiler)
这三种工具都包含某种形式的 polyfill 和所谓的 ES5“辅助”函数,以避免最终输出出现重复。这些工具使用的最常见的 ES5 辅助函数库是:babel-helpers、core-js、regenerator-runtime、tslib和$jscomp。
这些辅助库中的许多函数都非常独特,因此可以通过查询 HTTP 存档来检测(即使在压缩代码中)哪些网站正在使用它们。搜索这些辅助函数是否存在(而不是标准 ES5 语法(例如 或非var
箭头function
))有助于区分手写旧 ES5 代码(通常经过了相当优化)和转译器生成的较新 ES5 代码(通常相当臃肿)。
我在 HTTP Archive 上进行了搜索,以了解热门网站(根据CrUX 受欢迎程度排名,排名前 10,000 )将这些帮助程序包含在部署到生产中的脚本包中的情况有多普遍。我还想看看网站提供未编译的 ES6+ 语法的情况有多普遍。
以下是我发现的(完整结果):
- 89% 网站提供至少 1 个包含未编译的 ES6+ 语法的 JavaScript 文件。
- 79% 网站提供至少 1 个包含 ES5 帮助代码的 JavaScript 文件。
- 68% 的网站提供至少 1 个 JavaScript 文件,该文件在同一个文件中__同时包含 ES5 辅助代码和未编译的 ES6+ 语法。
最后的发现有点让我震惊。
重申一下我之前说过的话(因为值得重复),如果浏览器不支持 ES6+ 语法(例如 IE 11),则在尝试加载包含 ES6+ 语法的脚本文件时会出错。如果浏览器_支持_ES6+ 语法,则它不需要任何 ES5 辅助代码或任何旧版 polyfill。完全没有理由同时包含这两者。
为了再次确认此查询的结果是否准确,我手动测试了列表中的 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 广泛可用功能。
如果您不熟悉 Baseline,那么这是 W3C 内部WebDX 社区小组的一项工作,旨在帮助开发人员轻松识别稳定且受到所有主流浏览器和浏览器渲染引擎(桌面和移动设备)良好支持的功能。如果某项功能已在所有四种主流浏览器的稳定版本中可用至少 30 个月,则该功能被视为Baseline 广泛可用。
以广泛可用基线之类的目标为目标的主要好处是它是一个移动目标,这意味着它不会停留在过去,就像以 ES5 为目标时发生的情况一样(以及目前esmodule
以 Next.js、Vite 和 Parcel 使用的目标为目标时发生的情况)。
库作者现在可以使用以下Browserslist查询(对于任何支持 Browserslist 的工具)配置他们的构建系统以定位Baseline 广泛可用功能:
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',
];
注意: 有一个开放的功能请求,要求向 Browserslist 添加 Baseline 支持,这将使上述查询简化为“基线广泛可用”。
如果某个网站需要支持比Baseline Widely Available所涵盖的更多的浏览器,这 100% 没问题。该网站始终可以配置其构建系统以进一步转译他们所导入的任何库。关键是,这个决定最好由网站开发人员而不是库作者来做出。
对于网站开发者来说
事实上,许多流行的网站在同一个脚本包中同时提供未编译的 ES6+ 语法和 ES5 帮助程序,这清楚地表明将目录排除在转译之外的做法node_modules
并不是一个好的做法。
自 2017 年以来,我一直在主张这不是一个好的做法,但与我交谈过的大多数开发人员都不想遵循这个建议,因为这样做会减慢他们的构建速度。
然而,如今,构建工具的速度已经大大提高。此外,网站可以配置其构建,以便在node_modules
构建生产时只处理代码。在开发过程中,代码应该可以在开发人员使用的任何浏览器上正常运行,特别是如果库作者采纳了我上面的建议并以基线广泛可用为目标。
最后的想法
正如我上面提到的,本文的目的不是根据这些发现指责或羞辱网站或工具作者。我还想确保大家清楚,我绝对不是建议网站继续支持 IE 11。无论如何,这些发现表明,支持 IE 11 对大多数企业来说并不是必需的,即使是拥有全球客户群的大型企业也是如此。