跳到主要内容

Deno 1.7 发行说明

今天我们发布 Deno 1.7.0。此版本包含许多新功能、一些稳定化,以及对现有 API 和工具的重大改进。

如果您已经安装了 Deno,则可以通过运行 deno upgrade 升级到 1.7。如果您是第一次安装 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

在继续阅读之前,请考虑填写 Deno 调查问卷。即使您从未使用过 Deno!只需 10 分钟,这将极大地帮助我们指导我们的开发方向。

新功能和更改

deno compile 的改进

在上一个版本中,我们添加了将为 Deno 编写的应用程序编译为独立的、自包含的可执行文件的能力。当我们在 1.6.0 中首次发布 deno compile 时,我们列出了一系列我们想要解决的痛点和功能。此版本解决了其中的三个。

我们很高兴地报告,在 1.7 中,deno compile 现在可以从任何稳定的受支持架构(Windows x64、MacOS x64 和 Linux x64)交叉编译到任何其他稳定的受支持架构。这意味着您现在可以从单个 Linux CI 机器为 Windows 和 MacOS 创建二进制文件。

此外,deno compile 现在生成的二进制文件比 Deno 1.6 生成的二进制文件小 40-60%。要试用此功能,请在编译应用程序时使用 --lite 标志;这告诉 deno compile 使用精简的仅运行时 Deno 二进制文件,而不是之前使用的完整 Deno 二进制文件。

下面您可以看到一个简单的 hello world 程序从 macOS 交叉编译到 Linux 的示例,然后在 Linux 上运行。

最后,deno compile 现在可以创建具有内置 CA 证书、自定义 V8 标志、锁定 Deno 权限和预填充命令行参数的二进制文件。这应该使 deno compile 对更多人有用。

这是一个示例,我们从 std/http/file_server 模块创建一个可执行文件,该文件在端口 8080(而不是默认的 4507)上侦听,并且启用了 CORS。运行代码的权限也被锁定(只能从当前工作目录读取,并且只能在端口 8080 上侦听)。

支持 data URL 导入

Data URL 是一个用于执行动态生成的代码的有用工具。在此版本中,我们添加了对导入(静态和动态)以及 Web Worker 中 data URL 的支持。所有现代浏览器和 NodeJS 都已支持此功能。

这是一个供您尝试的示例

// main.ts
export const a = "a";

export enum A {
  A,
  B,
  C,
}

上面的代码可以表示为以下 data URL:"data:application/typescript;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo="。此 URL 通过 base64 编码文件内容,并将其附加到 data:application/typescript;base64, 来创建。对于 JavaScript,您将内容附加到 data:application/javascript;base64,

此导入说明符稍后可以像这样导入

// https://deno.org.cn/blog/v1.7/import_data_url.ts
import * as a from "data:application/typescript;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo=";

console.log(a.a);
console.log(a.A);
console.log(a.A.A);

试试看

$ deno run https://deno.org.cn/blog/v1.7/import_data_url.ts
a
{ "0": "A", "1": "B", "2": "C", A: 0, B: 1, C: 2 }
0

Worker 也是如此

// https://deno.org.cn/blog/v1.7/worker_data_url.ts
import { deferred } from "https://deno.land/std@0.83.0/async/deferred.ts";
import { assertEquals } from "https://deno.land/std@0.83.0/testing/asserts.ts";

