跳至主要内容
Deno 2 终于来了 🎉️
了解更多

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)。

支持导入数据 URL

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

以下是一个供您尝试的示例

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

export enum A {
  A,
  B,
  C,
}

上面的代码可以表示为以下数据 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/[email protected]/async/deferred.ts";
import { assertEquals } from "https://deno.land/[email protected]/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 工具的非常简单的版本。 您可以将域名作为第一个参数传递给它,它将通过标准输出返回此域名的 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)。 您可以阅读有关如何使用 Deno.emit 来打包、转译等等的全部信息,在手册的 TypeScript 部分

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 流 API 对齐到规范

此版本在将我们对各种 Web API(文本编码、URL、流和 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 在某些 std 模块(特别是 std/async 和 std/http)中被使用,版本为 0.83.0 或更低。 请升级到 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 工作线程,从而可以在 Deno 进程中运行不受信任的代码。

重要的是要知道,授予工作线程的权限必须是进程权限的子集,即,如果进程在没有“读取”权限的情况下运行,那么尝试创建具有“读取”权限的工作线程将导致 `PermissionDenied` 错误。

注意:此功能与浏览器不兼容。浏览器会忽略工作线程选项包中的 `deno` 字段。

以下是一个供您尝试的示例。它将生成一个具有 `read` 权限的工作线程,该线程将尝试读取文件 `./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 没有“文档”来运行其 JavaScript(HTML 页面)的一个不幸影响是,Deno 从来没有一个好的方法来确定脚本的 `origin`。这对像 `window.localstorage` 这样的 API 非常重要,因为您与之交互的数据取决于您所在的页面(文档)。本地存储 API 是众多使用 origin 的 API 之一,但我们希望尽快添加的 API 特别是这个 API。

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

在此版本中,我们通过添加 `--location` 标志来解决这个问题,该标志允许您为脚本设置“文档”位置。此位置可以是任何 `http` 或 `https` URL(它不需要存在)。如果未设置此标志,`window.location` 仍然存在,但访问时会抛出异常。对于 `fetch` 和 `new Worker` 中的相对 URL 也是如此。它们将相对于位置(如果已设置),否则将抛出异常。在工作线程中,位置始终设置为工作线程的入口点脚本。

$ 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/[email protected]/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/[email protected]/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://web.dev/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 })

针对 `net` 权限的不稳定 `Deno.permission` API 也略有更改。我们现在使用 `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 连接)从头开始重写,增加了对排队针对资源执行的不同“操作”的能力;这意味着对相同套接字或文件的 `write` 现在保证以它们开始的相同顺序发生,`read` 也是如此。

其他新闻

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

我们非常感谢您填写 Deno 调查,它只需要 10 分钟,并且将极大地帮助我们进一步开发 Deno。