Deno 1.0 版本正式发布

2020-05-13

Ryan Dahl, Bert Belder, and Bartek Iwańczuk

动态语言是有用的工具。 脚本编写使用户可以快速 简洁地将复杂的系统连接在一起并表达想法,而不必担心 诸如内存管理或构建系统之类的细节。 近年来, 像 Rust 和 Go 这样的编程语言使生产复杂的 本机代码变得更加容易。 这些项目是计算机基础架构中极为重要的发展。 但是,我们断言拥有一个能够解决各种问题领域的强大脚本环境仍然很重要。

JavaScript 是使用最广泛的动态语言,可通过 Web 浏览器在每台设备上运行。 大量的程序员精通 JavaScript,并且 已经在优化其执行方面投入了大量精力。 通过 像 ECMA International 这样的标准组织,对语言进行了认真、 不断的改进。 我们相信 JavaScript 是动态语言工具的自然选择; 无论是在浏览器环境中还是独立使用。

我们在该领域的原始事业,Node.js,被证明是一个非常成功的 软件平台。 人们发现它对于构建 Web 开发工具、 构建独立的 Web 服务器以及许多其他用例很有用。 但是,Node 是在 2009 年设计的,当时 JavaScript 是一种非常不同的 语言。 出于必要,Node 必须发明概念,这些概念后来被 标准组织采纳,并以不同的方式添加到语言中。 在 Design Mistakes in Node 演示文稿中,对此 进行了更详细的讨论。 由于 Node 拥有大量用户, 因此发展该系统既困难又缓慢。

随着 JavaScript 语言的不断变化以及诸如 TypeScript 之类的新成员的加入, 构建 Node 项目可能会成为一项艰巨的工作,涉及管理构建 系统和其他繁重的工具,这些工具剥夺了 动态语言脚本的乐趣。 此外,链接到外部库的机制基本上是 通过 NPM 存储库集中的, 这不符合 Web 的理想。

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

A Web Browser for Command-Line Scripts

Deno 是一个新的运行时,用于在 Web 浏览器之外执行 JavaScript 和 TypeScript。

Deno 试图提供一个独立的工具来快速编写复杂功能的脚本。 Deno 是(并将始终是)单个可执行文件。 就像网络 浏览器一样,它知道如何获取外部代码。 在 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 服务器模块作为依赖项添加到一行中。 没有额外的配置文件,没有安装, 只有 deno run example.js

与浏览器一样,默认情况下,代码在安全的沙箱中执行。 未经允许,脚本无法访问硬盘驱动器、打开网络连接或进行任何其他 潜在的恶意操作。 浏览器提供了 用于访问相机和麦克风的 API,但用户必须首先授予其权限。 Deno 在终端中提供类似的行为。 除非设置 --allow-net 命令行标志,否则以上示例将失败。

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

TypeScript Support

们希望 Deno 适用于广泛的问题领域:从小型 单行脚本到复杂的服务器端业务逻辑。 随着程序变得 越来越复杂,具有某种形式的类型检查变得越来越重要。 TypeScript 是 JavaScript 语言的扩展,允许用户 选择提供类型信息。

Deno 无需其他工具即可支持 TypeScript。 运行时在设计时 考虑了 TypeScript。 deno types 命令为 Deno 提供的所有内容提供类型声明。 Deno 的标准模块全部使用 TypeScript 编写。

Promises All The Way Down

Node 是在 JavaScript 具有 Promises 和 async/await 概念之前设计的。 与 Promises 对应的是 Node 的 EventEmitter,很多重要的 API, 例如 sockets 和 HTTP,就是基于其构建的。 除了 asyn/await 的工程学优势外, EventEmitter 模式还存在背压问题。 以 TCP sockets 为例。sockets 在收到传入数据包时将发出 "data" 事件。 这些 "data" 事件的回调将以不受限制的方式发出, 从而使事件充满整个过程。 由于 Node 继续接收新的 数据事件,因此底层的 TCP sockets 没有适当的背压, 因此远程发送方不知道服务器已超负荷并继续发送数据。 为了缓解此问题,添加了 pause() 方法。 这可以解决 问题,但是需要额外的代码; 而且由于泛洪问题仅在 进程非常繁忙时才会出现,因此许多 Node 程序都可能被 数据淹没了。 结果导致系统范围的尾部延迟。

在 Deno 中,sockets 仍然是异步的,但是接收新数据需要用户 明确调用 read()。 正确构造接收 sockets 不需要额外的暂停语义。 这不是 TCP sockets 所独有的。 系统的最低 绑定层从根本上与 promise 相关联 - 我们称 这些绑定为 "ops"。 Deno 中以某种形式出现的所有回调均来自 promise。

Rust 有其自己的类似于 promise 的抽象功能,称为 Futures。 通过 "op" 抽象,Deno 使将 Rust Futures 的 API 绑定到 JavaScript promise 中 变得容易。

Rust APIs

The primary component that we ship is the Deno command-line interface (CLI). The CLI is the thing that is version 1.0 today. But Deno is not a monolithic program, but designed as a collection of Rust crates to allow integration at different layers.

The deno_core crate is a very bare bones version of Deno. It does not have dependencies on TypeScript nor on Tokio. It simply provides our Op and Resource infrastructure. That is, it provides an organized way of binding Rust futures to JavaScript promises. The CLI is of course built entirely on top of deno_core.

