说明: 本文来自 Deno ,作者:Ryan Dahl, Bert Belder, and Bartek Iwańczuk

文章版权属于原网站/原作者。我依旧只是个搬运工+不称职的翻译。本文不翻译鸣谢部分。

Deno 1.0

动态语言是有用的工具。脚本允许用户快速高效的将复杂的系统连接在一起,并且在不需要关心诸如内存管理或者构建系统等细节中表达想法。最近几年,像 Rust 以及 Go 这样的语言,使机器代码更易生成;这些项目使得计算机基础架构获得了极大的发展。然而,我们认为拥有一个强大的脚本环境来处理各种问题依旧重要。

JavaScript 是最广泛使用的动态语言,运行于各种浏览器中。大量程序员精通 JavaScript,并且已经在优化其执行方面投入了大量精力。通过如 ECMA 这样的组织的建立,JavaScript 得到了谨慎并且持续的进步。我们相信 JavaScript 是动态语言工具的必然选择;无论是在浏览器环境还是作为独立进程。

我们是这块实践的先行者,Node.js 被证明是非常成功的平台。Node.js 足以构建 Web 开发工具、创建独立进程以及无数其他案例。在 2009 设计 Node 时,JavaScript 是截然不同的。出于必要,Node 发明了一些概念,这些概念随后由标准组织采用,并以不同的方式添加到语言中。在 《Node 的设计问题》 (译者注:安利这个演讲,一定要看哦。)演讲中,提供了更多细节。由于 Node 已经有大量的使用,使得这一系统发展变得困难并且缓慢。

随着 JavaScript 的更新,以及 TypeScript 的加入,构建 Node 项目可能会变得很艰难,涉及到管理构建系统和其他繁重的工具,这使动态语言丧失了乐趣。除此之外,通过 NPM 存储库从根本上集中了链接到外部库的机制,与 web 的理念并相冲突。

我们认为 JavaScript 和相关的软件基础架构已经发生了足够的变化,值得进行简化。我们寻求一种有趣且高效的脚本环境,可用于多种任务。

用于命令行脚本的 web 浏览器

Deno 是一种在 web 浏览器之外用于执行 JavaScript 和 TypeScript 的全新运行时(runtime)。

Deno 试图提供一个独立的工具来快速编写复杂功能的脚本。Deno 是(并且永远是)一个单一的可执行文件。正如 web 浏览器,它知道如何获取外部代码。在 Deno 中,单个文件无需任何其他工具即可定义任意复杂的行为。

1
2
3
4
5
import { serve } from "https://deno.land/std@0.50.0/http/server.ts";

for await (const req of serve({ port: 8000 })) {
req.respond({ body: "Hello World\n" });
}

在这里,只需一行代码,一个完整的 HTTP 服务器作为一个模块即添加完成。不需要额外的配置文件,也不要提前安装,只需要:

1
deno run example.js

与浏览器一致,代码默认运行于安全的沙盒中。代码无法访问硬件、打开网络连接或者是在没有许可的情况下进行任何潜在的恶意操作。浏览器提供了访问摄像头和麦克风的 API,但前提是通过用户的认证。Deno 在终端中提供类似的行为。上面的示例在没有标明 –allow-net 下会失败。

Deno 小心的不要偏离标准的浏览器 JavaScript API。当然,并不是每个浏览器 API 都与 Deno 相关,但无论它们在哪里,Deno 都不会偏离标准。

TypeScript 的默认支持

我们希望 Deno 适用于广泛的问题领域:从简单的一行代码,到复杂的服务端商业逻辑。随着程序变得越来越复杂,有一定的类型检测变得越来越重要。TypeScript 是 JavaScript 语言的扩展,允许用户选择性地提供类型信息。

Deno 在不需要任何外部工具下支持 TypeScript,内置 TypeScript 是其设计理念。deno types 命令是 Deno 提供的所有内容提供类型声明。Deno 的所有标准模块由 TypeScript 编写而成。

Promises 贯穿其中

在 javascript 之前 Node 设计时就引入了 Promises 或者是 async / await 的概念。Node 的 Promises 对应的是 EventEmitter,所有重要的 API 都基于这个,比如 sockets 和 HTTP。抛去对编程时使用 async / await 的好处来看,EventEmitter 存在背压(back-pressure)问题。拿 TCP 套接字(socket)来举例,套接字在接收到传入数据包时将发出“ 数据(data) ”事件。这些“ 数据 ”的回调将以不受限制的方式发出,从而使事件(events)充满整个过程。由于 Node 会持续接受数据事件,基础 TCP 套接字没有适当的背压,远程发送端并不知道服务端已经过载而继续发送数据。为了降低此风险,添加了 pause() 方法。解决了这个问题,但需要额外代码。由于只有在进程非常忙碌的时候,这个问题才会出现,导致 Node 程序充满了数据。结果是系统严重的尾延迟(tail latency)。

在 Deno 中,套接字依旧是异步的,但在接受新数据时,需要用户明确使用 read()。正确的构造接收套接字而不需要额外的暂停语义。这不是 TCP 套接字所独有的。系统的最底级别绑定层从根本上与 Promise 相关联,我们称之为“ ops ”。Deno 中所有的绑定是 Promises 的一种实现或提升。

Rust 有自己类似于 Promise 的抽象,称之为 Futures。通过 “ op ” 抽象,Deno 使基于 future 的 Rust API 巧妙的与 JavaScript 的 Promises 绑定在一起。

