node-fibers 的停止维护

Natalie Weizenbaum 于 2021 年 3 月 26 日发布

我们最近收到了一个不幸但并不完全令人意外的消息,即node-fibers 包已达到其生命周期终点,并且不会更新以兼容 Node 16。Dart Sass 历来允许 JavaScript 用户传入 node-fibers 以提高异步 render() 方法的性能,但未来在 Node 16 及更高版本中,这将不再是一种选择。

有许多替代方案可以恢复这种损失的性能,其中一些方案现已可用,一些正在开发中,还有一些方案是理论上的,但可以通过像您这样的用户的拉取请求变为现实。遗憾的是,目前没有一个现成的方案可以像 node-fibers 一样易于使用,因此,如果性能对您至关重要,我们建议暂时继续使用 Node 14。

发生了什么?发生了什么?永久链接

为了理解我们是如何走到这一步的,了解两段历史很重要。首先,Dart Sass 为什么首先使用 node-fibers?其次,node-fibers 为什么会停止维护?

本节内容比较技术性,如果您不关心详细内容,可以跳过

Sass 中的 FibersSass 中的 Fibers永久链接

Dart Sass 从现已弃用的 Node Sass 继承了其JavaScript API。此 API 具有两个主要功能用于编译 Sass 文件:renderSync() 同步返回编译后的 CSS,以及 render(),它采用一个回调函数,并将编译后的 CSS 异步传递给该回调函数。只有 render() 允许异步插件,包括广泛使用的导入器,如 webpack 的 sass-loader,因此 render() 在实践中得到了广泛应用。

对于 Node Sass,render()renderSync() 之间的性能差异可以忽略不计,因为它构建在 C++ 代码之上,对如何处理异步性几乎没有限制。但是,Dart Sass 以纯 JavaScript 运行,这使其受到 JavaScript 严格的异步规则的约束。JavaScript 中的异步性是具有传染性的,这意味着如果任何函数(例如导入器插件)是异步的,那么调用它的所有内容都必须是异步的,依此类推,直到整个程序都变成异步的。

而且,JavaScript 中的异步性并不是免费的。每个异步函数调用都必须分配回调函数,将它们存储在某个地方,并在调用这些回调函数之前返回到事件循环,所有这些都需要时间。事实上,它花费的时间足够长,以至于 Dart Sass 中的异步 render() 往往比 renderSync() 慢 2-3 倍。

引入 Fibers。Fibers 是一个非常酷的概念,在 Ruby 和 C++ 等语言中可用,它使程序员能够更好地控制异步函数。它们甚至可以允许一段同步代码(例如 Sass 编译器)调用异步回调函数(例如 webpack 插件)。node-fibers 包对 V8 虚拟机进行了一些神秘的操作来在 JavaScript 中实现 Fibers,这使得 Dart Sass 可以使用快速同步代码来实现异步 render() API。有一段时间,它非常棒。

Fibers 的消亡Fibers 的消亡永久链接

不幸的是,node-fibers 使用的神秘操作涉及访问 V8 的一些不是其公共 API 的正式组成部分的内容。不能保证它们使用的接口在版本之间保持不变,事实上,它们往往会定期更改。很长一段时间以来,这些更改都足够小,以至于可以发布一个支持这些更改的新版本的 node-fibers,但在 Node.js 16 中,运气用完了。

最新版本的 V8 涉及对其内部结构的一些重大修改。这些修改最终将使其能够实现一些很酷的改进,因此很难抱怨,但副作用是 node-fibers 使用的 API 完全消失了,也没有明显的替代方案。这不是任何人的错:由于这些接口不是 V8 公共 API 的一部分,因此它们没有义务保持其稳定性。有时在软件开发中,事情就是这样。

恢复性能恢复性能永久链接

有一些方法可以找回由于无法再将 node-fibers 传递给 sass.render() 而导致的性能损失。按从最近到最长期的顺序排列

避免异步插件避免异步插件永久链接

这是您今天可以做的事情。如果可以将您传递给 Sass 的插件设为同步的,则可以使用 renderSync() 方法,该方法不需要 Fibers 即可快速运行。这可能需要重写一些现有的插件,但会立即带来好处。

嵌入式 Dart Sass嵌入式 Dart Sass永久链接

虽然它还没有准备好投入使用,但 Sass 团队正在开发一个名为“嵌入式 Dart Sass”的项目。这涉及将 Dart Sass 作为子进程运行,而不是库,并使用特殊协议与之通信。与现有的替代方案相比,这提供了几个重要的改进

  • 与从命令行运行 sass 不同,这仍然可以使用 webpack 导入器等插件。事实上,我们计划尽可能地匹配现有的 JavaScript API。这可能会使异步插件的运行速度同步插件更快

  • 与现有的 JS 编译版本不同,此版本将使用 Dart VM。由于 Dart 语言的更静态特性,Dart VM 运行 Sass 的速度大大快于 Node.js,这将为大型样式表带来约 2 倍的速度提升。

嵌入式 Sass 的 Node.js 主机仍在积极开发中,但如果您想试用,可以下载 beta 版本(功能有限)。

Worker 线程Worker 线程永久链接

我们探讨了在 Node.js Worker 线程中运行纯 JS Dart Sass 的可能性。Worker 线程的工作方式有点像 Fibers,它们使同步代码能够等待异步回调函数运行。不幸的是,它们对于可以在线程边界之间传递的信息类型也极其严格,这使得使用它们来包装像 Sass 这样的复杂 API 变得更加困难。

目前,Sass 团队专注于嵌入式 Sass,因此我们没有足够的带宽将 Worker 线程作为替代方案进行深入研究。也就是说,我们很乐意帮助一个有动力的用户实现这一点。如果您有兴趣,请在GitHub issue上跟进!

重新激活 node-fibers重新激活 node-fibers永久链接

还有一个潜在的解决方案,尽管将其变成现实需要真正的奉献精神。原则上,可以向 V8 添加一个新的 API,该 API正式支持 node-fibers 做好工作所需的钩子。这将允许该包光荣地恢复生机,并使 Sass 能够在未来使 render() 保持快速运行。

Sass 团队已联系了 V8 团队和 node-fibers 的所有者,他们原则上都赞同这个想法。虽然他们都没有时间自己完成它,但他们表示愿意帮助一个愿意尝试的工程师。

但这并不是一项适合胆小者贡献的任务:它需要 C++ 知识,愿意学习至少 node-fibers 代码库和 V8 的隔离 API 的基础知识,以及 API 设计和人际交往方面的技能,以协商一个稳定的 API,以满足 node-fibers 的需求以及 V8 团队感到舒适承诺维护的需求。但如果您有兴趣,请不要犹豫联系我们