The rusty_v8 crate provides high quality Rust bindings to V8's C++ API. The API tries to match the original C++ API as closely as possible. It's a zero-cost binding - the objects that are exposed in Rust are exactly the object you manipulate in C++. (Previous attempts at Rust V8 bindings forced the use of Persistent handles, for example.) The crate provides binaries that are built in Github Actions CI, but it also allows users to compile V8 from scratch and adjust its many build configurations. All of the V8 source code is distributed in the crate itself. Finally rusty_v8 attempts to be a safe interface. It's not yet 100% safe, but we're getting close. Being able to interact with a VM as complex as V8 in a safe way is quite amazing and has allowed us to discover many difficult bugs in Deno itself.

Stability

We promise to maintain a stable API in Deno. Deno has a lot of interfaces and components, so it's important to be transparent about what we mean by "stable". The JavaScript APIs that we have invented to interact with the operating system are all found inside the "Deno" namespace (e.g. Deno.open()). These have been carefully examined and we will not be making backwards incompatible changes to them.

All functionality which is not yet ready for stabilization has been hidden behind the --unstable command-line flag. You can see the documentation for the unstable interfaces here. In subsequent releases, some of these APIs will be stabilized as well.

In the global namespace you'll find all sorts of other objects (e.g. setTimeout() and fetch()). We've tried very hard to keep these interfaces identical to those in the browser; but we will issue corrections if we discover inadvertent incompatibilities. The browser standards define these interfaces, not us. Any corrections issued by us are bug fixes, not interface changes. If there is an incompatibility with a browser standard API, that incompatibility may be corrected before a major release.

Deno also has many Rust APIs, namely the deno_core and rusty_v8 crates. None of these APIs are at 1.0 yet. We will continue to iterate on them.

Limitations

It's important to understand that Deno is not a fork of Node - it's a completely new implementation. Deno has been under development for just two years, while Node has been under development for over a decade. Given the amount of interest in Deno, we expect it to continue to evolve and mature.

For some applications Deno may be a good choice today, for others not yet. It will depend on the requirements. We want to be transparent about these limitations to help people make informed decisions when considering to use Deno.

Compatibility

Unfortunately, many users will find a frustrating lack of compatibility with existing JavaScript tooling. Deno is not compatible, in general, with Node (NPM) packages. There is a nascent compatibility layer being built at https://deno.land/std/node/ but it is far from complete.

Although Deno has taken a hardline approach to simplifying the module system, ultimately Deno and Node are pretty similar systems with similar goals. Over time, we expect Deno to be able to run more and more Node programs out-of-the-box.

HTTP Server Performance

We continuously track the performance of Deno's HTTP server. A hello-world Deno HTTP server does about 25k requests per second with a max latency of 1.3 milliseconds. A comparable Node program does 34k requests per second with a rather erratic max latency between 2 and 300 milliseconds.

Deno's HTTP server is implemented in TypeScript on top of native TCP sockets. Node's HTTP server is written in C and exposed as high-level bindings to JavaScript. We have resisted the urge to add native HTTP server bindings to Deno, because we want to optimize the TCP socket layer, and more generally the op interface.

Deno is a proper asynchronous server and 25k requests per second is quite enough for most purposes. (If it's not, probably JavaScript is not the best choice.) Furthermore, we expect Deno to generally exhibit better tail latency due to the ubiquitous use of promises (discussed above). All that said, we do believe there are more performance wins to be had in the system, and we hope to achieve that in future releases.

TSC Bottleneck

Internally Deno uses Microsoft's TypeScript compiler to check types and produce JavaScript. Compared to the time it takes V8 to parse JavaScript, it is very slow. Early on in the project we had hoped that "V8 Snapshots" would provide significant improvements here. Snapshots have certainly helped but it's still unsatisfyingly slow. We certainly think there are improvements that can be done here on top of the existing TypeScript compiler, but it's clear to us that ultimately the type checking needs to be implemented in Rust. This will be a massive undertaking and will not happen any time soon; but it would provide order of magnitude performance improvements in a critical path experienced by developers. TSC must be ported to Rust. If you're interested in collaborating on this problem, please get in touch.

Plugins / Extensions

We have a nascent plugin system for extending the Deno runtime with custom ops. However this interface is still under development and has been marked as unstable. Therefore, accessing native systems beyond that which is provided by Deno is difficult.

Acknowledgements

Many thanks to the many contributors who helped make this release possible. Especially: @kitsonk who has had a massive hand in many parts of the system, including (but not limited to) the TypeScript compiler host, deno_typescript, deno bundle, deno install, deno types, streams implementation. @kevinkassimo has contributed countless bug fixes over the whole history of the project. Among his contributions are the timer system, TTY integration, wasm support. Deno.makeTempFile, Deno.kill, Deno.hostname, Deno.realPath, std/node's require, window.queueMircotask, and REPL history. He also created the logo. @kt3k implemented the continuous benchmark system (which has been instrumental in almost every major refactor), signal handlers, the permissions API, and many critical bug fixes. @nayeemrmn contributes bug fixes in many parts of Deno, most notably he greatly improved the stack trace and error reporting, and has been a forceful help towards the stabilizing the APIs for 1.0. @justjavac has contributed many small but critical fixes to align deno APIs with web standards and most famously he wrote the VS Code deno plugin. @zekth has contributed a lot of modules to std, among them the std/encoding/csv, std/encoding/toml, std/http/cookies, as well as many other bug fixes. @axetroy has helped with all things related to prettier, contributed many bug fixes, and has maintained the VS Code plugin. @afinch7 implemented the plugin system. @keroxp implemented the websocket server and provided many bug fixes. @cknight has provided a lot of documentation and std/node polyfills. @lucacasonato built almost the entire deno.land website. @hashrock has done a lot of amazing artwork, like the loading page on doc.deno.land and the lovely image at the top of this page!

One last thing

Consider supporting this open source software work by pre-ordering a Deno v1.0 hoodie:

deno 1.0 hoodieOrder here →