跳到主要内容
Deno 2.4 发布,带来 deno bundle、字节/文本导入、OTel 稳定版等更多功能
了解更多

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 Workers 中 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 记录。目前仅支持基于 UDP/TCP 的 DNS(不支持基于 HTTPS 或 TLS 的 DNS)。您可以指定一个自定义名称服务器(例如 Cloudflare 的 1.1.1.1 或 Google 的 8.8.8.8)来使用,但默认情况下我们将使用系统解析器(例如 Linux 上的 /etc/resolv.conf)。

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

这里有一个您可以尝试的例子。该例子是 Unix 上 dig 工具的一个非常简单的版本。您可以将域名作为第一个参数传递给它,它将通过标准输出返回该域名的 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 内置 TypeScript 编译器交互的三个非稳定版 API(Deno.transpileOnlyDeno.bundleDeno.compile)替换为一个改进的函数(Deno.emit)。您可以在手册的 TypeScript 部分中阅读有关如何使用 Deno.emit 进行捆绑、转译等所有信息。

deno fmt 中的 Markdown 支持

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

此外,还添加了一个新标志 --ext,允许在从标准输入(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 中移除此已弃用方法,Deno 1.8 计划于 6 周后的 2021 年 3 月 2 日发布。

此已弃用 API 在 0.83.0 或更低版本的某些标准模块(特别是 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 的权限必须是进程权限的子集,即如果进程在没有“读取”权限的情况下运行,那么尝试创建具有“读取”权限的 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 没有“文档”(HTML 页面)来运行其 JavaScript 的一个不幸影响是,Deno 以前无法很好地确定脚本的 origin。这对于像 window.localstorage 这样的 API 很重要,因为您交互的数据取决于您所在的页面(文档)。Local Storage API 是许多使用 origin 的 API 之一,但这个 API 是我们特别希望尽快添加的。

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

在此版本中,我们通过添加 --location 标志来解决此问题,该标志允许您为脚本设置“文档”位置。此位置可以是任何 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 的执行方式。现在您不再能够指定 caFile(包含自定义 CA 证书的文件路径),而是指定 caData。这意味着您现在可以在 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 权限的部分也略有更改。现在,它不再接受 url 参数,而是使用 host,以匹配 --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 的重要部分进行重新架构以适应这些更改。结果,用于保存 Rust 分配对象(例如文件句柄、TCP 连接)的 ResourceTable 结构从头开始重写,增加了对资源执行不同“操作”进行排队的能力;这意味着对同一套接字或文件的 write 操作现在保证按其启动的相同顺序发生,read 操作也一样。

其他新闻

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

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