Rust API

我们提供的主要组件是 Deno 命令行界面(CLI)。今天(5 月 13日)CLI 正式发布了 1.0 版本。 Deno 并不是一个单一的程序,而是设计为 Rust 的集合,允许在不同层级的集成。

deno_core 是 Deno 的基础,它不依赖 TypeScript 和 Tokio。它简单的提供了 Op 和 Resource 模块。deno_core 提供了绑定 Rust futures 和 JavaScript promises 的组织方式。因此 CLI 完全建立在 deno_core 之上。

rusty_v8 为 Rust 和 V8 的 C++ API 提供了高质量的绑定。这个 API 试着还原所有原先 C++ API。这是零成本的绑定,Rust 暴露出的对象正是 C++ 的操作的对象(举个🌰,之前 Rust V8 的绑定尝试强制使用持久句柄(handles))。这个模块提供了建立于 Github Actions CI 之上的二进制代码,同样也允许用户从头编译 V8 并调整它的构建配置。所有 V8 源代码均在此模块进行分发。最后,rusty_v8 试着成为一个安全的接口。现在还没有 100% 完成,但我们在逐步接近于此。能够以一种安全的方式与像 V8 这样复杂的 VM 交互是非常令人惊奇的,这让我们发现 Deno 本身存在许多困难的bug。

稳定性

我们承诺在 Deno 中维护一个稳定的 API。Deno 有大量的接口和模块,所以对我们来说的”稳定”保持透明是很重要的。我们创建用于与操作系统交互的 JavaScript API 都可以在“ Deno ”命名空间中找到(例如 Deno.open())。这些已经被仔细检查过了,我们定会对它们做向后兼容的更改。

在使用 –unstable 命令行标志之前,所有还没有稳定的方法将会被隐藏。你可以在查看没有稳定的接口。在接下来的时间中,其中的 API 也会逐渐稳定下来。

在全局命名空间中,你可以发现各种其他对象(例如 setTimeout()fetch())。我们努力保持这些接口和浏览器中一致,但是如果发现意外的不兼容性,我们将进行更正。浏览器标准定义了自身的接口,而不是我们。任意错误的更正只会是修复问题,而不是更新接口。如果与浏览器的标准 API 不兼容,可能会在大版本发布之前进行修复。

Deno 还有很多 Rust API,比如 deno_core 和 rusty_v8 模块。这些 API 都还没有达到1.0。我们会继续对它们进行迭代。

局限性

需要了解的是 Deno 并不是 Node 的一个翻版,它是一种全新的实现。Deno 只是开发了两年,而开发 Node 已经超过十年。基于到对 Deno 的兴趣,我们希望它能继续发展和成熟。

对于一些应用来说,Deno 是一种好的选择,对于其他的则不是。这取决于需求。我们想说明 Deno 的局限性来帮助人们选择 Deno 。

兼容性

不幸的是,令人沮丧是许多用户会发现与现有 JavaScript 工具缺乏兼容性。Deno 通常与 Node(npm)包不兼容。初步建立在 https://deno.land/std/node/,但还远远没有完善。尽管 Deno 采取了强硬的方法来简化模块系统,最终 Deno 和 Node 是有着相似的目标和相似的系统。随着时间的推移,我们希望 Deno 可以开箱即用的运行比 Node 更多的系统。

HTTP 服务器性能

我们持续追踪 Deno HTTP 服务器的性能。每秒 25000 次使用 Deno HTTP 服务器请求 hello-world 最多延迟 1.3 毫秒。类似的 Node 程序每秒请求 34000 次,而最大的延迟在 2 ~ 300 毫秒之间。

Deno 的 HTTP 服务器是在原生 TCP 套接字之上的 TypeScript 中实现的。Node 的 HTTP 服务器是由 C 语言编写的,并暴露给 JavaScript 的高级绑定。我们阻止了将本地 HTTP 服务器绑定到 Deno 的冲动,因为我们想优化 TCP 套接字层,以及更普遍的 op 接口。

Deno 是适当的异步服务器,能应付每秒 25000 次的请求,可以满足大多数的需求。(如果不满足,JavaScript 可能并不是最好的选择。)不仅如此,我们预计,由于普遍使用了Promise (之前已经讨论过了),Deno 在尾延迟方面表现得更好。综上所述,我们的确相信该系统还有更多的性能优势,希望在将来的版本中实现这一目标。

TSC 的瓶颈

Deno 在内部使用微软的 TypeScript 编译器来进行类型检测和生成 JavaScript 。与使用 V8 直接编译 JavaScript 相比,这样做非常缓慢。之前,我们想使用“ V8 快照(V8 Snapshots)”来大幅度提升性能。快照当然有帮助,但它仍然很慢。我们认为可以在现有 TypeScript 编译器的基础上进行一些改进,但是很明显,类型检查最终需要在Rust中实现。这是个艰巨的任务,可能在短时间内不会发生;但它可以让开发人员在经历关键路径中提供数量级上的性能改进。TSC 需要移植到 Rust上。如果你对这个问题感兴趣,请联系我们。

插件 / 挂件

我们有一个使用自定义 ops 来对 Deno 运行时进行初步扩展的插件系统。这个接口还在开发中,已经标记为非稳定版。因此,访问 Deno 提供的系统以外的本地系统是很困难。