Deno 1.13 发布说明
Deno 1.13 已发布,包含以下新功能和变更
- 稳定化原生 HTTP 服务器 API
- 支持
self.structuredClone()
- TLS 使用系统证书存储
- 能够禁用 TLS 验证以进行测试
- WebCrypto API 更新
- Deno 语言服务器和 VSCode 扩展更新
- REPL 的改进
- 支持 navigator.hardwareConcurrency API
- V8 9.3
- deno info 中的类型引用
- writeFile 中支持 AbortSignal
- Markdown 文件中的代码示例类型检查
- 在干净环境中生成子进程
- 权限 API 支持 URL
- FFI 取代原生插件系统
- 实验性 WebSocketStream API
如果您已经安装了 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,如 fetch
、WebSocket
或 Deno.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 中常用的一些代码或以某种方式修改运行时非常有用
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.hasOwn
是 Object.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
文件,并对所有带有 js
、jsx
、ts
、tsx
属性的代码块进行类型检查。您可以通过添加 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 实现此功能。