Deno 钻研之术

译《Deno v1.9 发布说明》

今天我们发布了 Deno v1.9.0。此版本包含了许多新功能、性能优化以及 Bug 修复:

  • 原生 HTTP/2 Web 服务器:Deno 下的一个快速、准确、功能完整的 HTTP 服务器。
  • 使用 serde_v8 更快地调用 Rust:将我们的 op 基准开销优化了 98%。
  • Blob URL 支持与 **fetch** 改进:新的 Web 兼容性功能。
  • LSP 中 import 的支持更完整:本地、远程和注册表的支持现可以再次使用。
  • 交互式权限提示框:以交互式提示框来请求权限而无须预先声明。

如果你已经安装了 Deno,可以通过 deno upgrade 命令来更新到 1.9 版本。如果你是第一次体验 Deno,你可以尝试使用如下命令之一:

# Using Shell (macOS and Linux):
curl -fsSL https://deno.land/x/install/install.sh | sh

# Using PowerShell (Windows):
iwr https://deno.land/x/install/install.ps1 -useb | iex

# Using Homebrew (macOS):
brew install deno

# Using Scoop (Windows):
scoop install deno

# Using Chocolatey (Windows):
choco install deno

原生 HTTP/2 Web 服务器§

目前 Deno 下的 HTTP 服务器 std/http,是在 TCP 套接字顶部用纯 TypeScript 实现。尽管用了脚本化的 HTTP 服务器,但其依然具有良好的尾部延迟。但是 std/http 的主要缺点在于,它仅仅是 HTTP/1.1,没有提供通向 HTTP/2 的便捷方法。

最终,我们不想忙于编写 HTTP 服务器的工作。HTTP 变得越来越重要,并且在底层代码里已经存在了良好实现的 HTTP 服务器。

因此,我们使用 Hyper 来在 Deno 中构建新的原生 HTTP/2 服务器 API。

与纯 TypeScript 编写的 HTTP 服务器 std/http 相比,该绑定将 http-world 的吞吐量提高了 48%。

我们希望尽快稳定这个新的 API,但是现在你必须使用 --unstable 标志。欢迎进行测试并给我们提供反馈。

const body = new TextEncoder().encode("Hello World");
for await (const conn of Deno.listen({ port: 4500 })) {
  (async () => {
    for await (const { respondWith } of Deno.serveHttp(conn)) {
      respondWith(new Response(body));
    }
  })();
}

我们已经很谨慎地使用与 fetch() API 相同的 RequestResponse 对象。Rust 和 Response 对象都具有流体(streamable bodies),允许与客户端进行全双工通信。

另请参考下文关于 ALPN 的部分,这是通过 TLS 发布 HTTP/2 所必需的。

使用 serde_v8 更快地调用 Rust§

我们已经将绑定基础设施重建得更加简单和快捷:我们从核心中删除了 1500 多行代码,将基线绑定(又称为 ops 或 opcall)的开销提高了约 65 倍或 -98%,并且建立了干净的 op 基础,可以为我们的发展提供良好的基础(比如用于插件和未来的优化等)。

在 Deno 的早期版本中,opcalls 遵循请求/响应模式,在 ArrayBuffer 的自定义“有效负载”中对其数据进行编码。此前,这些有效负载使用范围从 JSON、flatbuffers 到自定义二进制编码的各种编码...这不仅成为性能瓶颈,也成为复杂性和碎片化的重要起源。

@AaronO 建议,与其在这些二进制格式(JS 和 Rust)之间来回序列化,不如直接更有效率地在 v8 和 Rust 值之间进行序列化。在此建议和一个快速的原型设计下,serde_v8 诞生。serde_v8 旨在在 v8 和 Rust 之间提供“最大效率”或“零开销”的双射,同时保持表达性和准确性(因为它建立在 David Tolnay 奇妙的 serde 库之上)。

基线 op 开销是衡量给定类 opcall 的最小开销 (单位为每个调用的纳秒) 的一个重要基准。

