原文:https://blog.rafaelgss.dev/nodejs-2023-year-in-review
进入 2024 年已经两个月了,我想对 2023 年 Node.js 领域取得的成就做一个回顾。这份总结是由我个人挑选的,可能会漏掉一些贡献,因为 Node.js 的合作者们完成了大量的工作,有些领域如 WASI 我也需要更多的了解。
Node.js 正在迅速进化,对于日常用户来说,要跟上其最新的更新可能颇具挑战。即便是项目的开发者,也可能会忽略掉一些更新。因此,这篇文章旨在突出 2023 年 Node.js 生态中的重大变化和讨论。
作为参考,2023 年 nodejs/node#main 项目共有 2641 次代码提交。虽然这个数字看似微不足道,但对我来说,将其与历史数据对比非常有趣,这就是过去十年中主分支上提交次数的情况。
Between Jan 1 2023 ~ Jan 1 2024 = 2641
Between Jan 1 2022 ~ Jan 1 2023 = 2629
Between Jan 1 2021 ~ Jan 1 2022 = 2683
Between Jan 1 2020 ~ Jan 1 2021 = 3390
Between Jan 1 2019 ~ Jan 1 2020 = 3953
Between Jan 1 2018 ~ Jan 1 2019 = 4720
Between Jan 1 2017 ~ Jan 1 2018 = 4609
Between Jan 1 2016 ~ Jan 1 2017 = 3081
Between Jan 1 2015 ~ Jan 1 2016 = 2261
Between Jan 1 2014 ~ Jan 1 2015 = 1052
我使用了我自己创建的一个名为 nodejs-stats[1] 的小工具来获取这些信息。
看到像 Node.js 这样经过多年发展依然不断进化的成熟项目,真的非常令人钦佩。遗憾的是,我手上没有历史数据,但我强烈感觉到,今年 Node.js 吸引了许多首次贡献者。这部分原因可能归功于各种倡议,如 Grace Hopper 纪念日和不同的工作坊活动。比如,由我们团队在 2023 年的 NodeConfEU 上主持的“你的首次 Node.js 贡献”工作坊。我相信,项目的其他成员也在持续不断地帮助新贡献者 —— 如果你想为 Node.js 项目做出自己的第一份贡献,请随时联系我。我正在进行直播,指导人们熟悉 Node.js 的代码库。
2023 年,Node.js 发布了 102 个不同版本线的更新,包括了安全更新。
$ git log --all --grep=', Version' --pretty=format:"%cs,%aN,%s" --since='Jan 1 2023' --before='Jan 1 2024' | wc -l
102
年初时,Node.js 有四个主要的版本线在维护:
其中,Node.js 14、Node.js 16 和 Node.js 19 已经进入了生命周期末期(EOL),而 Node.js 18 则进入了维护阶段,不再有新的常规更新。
到了年末,活跃的版本线更新为:
因此,如果你还未使用上述版本中的任何一个,请尽快升级。
在都柏林举行的 Node.js 合作者峰会(NodeConfEU)上,我们分析了 Node.js 二进制文件的下载统计,发现用户更新版本的速度并不如预期。这强化了我们的观点:用户需要更快地更新他们的 Node.js 版本。
如图显示,即便是已经结束生命周期(EOL)的版本,仍然有大量下载,这对 Node.js 用户构成了风险,因为他们可能会在某个时刻使用到一个存在安全漏洞的版本。因此,我们开始考虑一个可能的解释:我们的版本发布节奏可能过快。目前,有一个关于调整发布节奏的新提议正在讨论中,尚待发布团队的评估,该提议建议将每年的主要版本发布频率从两次减少到一次。
在详细分析上述的下载量图表时,尤其是仔细观察 y 轴数据,你可能会得出一个结论,认为 Node.js 的下载量在 3 月 23 日达到了大约 6000 万的峰值。但这种解读并不完全准确,因为它忽略了 NodeSource 的分发统计数据,后者主要涵盖了生产环境中的二进制文件,并没有包括那些不再活跃的版本线(如 Node.js 8、10、12 等)的下载量。为了更全面地了解 Node.js 的分发机制,我建议你深入阅读《Node By Numbers 2021~2022》[2]一文,因为深入探讨这一主题已超出了本文的范畴。
@nodejs/releasers 在 Node.js 生态中扮演着至关重要的角色。他们保障了你设备上运行的版本的稳定性。为达成这一目的,我们部署了一套全面的测试,涵盖所有支持的架构,并且针对特定的更改执行特定测试,如针对 V8 引擎的测试。此外,对于每次发布,我们都会运行 CITGM(金丝雀在金矿中测试),这一过程会拉取 lookup.json 文件中列出的所有模块,并使用新的候选版本运行它们的测试套件。如遇任何问题,我们将进行深入调查,并有时需要模块作者的指导。
然而,CITGM 需要强大的计算资源来正确完成所有测试。遗憾的是,我们在这方面受到一定的限制,这导致了一些测试之间出现并发错误。这是因为某些测试必须并行执行,否则 CITGM 的完成可能需要很长时间。保持 lookup.json 文件中列出的模块最新是另一个挑战。有时候,一些模块可能会被存档,或许永远不会支持 Node.js 的新版本,或者它们可能本身就不太稳定(这种情况相当普遍)。
因此,我们经常看到一些措施,如:
如果不这么做,我们可能会遇到一个不可靠的 CITGM,使我们对可能的重大变化猝不及防。
首先,我要明确这篇文章只代表我的个人看法,并非代表整个项目。
作为项目的长期成员,及自 2022 年起加入技术指导委员会(TSC),我注意到项目现在比以往更开放于重大的改变。这其中包括一些看似不那么直接相关的发展,如将新的依赖整合至核心,以及开发新的内置模块。这使得 Node.js 作为一个平台的范围得到了扩展。然而,在我看来,这种新策略可能会带来关于维护和潜在安全风险的担忧。但从好的方面来看,它也增强了开发者的能力,并减少了使用恶意库的风险,尽管这可能会影响到原生端的性能。为大家提供一个参考,我一直在跟踪 Node.js 不同版本的二进制文件大小,显而易见,加入的新依赖和功能直接影响了其二进制文件的大小。
除非你的运行环境特别受限,否则 100MiB 的大小增加不应该是一个令人担忧的问题。
Node.js 将外部依赖打包进了其二进制文件中:
{
node: '21.6.0',
acorn: '8.11.3',
ada: '2.7.4',
ares: '1.20.1',
base64: '0.5.1',
brotli: '1.1.0',
cjs_module_lexer: '1.2.2',
cldr: '44.0',
icu: '74.1',
llhttp: '9.1.3',
modules: '120',
napi: '9',
nghttp2: '1.58.0',
nghttp3: '0.7.0',
ngtcp2: '0.8.1',
openssl: '3.0.12+quic',
simdjson: '3.6.3',
simdutf: '4.0.8',
tz: '2023c',
undici: '5.28.2',
unicode: '15.1',
uv: '1.47.0',
uvwasi: '0.0.19',
v8: '11.8.172.17-node.19',
zlib: '1.3.0.1-motley-40e35a7'
}
2023 年,Node.js 引入了三个新的依赖库:
这些库都致力于提升性能,正如你在《2023 年 Node.js 性能报告》[3]中看到的,它们使 Node.js 在性能改进方面达到了新的高度。
从 16 版本开始,Node.js 开始使用 quictls 团队提供的 OpenSSL 分支。这是为了将 QUIC 协议引入 Node.js 所必需的第一步。然而,与 OpenSSL 3.2.x 相比,OpenSSL 3.0.x 的性能显著较差。从 Node.js 的视角出发,迁移到 OpenSSL 3.2.x 面临两大挑战:
如果需要更多背景信息,请参考 #51152[4]。关于性能,你可以参考我的 GitHub 仓库 nodejs-bench-operations[5],该仓库提供了加密操作的性能基准。
如《Node.js 性能状况 2023》报告所述,Node.js 在性能方面持续稳步发展。本节不会深入探讨具体数值数据(这将在《Node.js 性能状况 2024》中详细报道),而是重点介绍那些在性能领域取得明显进步的倡议和 PR。
一个值得注意的进步是 libuv 升级至 1.45.0 版本。此版本在 Linux 上启用了 IO_URING,使得文件系统操作(如 read、write、fsync、fdatasync、stat、fstat 和 lstat)的吞吐量提高了 8 倍。更多详情见对应的 pull 请求:libuv/libuv#3952[6]。
此外,在 2023 年,我们为 Node.js 引入了新的 URL 解析器 Ada,现已在所有活跃的版本线(18、20 和 21)中提供。详细信息请见 pull 请求:nodejs/node#46410[7]。
2023 年发现两个重要的性能回归问题:
这些特性对于 Node.js 的某些使用场景至关重要。比如,使用 fetch() 时可能会依赖 WebStreams,或在使用任何应用性能监控(APM)工具时,应通过 AsyncLocalStorage
一个在 1 月份开始的倡议,在问题记录 #46265 中提出了一个替代方案,旨在实现不依赖于 AsyncHooks 的 AsyncLocalStorage。当时,AsyncHooks 被认为是性能瓶颈。与此相关的工作已经通过拉取请求 #46387 和 #48528 得到了执行。
2022 年,WebStreams 在 fetch 函数中被发现是性能瓶颈,正如问题评论中所强调的。自那以后,我们不断通过多个 PR 提升了其在 undici 中的应用效率,包括:
对于那些关注 Node.js 性能表现的人,我特别推荐关注 nodejs/performance[10] 仓库并参与他们的会议。务必关注性能标签,以获取更新,如 nodejs/node#49745[11] 和 nodejs/node#49834[12] 这样的 PR,它们旨在提高常规 Node.js 流的性能。
在2023年,Node.js 差点新增了一个原生的基准测试模块。我和我的同事 Vinicius Lourenco 一起提交了一个拉取请求,这个请求为 Node.js 引入了一个试验性的基准测试模块:通过 require('node:benchmark')
使用。
尽管这个请求引起了广泛的关注,但我们因为以下几个原因没有继续深入:
不过,这并不意味着我们就此放弃了!我们最终以 bench-node 的名称在 npmjs 上发布了这个模块 —— 是的,我们还没想到更好的名字。不妨去看看,并给它加个星标 ⭐ https://github.com/RafaelGSS/bench-node/。
2023 年,我主要致力于 Node.js 的安全领域。我与 OpenSSF 合作,全时间专注于 Node.js 安全性的开发与提升。在这一节中,我会简要介绍我们讨论过的主题、实施的功能、工作流程等。非常重要的一点是,我要向 Node.js 安全团队的所有成员表示衷心的感谢,感谢他们在过往及当前项目中的支持与帮助。同时,也要感谢 Node.js 审查团队在处理 HackerOne 报告方面给予我的帮助。对 Tobias Nießen 的特别感谢,他在发现和解决 Node.js 核心问题方面付出了巨大努力。
让我们从 2023 年我个人非常看重的一个安全里程碑谈起——这可能有点主观——Node.js 权限模型。这个项目最初是由 Anna Henningsen 和 James Snell提出的,但直到 2022/2023 年我才对其进行了重新实现和完善。如果你对这个功能背后的深层次原理感兴趣,我在 NodeConf EU 上做了一个关于它的演讲:“Node.js权限模型的发展之旅”[13]。
从技术角度讲,这个实验性功能允许你限制对以下环境资源的访问:
使用方法非常简单,仅需在启动Node.js进程时加上--experimental-permission
参数,并通过--allow-*
标志来指定允许的操作。例如,如果我想为我的应用入口点设置只读权限。
$ node --experimental-permission --allow-fs-read=./index.js index.js
因此,如果尝试从其他路径读/写,就会出现错误:
// index.js
const fs = require('fs')
const data = fs.readFileSync('/etc/passwd')
console.log(data.toString())
node:fs:581
return binding.open(
^
Error: Access to this API has been restricted
at Object.openSync (node:fs:581:18)
at Object.readFileSync (node:fs:460:35)
at Object.<anonymous> (/home/rafaelgss/index.js:3:17)
at Module._compile (node:internal/modules/cjs/loader:1378:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1437:10)
at Module.load (node:internal/modules/cjs/loader:1212:32)
at Module._load (node:internal/modules/cjs/loader:1028:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:142:12)
at node:internal/main/run_main_module:28:49 {
code: 'ERR_ACCESS_DENIED',
permission: 'FileSystemRead',
resource: '/etc/passwd'
}
Node.js v21.6.1
此外,还可以在官方文档[14]中找到相关信息。
在 2023 年,我们发布了更多安全更新,这主要得益于我们对第三方 CVE 的主动应对措施加强了。根据《Alpha-Omega Node.js 报告 2022》的描述,2022 年安全团队启动了若干倡议,这些倡议的成果在 2023 年得到了显现。这些倡议包括更新自动化和 Node.js 发布影响的自动化,这些都加快了我们团队响应漏洞的速度。
随着权限模型的引入,我们发现人们也开始关注 Node.js 从 11.8.0 版本起就已经存在的另一项实验性安全功能:策略机制。这项功能属于我们所定义的模块化权限范畴,我们在 2023 年修复了该功能中的数个漏洞。
安全更新如下:
在报告方面,我们对我们的威胁模型进行了一些明确的说明。实验性功能,如权限模型和策略机制,其严重性级别可以与任何稳定功能相媲美。因此,在审阅 Node.js 的安全更新时,请确保检查漏洞是否影响到你。我们常常修补的“高”危漏洞,往往只影响使用该功能的用户。
在 2023 年,大家讨论了给 Node.js 网站更新换代,让它焕然一新,具体讨论可参见此处。这个想法不仅在 TSC 会议上被多次提及,还在 Node.js Collab 峰会上得到了分享。这项更新是一项包含多个步骤的庞大工程,我想特别感谢 @nodejs/website 团队的每一个成员,为了这次更新他们付出了巨大的努力。详细信息如下:
即将推出的下载页面:
Draft PR: nodejs/nodejs.org#6353[19]
2023 年,Node.js 推出了多项新功能,由于篇幅限制,不可能在一篇文章中对每一项进行详尽的介绍。因此,本文将重点介绍一些重要的更新,欢迎大家深入探索!
--experimental-websocket
标志启用。在诊断领域,核心部分添加了一些重要的 PR:
HTTP 和 WHATWG 规范也有重要更新:
此外,针对 fetch 性能的分析发现 WebStreams 是主要瓶颈之一,2023 年对其进行了多项优化,包括状态错误的重用,这在特定基准测试中提高了 fetch 的性能约 23%。