const promise = deferred();
const tsWorker = new Worker(
  `data:application/typescript;base64,${
    btoa(`
    if (self.name !== "tsWorker") {
      throw Error(\`Invalid worker name: \${self.name}, expected tsWorker\`);
    }
    onmessage = function (e): void {
      postMessage(e.data);
      close();
    };
  `)
  }`,
  { type: "module", name: "tsWorker" },
);

tsWorker.onmessage = (e): void => {
  assertEquals(e.data, "Hello World");
  promise.resolve();
};

tsWorker.postMessage("Hello World");

await promise;
tsWorker.terminate();

新的不稳定 Deno.resolveDns API

此版本添加了一个新的 Deno.resolveDns API。它可用于从 DNS 解析器查询 DNS 记录。目前仅支持 DNS over UDP/TCP(不支持 DNS over HTTPS 或 DNS over TLS)。可以指定自定义名称服务器(例如 Cloudflare 的 1.1.1.1 或 Google 的 8.8.8.8)来使用,但默认情况下我们将使用系统解析器(例如 Linux 上的 /etc/resolv.conf)。

该 API 目前支持 AAAAAANAMECNAMEMXPTRSRVTXT 记录。响应以结构化数据形式返回。

这是一个供您尝试的示例。该示例是 unix 上 dig 工具的一个非常简单的版本。您可以将域名作为第一个参数传递给它,它将通过 stdout 返回此域名的 A 记录。

// https://deno.org.cn/blog/v1.7/dig.ts
const domainName = Deno.args[0];
if (!domainName) {
  throw new Error("Domain name not specified in first argument");
}

const records = await Deno.resolveDns(domainName, "A");
for (const ip of records) {
  console.log(ip);
}
$ deno run --allow-net --unstable https://deno.org.cn/blog/v1.7/dig.ts deno.land
104.21.18.123
172.67.181.211

内部编译器 API 变为 Deno.emit

我们用一个改进的函数 (Deno.emit) 替换了用于与 Deno 内置 TypeScript 编译器交互的三个不稳定 API(Deno.transpileOnlyDeno.bundleDeno.compile)。您可以在 手册的 TypeScript 部分中阅读有关如何使用 Deno.emit 进行捆绑、转译等的全部内容。

deno fmt 中的 Markdown 支持

deno fmt 现在支持格式化 markdown 文件,包括格式化这些文件中的 JavaScript 和 TypeScript 代码块。

此外,还添加了一个新的标志 --ext,以允许在从 stdin 格式化代码 (deno fmt -) 时指定文件扩展名。可用的文件扩展名有 jsjsxtstsxmd。请记住,此标志在格式化磁盘上的文件时无效。

这是一个示例

# Format files on disk
$ deno fmt docs.md source_code.js source_code2.ts

# Format contents from stdin as Markdown
$ cat docs.md | deno fmt --ext=md -

使 web streams API 与规范对齐

此版本投入了大量精力,使我们对各种 web API(文本编码、URL、Streams 和 WASM)的实现与这些 API 的各种规范保持一致。这主要是内部错误修复,但在一种情况下,存在相当严重的用户界面更改。

以前,我们根据 2020 年 3 月左右的规范实现了 Streams API。在该规范的修订版中,ReadableStream 类具有 getIterator 方法,该方法可用于从 ReadableStream 获取异步迭代器。在最新的修订版中,ReadableStream 类是一个异步迭代器,并且 getIterator 方法已被删除。

为了更接近规范,我们想要删除 ReadableStream 上的 getIterator 方法。为了给您时间更新此已弃用 API 的用法,我们已将该方法标记为在此版本 (1.7) 中已弃用。我们计划在 Deno 1.8 中删除已弃用的方法,该版本计划于 6 周后的 2021 年 3 月 2 日发布。

此已弃用的 API 在版本 0.83.0 或更低版本的某些 std 模块(特别是 std/async 和 std/http)中使用。请升级到 std 版本 0.84.0。在您自己的代码中,删除所有 .getIterator() 调用,如下所示

- for await (const item of body.getIterator()) {
+ for await (const item of body) {

支持可配置的 web worker 权限

默认情况下,Deno 在完全沙箱中执行用户代码,除非用户在 CLI 上传递 --allow-* 标志。不幸的是,这些权限不能限定于特定模块。许多用户都要求此功能,我们很高兴地宣布,在这方面取得了一些进展。从 Deno 1.7 开始,用户可以使用自定义权限集生成 Web Worker,从而可以在 Deno 进程内部运行不受信任的代码。

重要的是要知道,授予 worker 的权限必须是进程权限的子集,即,如果进程在没有“read”权限的情况下运行,则尝试创建具有“read”权限的 worker 将导致 PermissionDenied 错误。

注意:此功能与浏览器不兼容。浏览器将忽略 worker 选项包中的 deno 字段。

这是一个供您尝试的示例。它将生成一个具有 read 权限的 worker,然后该 worker 将尝试读取文件 ./log.txt 并将其发送回客户端。

// worker_permissions.ts
const workerUrl = new URL("worker_permissions_worker.ts", import.meta.url).href;
const worker = new Worker(workerUrl, {
  type: "module",
  deno: {
    namespace: true,
    permissions: {
      read: true,
    },
  },
});

worker.postMessage({ cmd: "readFile", fileName: "./log.txt" });
// worker_permissions_worker.ts
self.onmessage = async function (e) {
  const { cmd, fileName } = e.data;
  if (cmd !== "readFile") {
    throw new Error("Invalid command");
  }
  const buf = await Deno.readFile(fileName);
  const fileContents = new TextDecoder().decode(buf);
  console.log(fileContents);
  self.close();
};

试试看

$ echo "hello world" > ./log.txt
$ deno run --allow-read --unstable https://deno.org.cn/blog/v1.7/worker_permissions.ts
hello world

您也可以尝试在没有 --allow-read 权限的情况下运行它。这将导致抛出错误,因为您试图升级权限

$ deno run --unstable https://deno.org.cn/blog/v1.7/worker_permissions.ts
error: Uncaught PermissionDenied: Can't escalate parent thread permissions
    throw new ErrorClass(res.err.message);
          ^

添加对 globalThis.location 和相对 fetch 的支持

Deno 没有“document”来运行其 JavaScript 代码(HTML 页面)的一个不幸后果是,Deno 从来没有一个好的方法来确定脚本的 origin。这对于像 window.localstorage 这样的 API 很重要,您与之交互的数据取决于您所在的页面(document)。Local Storage API 是许多使用 origin 的 API 之一,但这个 API 是我们特别希望尽快添加的 API 之一。

这对于同构代码(在客户端和服务器上运行的代码)也非常有用,例如在 React 组件的服务器端渲染期间,因为它们现在都可以使用 globalThis.location 和相对 fetch

在此版本中,我们通过添加 --location 标志来解决此问题,该标志允许您为脚本设置“document”位置。此位置可以是任何 httphttps URL(不需要存在)。如果未设置此标志,则 window.location 仍然存在,但在访问时会抛出错误。fetchnew Worker 中的相对 URL 也是如此。如果设置了位置,它们将相对于该位置,否则它们将抛出错误。在 worker 中,位置将始终设置为 Worker 的入口点脚本。

$ cat example.ts
console.log(globalThis.location.href);

const res = await fetch("/std/version.ts");
console.log(res.status, res.url);
console.log(await res.text());
$ deno run --location="https://deno.land" --allow-net example.ts
https://deno.land/
200 https://deno.land/std@0.83.0/version.ts
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
/** Version of the Deno standard modules
 *
 * Deno std is versioned differently than Deno cli because it is still unstable;
 * the cli's API is stable. In the future when std becomes stable, likely we
 * will match versions with cli as we have in the past.
 */
export const VERSION = "0.83.0";

我们知道某些模块使用 window.location 来确定它们是否在浏览器中运行。这是一种不良做法。使用 typeof Deno !== "undefined" 来确定您是否在 Deno 中运行,并使用 typeof document !== "undefined" 来确定 DOM 是否可用。

支持 fetch 请求体流式传输

除了我们对 fetch 中流式响应体的支持之外,我们现在还支持流式请求体。这可用于将大型文件上传到 web 服务器,而无需先将其缓冲到内存中。这可以通过将 ReadableStream 传递给 fetch 选项中的 body 字段来完成。

import { readableStreamFromAsyncIterator } from "https://deno.land/std@0.84.0/io/streams.ts";

// Open the file we want to upload to the server.
const file = await Deno.open("./large_file_on_disk.txt");

// Construct a `ReadableStream` from the `Deno.Reader` returned by `Deno.open`.
const body = readableStreamFromAsyncIterator(Deno.iter(file));

// Send the body to the server.
const res = await fetch("https://myfileserver.com/upload", {
  method: "POST",
  body,
});

目前唯一支持 fetch 上传流式传输的运行时是 Chromium 和 Deno。这两个实现都有一个限制,即在请求体完全发送完毕之前,您无法开始接收响应体。这不是 Fetch 规范中的限制,而是实现中的限制,将来会解决。

有关 fetch 上传流式传输的更多示例和用例,请查看 Jake Archibald 关于此事的帖子:https://webdev.ac.cn/fetch-upload-streaming/

TLS 会话缓存

某些服务器要求用户重用现有的 TLS 会话(例如 FTP)。到目前为止,Deno 还没有重用 TLS 会话的能力,而是为每个连接重新建立一个新的 TLS 会话。在此版本中,我们添加了一个进程全局 TLS 会话缓存,该缓存允许在连接之间重用现有的 TLS 会话。

会话缓存是一个内存缓存,大小为 1024 个会话。溢出会话将导致其他会话被逐出。TLS 会话缓存以尽力而为的方式使用。

Deno API 的更改

Deno.shutdown()Conn#closeWrite() 在此版本中已稳定。这些函数用于通过向另一端发出信号表明您已完成发送数据来优雅地关闭连接。与此 API 的不稳定版本不同,shutdown() 方法不再具有 mode 参数;只有套接字的写入端可以关闭。

同样在此版本中,不稳定的 Deno.createHttpClient API 的选项包签名已更改。此 API 可用于自定义 fetch 的执行方式。您现在可以指定 caData,而不是能够指定 caFile(包含自定义 CA 证书的文件路径)。这意味着您现在可以将内存证书用于 Deno.createHttpClient

- const client = Deno.createHttpClient({ caFile: "./my_ca.pem" });
- const res = await fetch("https://my.kubernetes:4443", { client })
+ const client = Deno.createHttpClient({ caData: Deno.readFileSync("./my_ca.pem") });
+ const res = await fetch("https://my.kubernetes:4443", { client })

不稳定的 Deno.permission API 的 net 权限也略有更改。我们现在使用 host 而不是采用 url 参数,以匹配 --allow-net 标志支持的内容。

- await Deno.permissions.query({ name: "net", url: "localhost:4000" });
+ await Deno.permissions.query({ name: "net", host: "localhost:4000" });

覆盖率改进

deno test --coverage 现在能够报告部分覆盖的行,以及收集从测试中生成的 Deno 子进程的覆盖率。我们将在即将发布的版本中继续改进覆盖率功能,包括其他报告格式。

Tokio 1.0

Deno 1.7 标志着从 Tokio 0.2 迁移到 Tokio 1.0 的漫长过程结束。

用户之前报告了许多关于在众多 Deno API 中发生半随机挂起的问题。经过彻底调查,确定所有这些问题都是由与 tokio 运行时的交互引起的。由于 tokio 1.0 中的 API 更改,我们不得不重新架构 deno_core 的重要部分以适应这些更改。实际上,ResourceTable(一种保存 Rust 分配的对象(例如文件句柄、TCP 连接)的结构)已从头开始重写,增加了对资源执行不同“ops”进行排队的能力;这意味着对同一套接字或文件的 write 操作现在保证以它们启动的相同顺序发生,read 操作也是如此。

其他新闻

如上所述,我们在此版本中花费了大量时间来使我们的 web API 与各种 API 规范保持一致。将 web 平台测试套件集成到我们的测试中对此有很大帮助。Web 平台测试是所有浏览器供应商用来测试与 web 平台规范兼容性的测试套件。我们已经启用了数千个测试,但我们离完成这项工作还很远(仍有数千个 web 平台测试需要启用)。如果您认为可以帮助我们完成这项工作,请查看此问题:https://github.com/denoland/deno/issues/9001

如果您填写 Deno 调查问卷,我们将不胜感激,这只需 10 分钟,将极大地帮助我们进一步开发 Deno。