跳到主要内容
Deno 2.4 带着 deno bundle、bytes/text 导入、稳定的 OTel 等功能到来
了解更多

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 Worker 和 MessagePort 之间消息传递的结构化克隆算法。以前无法同步结构化克隆对象。

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

import { assert } from "https://deno.land/std@0.104.0/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_TLS_CA_STORE=system,Deno 现在能够使用系统根 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 个硬件核心和同步多线程(Intel® 超线程)的机器有 8 个逻辑核心。这通常用于确定工作池应生成多少个 Web Worker,以实现最有效的资源利用。

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

更多文档可在MDN上找到。

感谢 Divy Srivastava 实现此功能。

V8 9.3

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

Error cause(错误原因)

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,现在在查询“read”、“write”和“run”权限时,除了字符串外,还可以接受 URL。URL 现在也支持在 Deno.test 和 Web Worker 的权限配置中使用。

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 库并调用该库中定义的“ops”。尽管该系统背后的想法很棒,但我们面临许多技术挑战,主要源于 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 所取代。这使得使用新 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 评论