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

Deno 1.13 版本说明

Deno 1.13 已标记并发布,包含以下功能和更改

如果您已安装 Deno,可以通过运行以下命令升级到 1.13

deno upgrade

如果您是第一次安装 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 服务器 API

此版本中的原生 HTTP 服务器 API 现已成为稳定 API。Deno 现在开箱即用,能够有效地提供 HTTP/1.1 和 HTTP/2 流量。系统将 Hyper Web 服务器 作为 JavaScript API 公开,这将让许多 Web 开发人员感觉熟悉。

自从它首次在 Deno 1.9 中作为不稳定 API 引入以来,许多错误已得到修复。在过去的几周里,我们已经从使用此新 API 驱动的服务器向 https://deno.land 提供了数百万次请求。通过这种试用,我们现在有信心原生 HTTP API 兼容且稳定,足以供通用使用。

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

如果您目前正在使用 std/http,我们建议您升级到原生 HTTP 服务器。std/http 将在 std 中提供几个版本,但很快将被删除。迁移指南将在手册中尽快提供。如果您使用的是最新版本的 oak,您的应用程序在升级到 Deno 1.13 后将无缝切换到使用原生 HTTP 服务器。

内置的服务器端 WebSocket 支持仍然被标记为不稳定,但可通过 自 1.12 起通过 Deno.upgradeWebSocket() 函数使用。

有关新 API 的介绍,请查看 手册条目

支持 self.structuredClone()