这些 op-layer 的改进不仅仅是学术上的,还大大提升了 Deno 的效率,也提高了我们在 HTTP 基准测试的吞吐量和延迟。你可以在你自己的 Deno 程序中看到在重负荷下或者是以前遇到的 opcall 效率瓶颈时的改进。

如上所见,Deno 中很多常用的功能现在都可以快 ~3 倍地运行。

支持 Blob URL 与改进 fetch§

此版本下,我们引入了 blob:(又称 object URLs)——一个与浏览器中相同的,可以用来创建和撤销 blob URL 的 API:

const blob = new Blob(["Hello World!"]);
const url = URL.createObjectURL(blob);
console.log(url); // blob:null/7b09af21-03d5-461e-90a3-af329667d0ac

const resp = await fetch(url);
console.log(await resp.text()); // Hello World!

URL.revokeObjectURL(url);

Blob URLs 可用于 fetch,使用 new Worker 来实例化 Web Worker,以及动态导入(使用import())。

除了 blob URLS,fetch 现在还支持 data URLs:

const resp = await fetch("data:text/plain;base64,SGVsbG8gV29ybGQh");
console.log(await resp.text()); // Hello World!

LSP 中 import 的支持更完整§

此版本中,还为 Deno Language Server(一个为 Deno 提供编辑器拓展功能的工具)添加了一些很棒的新功能和改进。

首先,我们从旧的 VS Code 拓展中改进并重新引入了导入补全功能。它允许用户在 import 语句中获得补全。LSP 提供本地文件的补全、已经下载到 DENO——DIR 缓存的文件以及注册表的补全。

这是功能的示例:

想要开启 https://deno.land/x 仓库的补全功能,需要在你的 VS Code(或其它编辑器) 中添加如下配置:

{
  "deno": {
    "suggest": {
      "imports": {
        "hosts": {
          "https://deno.land": true
        }
      }
    }
  }
}

注册表自动补全功能目前由 https://deno.land/x 提供。我们希望更多的其它注册表会根据注册表协议来支持这个功能。Skypack 注册表显示出了相关兴趣,并且可能很快会得到支持。如果要添加对自己注册表的支持,则可以阅读注册表实现文档

除了新的导入补全功能之外,我们还实现了 textDocument/foldingRangetextDocument/selectionRange 的 LSP 函数,使你的编辑器可以再选择期间提供更好地文本捕捉,并更好地支持折叠和拓展代码块。

此版本还包含许多针对 LSP 的错误修复,其中一个特殊的缺陷是 Windows 系统上的一个讨厌的错误:当 LSP 遇到特定的 file:// URL时,会导致 LSP 崩溃。

--allow-env--allow-run 的白名单§

Deno 的多个权限标志接收一个允许列表,使得程序权限可以再细粒度地得到控制。例如,使用 --allow-read=/tmp 仅授予对 /tmp 目录的读取权限。

在 1.9 之前的版本中,--allow-env--allow-run 都是全部开启或全部关闭,这意味着通过这些标志将授予对环境变量的完全访问权限,并且可以分别为系统中的任何二进制文件生成子进程。

现在可以精确地指定程序应该访问哪些环境变量,或者允许程序产生哪些子进程:

$ deno run --allow-env=DEBUG,LOG https://deno.com/v1.9/env_permissions.ts
$ deno run --allow-run=deno https://deno.com/v1.9/run_permissions.ts

此外,Deno.permissions.query() 现在允许使用命令字段查询执行特定二进制文件的权限:

await Deno.permissions.query({ name: "run", command: "deno" });

交互式权限提示框§

当前在 Deno 中,如果你运行的程序确实相应的权限标志,它将抛出错误并退出。在 1.9 中,我们添加了 --pormpt 标志,允许用户迭代地授予运行时所需的权限。

当从 Internet 运行一次性脚本时,使用 --prompt 尤其有用:你无需预先知道所有必需的权限,而是可以在没有任何权限的情况下运行脚本,并根据程序的请求逐一授予或拒绝。

