Deno 1.17 已标记并发布,包含以下功能和更改
- 导入断言和 JSON 模块
- 对 Web 加密 API 的改进
--no-check=remote
标志- 在
Deno.connectTls()
中对 ALPN 的不稳定支持 - 未引用计时器
- 更新 AbortSignal 中的取消原因
- 更新 Deno 语言服务器
- 更新
deno test
- 更新文件监视器
- 更新 REPL
- 更新 FFI API
- TypeScript 4.5
- 更新标准库
如果您已安装 Deno,可以通过运行以下命令升级到 1.17
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
新功能和更改
导入断言和 JSON 模块
Deno v1.17 随附对导入断言和 JSON 模块的完全支持。
导入断言 是一个第 3 阶段提案,已在今年早些时候的 V8 中发布。此提案的主要用例是允许导入 JSON 模块;如果没有导入断言,可能会造成 安全漏洞。
在此版本之前,读取 JSON 文件的唯一方法是使用 fetch()
或 Deno.readTextFile()
与 JSON.parse()
结合使用。从本版本开始,您可以直接将 JSON 文件导入模块图
// Prior to Deno v1.17
// Get JSON data from remote server
const response = await fetch("https://example.com/data.json");
const jsonData = await response.json();
console.log(jsonData);
// Get JSON data from local file
const text = await Deno.readTextFile("./data.json");
const jsonData = JSON.parse(text);
console.log(jsonData);
// Starting with Deno v1.17
// Get JSON data from remote server
import jsonData from "https://exmaple.com/data.json" with { type: "json" };
console.log(jsonData);
// Get JSON data from local file
import jsonData from "./data.json" with { type: "json" };
console.log(jsonData);
您还可以使用 import()
动态导入 JSON 模块
const jsonData = await import("./data.json", { assert: { type: "json" } });
console.log(jsonData);
使用导入声明导入 JSON 模块的优点是,您无需提供权限即可导入它们;动态导入会受到常规权限检查的限制。
对 Web 加密 API 的改进
本版本再次向 Web 加密 API 添加了新功能。随着这些添加,API 现在接近与浏览器的功能一致性。
在 v1.17 中添加
crypto.subtle.importKey
:- 支持以 SPKI 格式导入 RSA 密钥
- 支持以 JWK 格式导入 RSA 密钥
- 支持以 JWK 格式导入 AES 密钥
- 支持以 PKCS#8 格式导入 EC 密钥
- 支持以 SPKI 格式导入 EC 密钥
- 支持以 JWK 格式导入 EC 密钥
crypto.subtle.exportKey
:- 支持以 JWK 格式导出 RSA 密钥
- 支持以 JWK 格式导出 AES 密钥
crypto.subtle.unwrapKey
:- 支持使用 RSA-OAEP 解包
- 支持使用 AES-GCM 解包
最后几个功能(椭圆曲线密钥导入和导出,以及更多 AES 加密/解密格式)仍在开发中,但进展顺利,很可能在下一个版本中提供。
查看此处的进度:denoland/deno#11690
感谢 Yacine Hmito、Sean Michael Wykes 和 Divy Srivastava 为这些改进做出贡献。
--no-check=remote
标志
Deno 的常见挑战之一是,有时远程依赖项无法正确类型检查,即使它们具有正确的运行时行为。在类型检查本地代码时,这会导致发出诊断信息,即使您无法控制远程依赖项,您的程序也无法运行。
添加了 --no-check=remote
选项来帮助解决此问题。当传递此标志时,程序将作为一个整体进行类型检查,但来自远程模块的任何诊断信息都将被丢弃。如果没有本地诊断信息,您的程序将运行。
在命令行上传递选项
> deno run --no-check=remote server.ts
我们鼓励您尝试使用此标志并报告反馈。我们认为此模式可能是 Deno 未来版本的默认类型检查配置。
Deno.connectTls()
的 ALPN 协商不稳定支持
对 ALPN 是一种 TLS 扩展,允许客户端与服务器协商通信协议。例如,在使用 HTTP/2 时,它用于协商服务器是否支持 HTTP/2。
到目前为止,Deno 不支持手动协商 ALPN。本版本添加了该功能。如果您对该功能感兴趣,我们很乐意听到您的反馈。
一个例子
const conn = await Deno.connectTls({
hostname: "example.com",
port: 443,
alpnProtocols: ["h2", "http/1.1"],
});
const { alpnProtocol } = await conn.handshake();
if (alpnProtocol !== null) {
console.log("Negotiated protocol:", alpnProtocol);
} else {
console.log("No protocol negotiated");
}
感谢 Yury Selivanov 为此功能做出贡献。
未引用计时器
本版本添加了两个新的不稳定 API
Deno.unrefTimer(id: number)
Deno.refTimer(id: number)
这些 API 可用于更改计时器(setTimeout
和 setInterval
)的行为,以阻止或不阻止事件循环退出。
默认情况下,所有计时器都会阻止事件循环退出,直到它们被清除。
在以下示例中,程序将在 5 秒后打印 hello from timeout
后完成。
setTimeout(() => {
console.log("hello from timeout");
}, 5000);
console.log("hello world!");
但是,有时不希望计时器阻止事件循环退出。此行为的一个示例是定期收集遥测数据
// Collect data every 5s
setInterval(() => {
const data = collectSomeData();
fetch("https://example.com/telemetry", { method: "POST", body: data })
.catch(() => {});
}, 5000);
// Main entry point to the program
await longRunningTask();
在上面的示例中,我们希望我们的程序在 longRunningTask
完成后退出。为了实现此行为,我们可以“取消引用”间隔
// Collect data every 5s
const intervalId = setInterval(() => {
const data = collectSomeData();
fetch("https://example.com/telemetry", { body: data }).catch(() => {});
}, 5000);
// Unref the telemetry interval so the program exits immediately, once
// `longRunningTask` finishes.
Deno.unrefTimer(intervalId);
// Main entry point to the program
await longRunningTask();
更新 AbortSignal 中的取消原因
上一个版本添加了对使用特定取消原因取消 AbortSignal
的支持。这些原因现在通过以下 API 正确传播
Deno.readFile()
和Deno.readTextFile()
Deno.writeFile()
和Deno.writeTextFile()
- 所有 WHATWG 流 API(
ReadableStream
、TransformStream
、WritableStream
及其朋友)。 WebSocketStream
fetch
、Request
和Response
此外,我们现在支持 AbortSignal
上的新 throwIfAborted()
方法,该方法可用于同步地抛出错误(如果信号已被取消)。
const controller = new AbortController();
const signal = controller.signal;
try {
signal.throwIfAborted();
} catch (err) {
unreachable(); // the abort signal is not yet aborted
}
controller.abort("Hello World");
try {
signal.throwIfAborted();
unreachable(); // the method throws this time
} catch (err) {
assertEquals(err, "Hello World");
}
感谢 Andreu Botella 为这些更改做出贡献。
Deno 语言服务器更新
Deno 语言服务器添加了对修订后的模块注册表建议协议的支持。这将允许大型包注册表(如 NPM)提供部分结果集、智能增量搜索以及有关包和模块的文档详细信息,作为编辑体验的一部分。将新功能添加到deno.land/x
和deno.land/std
将在 1.17 发布后不久进行。
另一个幕后改进是,来自包注册表的值将尊重提供的缓存标头。以前,来自注册表的结果会无限期地缓存,要求用户手动清除缓存才能获取新结果或更改后的结果。
要利用这些功能,请确保编辑器的 Deno 配置中包含deno.suggest.imports.hosts
中已配置的支持注册表。例如:
{
"deno.suggest.imports.hosts": {
"https://deno.land": true,
"https://cdn.nest.land": true,
"https://crux.land": true
}
}
虽然与 Deno 1.17 版本没有直接关系,但vscode_deno正在更新以默认情况下启用支持注册表协议的已知包注册表。这将提高该功能的可见性,使人们更容易找到在 Deno 下运行的代码。
凭借注册表协议的新功能,我们希望与esm.sh、Skypack和JSPM等公司合作,提供智能注册表,以便轻松使用来自更广泛的 JavaScript 和 TypeScript 生态系统的代码。
如果您想为自己的注册表添加对导入完成的支持,您可以阅读有关注册表协议的更多信息。
此外,Deno 语言服务器达到了成熟程度,添加了一些出色的功能,包括转到类型定义支持和工作区符号搜索。
deno test
更新
Deno 带有一个内置的测试运行器。您可以使用Deno.test()
API 注册测试。到目前为止,API 有两种可用的重载 - “简写”和“定义”基于。简写适用于简单的测试,而定义提供了最大的控制,例如允许测试被忽略。
import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts";
Deno.test("My test description", (): void => {
assertEquals("hello", "hello");
});
Deno.test({
name: "example test",
ignore: Deno.build.os == "windows",
fn(): void {
assertEquals("world", "world");
},
});
如果您需要忽略/关注使用“简写”重载编写的单个测试,这会变得非常麻烦。您需要重写整个测试以使用“定义”重载。为了缓解这个问题并提供编写测试的更多灵活性,我们在Deno.test()
API 中添加了四个额外的重载。
import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts";
Deno.test(function myTestName(): void {
assertEquals("hello", "hello");
});
Deno.test("My test description", { permissions: { read: true } }, (): void => {
assertEquals("hello", "hello");
});
Deno.test(
{ name: "My test description", permissions: { read: true } },
(): void => {
assertEquals("hello", "hello");
},
);
Deno.test({ permissions: { read: true } }, function myTestName(): void {
assertEquals("hello", "hello");
});
您现在可以混合使用这两种形式,并将部分“定义”作为第一个或第二个参数提供。
另一个更新是针对使用不稳定测试步骤 API 的用户。最终的测试报告输出现在将包括测试步骤的数量,并区分通过/忽略/失败的步骤。测试步骤 API 预计将在 v1.18 中稳定。
Deno.test("nested failure", async (t) => {
const success = await t.step("step 1", async (t) => {
let success = await t.step("inner 1", () => {
throw new Error("Failed.");
});
if (success) throw new Error("Expected failure");
success = await t.step("inner 2", () => {});
if (!success) throw new Error("Expected success");
});
if (success) throw new Error("Expected failure");
});
$ deno test --unstable test.ts
running 1 test from file:///dev/test.ts
test nested failure ...
test step 1 ...
test inner 1 ... FAILED (11ms)
Error: Failed.
at file:///dev/test.ts:4:13
at testStepSanitizer (deno:runtime/js/40_testing.js:187:13)
at asyncOpSanitizer (deno:runtime/js/40_testing.js:68:15)
at resourceSanitizer (deno:runtime/js/40_testing.js:138:13)
at exitSanitizer (deno:runtime/js/40_testing.js:170:15)
at TestContext.step (deno:runtime/js/40_testing.js:800:19)
at file:///dev/test.ts:3:27
at testStepSanitizer (deno:runtime/js/40_testing.js:187:13)
at asyncOpSanitizer (deno:runtime/js/40_testing.js:68:15)
at resourceSanitizer (deno:runtime/js/40_testing.js:138:13)
test inner 2 ... ok (11ms)
FAILED (32ms)
FAILED (44ms)
failures:
nested failure
Error: 1 test step failed.
at runTest (deno:runtime/js/40_testing.js:432:11)
at async Object.runTests (deno:runtime/js/40_testing.js:541:22)
failures:
nested failure
test result: FAILED. 0 passed (1 step); 1 failed (2 steps); 0 ignored; 0 measured; 0 filtered out (66ms)
文件观察程序更新
Deno 带有一个内置的文件观察程序,可以使用--watch
标志激活。
文件观察程序会根据模块图中包含的文件(对于deno run
子命令)或作为参数传递的文件路径(对于deno lint
或deno fmt
等子命令)自动发现要观察的文件。
在这个版本中,deno run
的--watch
标志接受一个可选的外部文件列表,这些文件也应该被观察以进行更改。
$ deno run --watch=data/external.txt script.ts
感谢Jasper van den End贡献了此功能。
观察程序的一项小质量改进是自动清除终端屏幕以进行重启。
REPL 更新
模块说明符完成
REPL(deno repl
)现在为文件系统上的相对说明符和来自deno.land注册表的绝对说明符提供完成。
这些完成可以通过在键入模块说明符时按下<TAB>
键来请求。
Node.js 兼容模式
REPL 现在可以在Node.js 兼容模式下运行;允许访问 Node 的全局变量(例如process
)、使用require()
导入 CommonJS 模块以及提供与 Node.js 兼容的 ESM 解析。要以兼容模式进入 REPL,请使用deno repl --compat --unstable
。
$ deno repl --compat --unstable
Deno 1.17.0
exit using ctrl+d or close()
> console.log(process)
process {
_events: {},
_eventsCount: 0,
_maxListeners: undefined,
...
}
忽略证书错误
此版本在deno repl
子命令中添加了--unsafely-ignore-certificate-errors
标志,允许禁用 TLS 证书验证。
请注意,这是一个危险的设置。您不应该使用此标志来消除证书错误。禁用 TLS 证书验证(忽略证书错误)使 TLS 无用,因为它允许 MITM 攻击。如果未验证和信任证书,则通过 TLS 连接发送的数据不会保密。
在Deno 1.13 版本说明博客文章中了解更多信息。
感谢VishnuJin贡献了此功能。
FFI API 更新
继v1.15 中 FFI API 的改进之后,此版本添加了两个用于跨语言边界处理指针的新 API:Deno.UnsafePointer
和Deno.UnsafePointerView
。此外,buffer
参数类型已重命名为pointer
。
示例
// test_ffi.rs
static BUFFER: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8];
#[no_mangle]
pub extern "C" fn return_buffer() -> *const u8 {
BUFFER.as_ptr()
}
#[no_mangle]
pub extern "C" fn is_null_ptr(ptr: *const u8) -> u8 {
ptr.is_null() as u8
}
// test_ffi.js
const [libPrefix, libSuffix] = {
darwin: ["lib", "dylib"],
linux: ["lib", "so"],
windows: ["", "dll"],
}[Deno.build.os];
const libPath = `${targetDir}/${libPrefix}test_ffi.${libSuffix}`;
const dylib = Deno.dlopen(libPath, {
"return_buffer": { parameters: [], result: "pointer" },
"is_null_ptr": { parameters: ["pointer"], result: "u8" },
});
const ptr = dylib.symbols.return_buffer();
dylib.symbols.print_buffer(ptr, 8);
const ptrView = new Deno.UnsafePointerView(ptr);
const into = new Uint8Array(6);
const into2 = new Uint8Array(3);
const into2ptr = Deno.UnsafePointer.of(into2);
const into2ptrView = new Deno.UnsafePointerView(into2ptr);
const into3 = new Uint8Array(3);
ptrView.copyInto(into);
console.log([...into]);
ptrView.copyInto(into2, 3);
console.log([...into2]);
into2ptrView.copyInto(into3);
console.log([...into3]);
const string = new Uint8Array([
...new TextEncoder().encode("Hello from pointer!"),
0,
]);
const stringPtr = Deno.UnsafePointer.of(string);
const stringPtrview = new Deno.UnsafePointerView(stringPtr);
console.log(stringPtrview.getCString());
console.log(stringPtrview.getCString(11));
console.log(Boolean(dylib.symbols.is_null_ptr(ptr)));
console.log(Boolean(dylib.symbols.is_null_ptr(null)));
console.log(Boolean(dylib.symbols.is_null_ptr(Deno.UnsafePointer.of(into))));
$ deno run --allow-ffi --allow-read --unstable test_ffi.js
[1, 2, 3, 4, 5, 6, 7, 8]
[ 1, 2, 3, 4, 5, 6 ]
[ 4, 5, 6 ]
[ 4, 5, 6 ]
Hello from pointer!
pointer!
false
true
false
我们已经看到一些有趣的项目正在使用这些新 API,例如对 SQLite C API 的原生绑定:https://deno.land/x/sqlite3
FFI 需要--allow-ffi
和--unstable
标志。请注意,启用 FFI 将有效地禁用 Deno 中的所有安全保护。
感谢Elias Sjögreen贡献了此功能。
TypeScript 4.5
Deno 1.17 附带了最新稳定版本的 TypeScript。这是支持导入断言功能的关键因素。此外,还支持一种新的导入类型的方式。
以前建议仅以这种方式导入在类型位置使用的项目。
import { A } from "https://example.com/mod.ts";
import type { B } from "https://example.com/mod.ts";
但现在这些可以合并到一个导入语句中。
import { A, type B } from "https://example.com/mod.ts";
此外,TypeScript 4.5 添加了对.mts
和.d.mts
扩展名的支持,以及对.mjs
文件的首要支持,这些文件在 Deno 1.17 中得到妥善处理。
有关 TypeScript 中新功能的更多信息,请参阅TypeScript 的 4.5 博客文章
标准库更新
此版本包含许多标准库的新功能和重大更改。我们正在努力清理标准库,使其处于可以稳定的状态。
重大变更
在 deno_std
的 0.118.0 版本中,以下 API 发生了重大变更
ws
模块已被移除 - 请使用Deno.upgradeWebSocket()
API 代替testing/asserts.ts
中的assertThrowsAsync
已被移除 - 请使用同一模块中的assertRejects
API 代替http/server_legacy.ts
已被移除 - 这是 HTTP 服务器的旧实现,请使用http/server.ts
代替fs/mod.ts
中的copy
API 已被移除 - 请直接从fs/copy.ts
导入此 APIsignals
模块中的onSignal
已被移除 - 请使用Deno.addSignalListener()
API 代替collections
模块中的findLast
和findLastIndex
已被移除http
服务器模块不再接受字符串地址,请使用{ host: string, port: number }
接口代替
新的 HTTP 服务器选项
以前,std/http
模块中的 serve
函数会吞噬 HTTP 处理函数产生的所有错误。此版本改变了这种行为,服务器将使用 console.error
记录处理函数产生的所有错误。您可以通过将自定义 onError
函数传递给 serve
选项来自定义此行为。
import { Server } from "https://deno.land/[email protected]/http/server.ts";
const port = 4505;
const handler = (request: Request) => {
const body = `Your user-agent is:\n\n${
request.headers.get(
"user-agent",
) ?? "Unknown"
}`;
return new Response(body, { status: 200 });
};
const onError = (_error: unknown) => {
return new Response("custom error page", { status: 500 });
};
const server = new Server({ port, handler, onError });
std/collections
的新增功能
std/collections
是我们用来试验用于遍历数组和可迭代对象的更高级函数的游乐场。此版本添加了 aggregateGroups
。它将给定的聚合器应用于给定分组中的每个组,并将结果与相应的组键一起返回
import { aggregateGroups } from "https://deno.land/[email protected]/collections/mod.ts";
import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts";
const foodProperties = {
"Curry": ["spicy", "vegan"],
"Omelette": ["creamy", "vegetarian"],
};
const descriptions = aggregateGroups(
foodProperties,
(current, key, first, acc) => {
if (first) {
return `${key} is ${current}`;
}
return `${acc} and ${current}`;
},
);
assertEquals(descriptions, {
"Curry": "Curry is spicy and vegan",
"Omelette": "Omelette is creamy and vegetarian",
});
std/fmt/bytes
进行字节格式化
使用 一个新的 std/fmt/bytes
模块已添加到标准库中。它提供了一个用于以人类可读的格式格式化字节的函数
import { prettyBytes } from "https://deno.land/[email protected]/fmt/bytes.ts";
import { assertEquals } from "https://deno.land/[email protected]/testing/asserts.ts";
assertEquals(prettyBytes(1024), "1.02 kB");
assertEquals(prettyBytes(1024, { binary: true }), "1 kiB");
此 API 是流行的 pretty-bytes npm 包的移植。