HTML 规范作者(感谢 Surma最近添加了一个新的 self.structuredClone 函数。它在简单、惯用且同步的 API 中公开了用于在 Web 工作者和 MessagePort 之间进行消息传递的结构化克隆算法。以前无法对对象进行同步结构化克隆。

结构化克隆算法可以深度克隆 JavaScript 值,并支持循环对象引用。有关结构化克隆的更多信息,请访问 MDN

import { assert } from "https://deno.land/[email protected]/testing/asserts.ts";

// Create an object with a value and a circular reference to itself.
const foo = { bar: "baz" };
foo.foo = foo;

// Clone it
const clone = self.structuredClone(foo);

assert(clone !== foo); // assert they are not the same object
assert(clone.bar === "baz"); // assert they  do have the same value though
assert(clone.foo === clone); // assert that the circular reference is preserved

console.log("All assertions passed!");

自己试试

deno run https://deno.org.cn/blog/v1.13/structured_clone.js

Deno 是第一个提供此新功能的运行时,但它可能会在接下来的几个版本中登陆 Chrome 和 Firefox。我们很高兴继续与标准机构和浏览器供应商合作,统一和简化服务器端和客户端 JavaScript 生态系统。

感谢 @crowlKats 实施此功能。

使用系统证书存储进行 TLS

此版本引入了新的 DENO_TLS_CA_STORE 环境变量,可用于切换 Deno 信任的证书颁发机构进行 TLS。在此版本之前,Deno 仅支持信任捆绑的 Mozilla 根 CA 存储以及使用 --cert 标志的单个附加证书。

这对在使用自签名 TLS 证书代理 TLS 连接以重新签名流量的网络中运行 Deno 的用户来说是一个障碍。这些 TLS 证书通常由网络管理员安装到系统根信任存储中。

Deno 现在可以通过设置环境变量 DENO_TLS_CA_STORE=system 来使用系统根 CA 存储。在 macOS 上,证书从钥匙串加载,在 Windows 上从系统证书存储加载,在 Linux 上从磁盘上的知名路径读取系统 CA 捆绑包。指定此参数将禁用内置 CA 存储。

有关 DENO_TLS_CA_STORE 的更多信息,请参阅手册:https://docs.deno.org.cn/runtime/manual/getting_started/setup_your_environment#environment-variables

感谢 Justin Chase 实施此功能。

禁用 TLS 验证

Deno 1.13 添加了一个名为 --unsafely-ignore-certificate-errors 的新标志。此标志允许禁用 SSL 证书验证。

请注意,**这是一个危险的设置**。您不应使用此标志来消除证书错误。禁用 TLS 证书验证(忽略证书错误)**使 TLS 毫无意义,因为它允许 MITM 攻击**。如果证书未经验证和信任,通过 TLS 连接发送的数据将不是机密的。

当您遇到 TLS 错误时,您应该确保为签署服务器 TLS 证书的 CA 添加证书到 Deno 的根信任存储,而不是使用此标志。您可以使用 --cert 标志为一次性证书执行此操作。

如果您在代理所有 TLS 连接并使用自签名证书的企业网络中,该证书通常安装到您的系统根 CA 存储中。要告诉 Deno 使用它,请全局设置 DENO_TLS_CA_STORE=system 环境变量。在许多情况下,这将解决您的 TLS 问题,而不会禁用证书验证。

--unsafely-ignore-certificate-errors 标志可以选择接受应禁用验证的主机名列表。如果没有提供参数,则不会执行任何证书验证。这非常不安全,不应该使用。此标志会影响内部 Deno 函数(例如,下载依赖项),以及运行时 API,例如 fetchWebSocketDeno.connectTls

使用此标志启动 Deno 将始终打印警告消息

$ deno run --allow-net --unsafely-ignore-certificate-errors fetch.ts
DANGER: TLS certificate validation is disabled for all hostnames
...
$ deno run --allow-net --unsafely-ignore-certificate-errors=localhost fetch.ts
DANGER: TLS certificate validation is disabled for: localhost
...

感谢 TheAifam5 为我们贡献了此功能。

更新 WebCrypto API

此版本为 WebCrypto API 添加了一些更多功能

  • crypto.subtle.importKey()crypto.subtle.exportKey() 现在支持导入和导出原始 HMAC 密钥。
  • crypto.subtle.verify() 现在支持验证从 HMAC 密钥创建的签名。

感谢 Divy Srivastava 实施此功能。

更新 Deno 语言服务器和 VSCode 扩展

重构代码操作

现在,JavaScript 和 TypeScript 文件可以使用代码重构操作。这些操作提供了一些常见任务的重构方法,例如将代码提取到函数和常量中,或将代码移动到新的文件中。您不需要执行任何操作来启用它们,因为它们会由您的编辑器提供。

感谢 Jean Pierre 为此功能做出贡献。

通过设置设置缓存/DENO_DIR 的能力

语言服务器添加了设置 deno.cache,可以通过您的编辑器进行设置。这会将语言服务器配置为使用特定路径到 Deno 的缓存目录。这类似于从命令行启动 Deno 时可以使用 DENO_DIR 环境变量。

这在您希望在开发代码时分离缓存目录时非常理想,您可能将缓存目录作为项目的一部分签入。

各种次要增强和错误修复

还有一些其他次要增强,例如改进可用的诊断信息、修复使用导入映射时出现的问题,以及修复导致分离的语言服务器进程无法自行终止的问题。

REPL 的改进

此版本为 REPL (deno repl) 带来了两项重大改进

现在忽略导出

函数、类或 TypeScript 类型之前的 export 关键字现在将被忽略。这对于将模块中的某些代码片段复制粘贴到 REPL 时很有用。以前,REPL 会在 export 语法上报错。

--eval 标志

REPL 现在有一个 --eval 标志。这使您可以在用户进入 REPL 之前在 JS 运行时运行一些代码。这对于导入您在 REPL 中经常使用的某些代码或以某种方式修改运行时很有用。

--eval for `deno repl`

navigator.hardwareConcurrency API 的支持

navigator.hardwareConcurrency Web API 在此版本中添加,取代了不稳定的 Deno.cpuInfo() API。它可用于检索机器具有的逻辑 CPU 内核数量。具有 4 个硬件内核和同时多线程 (英特尔® 超线程) 的机器具有 8 个逻辑内核。这通常用于确定为工作池应生成多少个 Web 工作线程,以便最有效地利用资源。

console.log(navigator.hardwareConcurrency);
// 8

更多文档可从 MDN 获取。

感谢 Divy Srivastava 实施此功能。

V8 9.3

此版本将 V8 更新至 9.3 版。此版本引入了两个新的 JavaScript 语言特性,我们还通过内置的 TypeScript 类型库提供了这些新特性。

错误原因

Error 有一个新的 cause 属性,可用于链接错误。

const parentError = new Error("parent");
const error = new Error("parent", { cause: parentError });
console.log(error.cause === parentError);
// → true

Object.hasOwn

Object.hasOwnObject.prototype.hasOwnProperty.call 的一个更容易访问的别名。

Object.hasOwn({ prop: 42 }, "prop");
// → true

完整的版本说明可以在 v8 博客 上找到。

deno info 中的类型引用

Deno 附带一个内置的依赖关系检查器,可作为 deno info 命令使用。检查器显示给定入口模块的依赖关系树。从 1.13 版开始,使用 /// <reference lib="...">// @deno-types 指令或 X-TypeScript-Types 头部引用的类型声明将显示在树视图中。

writeFile 中的 AbortSignal 支持

此版本添加了在 writeFile 中指定中止信号的支持。这使您可以在发现文件太大或写入磁盘的时间过长时终止文件的写入。

const aborter = new AbortController();
const data = new UInt8Array(32 * 1024 * 1024);
Deno.writeFile("./super_large_file.txt", { signal: aborter.signal })
  .then((data) => console.log("File write:", data.length))
  .catch((err) => console.error("File write failed:", err));
setTimeout(() => aborter.abort(), 1000);

感谢 Benjamin Gruenbaum 为此功能做出贡献。

在 Markdown 文件中类型检查代码示例

在 Deno 1.10 中,我们引入了 允许在 JSDoc 注释中类型检查示例代码的功能,在此基础上,Deno 1.13 添加了对在 Markdown 文件中的代码块中类型检查示例的支持。deno test --doc 现在将包含 *.md 文件,并类型检查所有具有 jsjsxtstsx 属性的代码块。您可以通过添加 ignore 属性来选择退出类型检查,例如 ts, ignore

Deno 使用此功能来确保 标准库手册 中的代码示例是最新的。

感谢 Casper Beyer 为此功能做出贡献。

使用干净的环境生成子进程

Deno.RunOptions 中添加了一个名为 clearEnv 的新选项。它允许使用“干净”的环境生成子进程,即父进程中的环境变量不会包含在子进程中。

这是一个不稳定的功能,需要 --unstable 标志才能使用。

感谢 @crowlKats 实施此功能。

权限 API 接受 URL

此版本更新了 Deno.permissions API,它现在除了在查询“读取”、“写入”和“运行”权限时接受字符串外,还可以接受 URL。URL 现在也被 Deno.test 和 Web 工作线程的权限配置接受。

await Deno.permissions.query({
  name: "read",
  path: new URL(".", import.meta.url),
});
await Deno.permissions.query({
  name: "write",
  path: new URL(".", import.meta.url),
});
await Deno.permissions.query({
  name: "run",
  command: new URL(".", import.meta.url),
});

感谢 @crowlKats 实施此功能。

实验性 FFI 替换原生插件系统

Deno 带有一个原生插件系统,它允许您使用 Deno.openPlugin() API 打开一个动态 Rust 库,并调用该库中定义的“操作”。尽管该系统背后的理念很棒,但我们面临着许多技术挑战,主要原因是 Rust 中缺乏 ABI 稳定性。

在 1.13 版本中,原生插件系统被一个更通用的 FFI API 所取代,该 API 允许您直接从 Deno 调用用 Rust 以外的语言编写的库 - 实际上 Deno.openPlugin()Deno.dlopen() 所取代。

请注意,这是新 API 的第一次迭代,它被认为是不稳定的,需要使用 --unstable API 才能使用它。我们计划在未来的版本中添加对更多类型跨语言边界支持。

以下是一个示例,展示了如何从 Deno 调用 C 函数

// add.c
int add_numbers(int a, int b) {
  return a + b;
}

将文件编译为共享库

// unix
cc -c -o add.o add.c
cc -shared -W -o libadd.so add.o
// windows
cl /LD add.c /link /EXPORT:libadd

从 Deno 调用共享库

// ffi.js
let libSuffix = "so";

if (Deno.build.os == "windows") {
  libSuffix = "dll";
}

const libName = `add_numbers.${libSuffix}`;
const dylib = Deno.dlopen(libName, {
  "add_numbers": { parameters: ["i32", "i32"], result: "i32" },
});

console.log(dylib.symbols.add_numbers(123, 456));

运行它

deno run --allow-ffi --unstable ffi.js
579

感谢 Elias Sjögreen 实现此功能。

实验性 WebSocketStream API

在过去的几个月里,Chrome 团队一直在开发一个基于 Web 流 的新的现代 WebSocket API。新的 API 被称为 WebSocketStream。它是现有基于事件的 WebSocket API 的继任者。

使用流解决了现有 WebSocket API 的一个长期问题:背压。事件可以像浏览器想要的那样快地分派,而没有给开发人员任何暂停事件分派的方法。这意味着如果您对每个事件进行一些计算,很容易使 JS 线程过载。

新的 API 通过使用 ReadableStream 作为其接收(readable)端,以及 WritableStream 作为其发送(writable)端来解决这个问题。因为开发人员总是必须从流中“请求”下一项,所以浏览器不能随意地向脚本发送事件。

旧的 API 还有一个 "close" 事件,当流关闭时会分派该事件。这已被一个 Promise 所取代,该 Promise 在流关闭时解析。这使得使用新的 WebSocketStream 的代码更有效地与 async/await 集成。

这是一个旧 API 与新 API 的并排示例。新的 API 在顶部,旧的 API 在底部。该示例向远程服务器发送消息,然后打印出从服务器接收的所有消息。

// Initiate a connection, and wait for it to be established. The
// `wss.connection` throws if the connection can not be established.
const wss = new WebSocketStream("wss://example.com");
const { writable, readable } = await wss.connection;

// Send a text message to the remote server. If this errors (connection closed)
// the writer.write call will reject.
const writer = writable.getWriter();
await writer.write("Hello server!");

// Iterate over all incoming messages, and print them out. If this errors
// (connection closed) async iterator will reject, and the for await loop
// terminates.
for await (const message of readable) {
  console.log("new message:", message);
}
// Initiate a connection, and wait for it to be established. The below promise
// will reject if an error occurs during connection establishment.
const ws = await new Promise((resolve, reject) => {
  const ws = new WebSocket("wss://example.com");
  ws.onerror = (e) => {
    reject(new Error(e.message));
  };
  ws.onopen = (e) => {
    resolve(ws);
  };
});

// Listen for error events, because the below calls do not reject if an error
// occurs.
ws.onerror = (e) => {
  console.error("connection closed:", e.code, e.reason);
};

// Send a text message to the remote server. This does not reject if an error
// occurs while sending.
ws.send("Hello server!");

// Listen for the message event.
ws.onmessage = (e) => {
  console.log("new message:", e.data);
};

该 API 仍处于原型阶段,尚未编写正式规范(但有一个 说明)。我们决定在 --unstable 中实现该 API,以便在 API 最终确定并发布到浏览器之前,开始收集开发人员的一些反馈。请在 Deno 问题跟踪器或 说明 存储库中的问题跟踪器中报告任何人体工程学问题。

有关更多信息,还可以参阅这篇文章 web.dev 文章

感谢 @crowlKats 实施此功能。





HN 评论