尝试运行这个示例:deno run --prompt ``[https://deno.com/v1.9/prompt_permissions.ts](https://deno.com/v1.9/prompt_permissions.ts)

如果 --prompt 对你有用,请告诉我们,我们正在考虑在将来的版本中默认将其打开。

Deno.listenTls 中的 ALPN 支持§

HTTP/2 协议与连接无关。因此,它可以用于 Unix 套接字、TCP 套接字或者使用 TLS 的连接。主流浏览器只允许在 TLS 握手过程中宣布支持 HTTP/2 的 TLS 连接。它通过“应用层协议协商”TLS 扩展来实现,也被称为 ALPN。这种对 TLS 握手的扩展允许 TLS 服务器和客户端就它们将使用哪种应用协议来进行 TLS 连接通信进行协商。HTTP/1.1 和 HTTP/2 是网络上两个主要的应用协议。这些协议的 ALPN 名称分别为“http/1.1”和“h2”。浏览器只会将 HTTP/ 2 请求发送到声明支持 HTTP/2 的服务器。如果没有列出 ALPN 协议,或者在 ALPN 协议中只列出了“http/1.1”,则将使用 HTTP/1.1。

迄今为止,std/http 服务器仅支持 HTTP/1.1,因此无需支持 TLS 连接上的 ALPN。当Deno.serviceHttp 在这一版本中引入时,事情发生了变化。要在 Deno 中实现完全的 HTTP/2,我们现在添加了对指定 ALPN 协议的支持,当 TLS 侦听器通过 Deno.ListentLs 启动时,它将进行公告。

下面是一个创建完全支持 HTTP/2 的 HTTPS 服务器的例子:

const listener = Deno.listenTls({
  port: 443,
  certFile: "./cert.pem",
  keyFile: "./key.pem",
  alpnProtocols: ["h2", "http/1.1"],
});

for await (const conn of listener) {
  handleConn(conn);
}

async function handleConn(conn: Deno.Conn) {
  const httpConn = Deno.serveHttp(conn);
  for await (const { request, respondWith } of httpConn) {
    respondWith(new Response(`Responding to ${request.url}`));
  }
}

新稳定的 API§

1.9 稳定了与文件系统相关的几个 API:

  • Deno.fstat
  • Deno.fstatSync
  • Deno.ftruncate
  • Deno.ftruncateSync

此外如下方法加入到了 Deno.file 类中:

  • File.stat
  • File.statSync
  • File.truncate
  • File.truncateSync

新弃用的 API§

为了使更多使用 Deno 编写的代码直接移植到浏览器和其他非 Deno 运行时中,我们决定弃用并最终从 Deno 命名空间中删除所有不受系统 API 支持的 API。这些 API 将被移植至 Deno 标准库中,该库也可以在浏览器中使用。

在此版本中,我们不推荐使用以下 API:

  • Deno.Buffer
  • Deno.readAll
  • Deno.readAllSync
  • Deno.writeAll
  • Deno.writeAllSync
  • Deno.iter
  • Deno.iterSync

这些 API 已移至 std/io 模块中。我们在 deno lint 中引入了一个新的 lint 规则,它可以发现开发者使用这些不稳定的 API,并发出警告。它还会建议开发者在标准库找到该 API。

在 Deno 2.0 中,我们计划删除这些废弃的 API。更激进的弃用信息可能会出现在 2.0 之前的版本。请尽可能快地迁移使用到这些弃用 API 的代码。

新的 TypeScript 默认选项 useDefineForClassFields§

在此发布中,我们修改了默认的 Deno tsconfig,包括 "useDefineForClassFields": true 选项。这个选项使 TypeScript 对类字段的处理符合标准 ECMA 脚本语义。此选项不能在用户代码中覆盖。我们希望大部分用户不必更改代码。

© https://github.com/hylerrix/deno-tutorial 2020~2021