Deno 1.24 已标记并发布,包含以下新功能和更改
- 类型检查和发射性能改进
unhandledrejection
事件beforeunload
事件import.meta.resolve()
API- FFI API 改进
deno test
改进- 更新了新的子进程 API
- LSP 改进
- 添加了
semver
模块 - 改进了
flags
模块的类型定义 - 在
dotenv
模块中添加了变量扩展
如果您已经安装了 Deno,可以通过运行以下命令升级到 1.24
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
类型检查和发射性能改进
以前,Deno 在指定 --check
标志时使用 TypeScript 编译器将 TypeScript 代码内部转换为 JavaScript,否则使用 swc。在这个版本中,所有发射都是用 swc 完成的,它快得多。
此外,由于一些架构重构
- 发射不再与
deno check
一起发生。 - 用于存储发射的 JavaScript 的缓存更加健壮。
- Deno 更加智能,如果它之前成功地对某些代码进行了类型检查,它就不会进行类型检查。
总的来说,这些改进应该会有相当大的性能改进,但会根据代码库而有所不同。例如,在第一次运行 deno check
时,在没有缓存的情况下对 oak 的仓库进行类型检查速度快 20%,而在代码发生更改后,后续运行的速度快 70%。在某些情况下,它快了 90% 以上,因为 Deno 更好地识别了它之前成功地进行了类型检查。
unhandledrejection
事件
此版本添加了对 unhandledrejection
事件 的支持。当没有拒绝处理程序的承诺被拒绝时,会触发此事件,即没有 .catch()
处理程序或 .then()
的第二个参数的承诺。
示例
// unhandledrejection.js
globalThis.addEventListener("unhandledrejection", (e) => {
console.log("unhandled rejection at:", e.promise, "reason:", e.reason);
e.preventDefault();
});
function Foo() {
this.bar = Promise.reject(new Error("bar not available"));
}
new Foo();
Promise.reject();
运行此程序将打印
$ deno run unhandledrejection.js
unhandled rejection at: Promise {
<rejected> Error: bar not available
at new Foo (file:///dev/unhandled_rejection.js:7:29)
at file:///dev/unhandled_rejection.js:10:1
} reason: Error: bar not available
at new Foo (file:///dev/unhandled_rejection.js:7:29)
at file:///dev/unhandled_rejection.js:10:1
unhandled rejection at: Promise { <rejected> undefined } reason: undefined
此 API 将使我们能够在未来版本的 Node 兼容层中填充 process.on("unhandledRejection")
。
beforeunload
事件
此版本添加了对 beforeunload
事件 的支持。当事件循环没有更多工作要完成并且即将退出时,会触发此事件。安排更多异步工作(如计时器或网络请求)将导致程序继续运行。
示例
// beforeunload.js
let count = 0;
console.log(count);
globalThis.addEventListener("beforeunload", (e) => {
console.log("About to exit...");
if (count < 4) {
e.preventDefault();
console.log("Scheduling more work...");
setTimeout(() => {
console.log(count);
}, 100);
}
count++;
});
globalThis.addEventListener("unload", (e) => {
console.log("Exiting");
});
count++;
console.log(count);
setTimeout(() => {
count++;
console.log(count);
}, 100);
运行此程序将打印
$ deno run beforeunload.js
0
1
2
About to exit...
Scheduling more work...
3
About to exit...
Scheduling more work...
4
About to exit...
Exiting
这使我们能够在 Node 兼容层中填充 process.on("beforeExit")
。
import.meta.resolve()
API
Deno 自 v1.0 起支持 import.meta
。两个可用的选项是 import.meta.url
用于告知当前模块的 URL,以及 import.meta.main
用于让您知道当前模块是否是程序的入口点。此版本添加了对 import.meta.resolve()
API 的支持,该 API 使您能够相对于当前模块解析说明符。
之前
const worker = new Worker(new URL("./worker.ts", import.meta.url).href);
之后
const worker = new Worker(import.meta.resolve("./worker.ts"));
此 API 相对于 URL
API 的优势在于它考虑了当前应用的 导入映射,这使您能够解析“裸”说明符。
加载这样的导入映射后…
{
"imports": {
"fresh": "https://deno.land/x/[email protected]/dev.ts"
}
}
…您现在可以解析
// resolve.js
console.log(import.meta.resolve("fresh"));
$ deno run resolve.js
https://deno.land/x/[email protected]/dev.ts
FFI API 改进
此版本在不稳定的 外部函数接口 API 中添加了新功能和性能改进
回调
FFI 调用现在支持将 JS 回调作为线程安全的 C 函数传递。
新的 Deno.UnsafeCallback
类用于准备用作参数的 JS 函数
// type AddFunc = extern "C" fn (a: u32, b: u32) -> u32;
//
// #[no_mangle]
// extern "C" fn add(js_func: AddFunc) -> u32 {
// js_func(2, 3)
// }
//
const { symbols: { add } } = Deno.dlopen("libtest.so", {
add: {
parameters: ["function"],
return: "u32",
// Use `callback: true` indicate that this call might
// callback into JS.
callback: true,
}
});
const jsAdd(a, b) { return a + b };
const cFunction = new Deno.UnsafeCallback({
parameters: ["u32", "u32"],
result: ["u32"]
}, jsAdd);
const result = add(cFunction.pointer); // 5
感谢 Aapo Alasuutari 实现此功能。
改进的 FFI 调用性能
在此版本中,许多 FFI 调用现在快了 ~200 倍。这是通过结合 V8 快速 API 调用和 JIT 跳板实现的。Deno 动态生成优化过的 JIT 代码以调用 FFI,同时利用 V8 快速 API 调用。
之前
cpu: Apple M1
runtime: deno 1.23.0 (aarch64-apple-darwin)
file:///ffi_bench.js
benchmark time (avg) (min … max) p75 p99 p995
------------------------------------------------------------------------- -----------------------------
nop() 436.71 ns/iter (403.72 ns … 1.26 µs) 408.97 ns 968.96 ns 1.26 µs
add_u32() 627.42 ns/iter (619.14 ns … 652.66 ns) 630.02 ns 652.66 ns 652.66 ns
之后
cpu: Apple M1
runtime: deno 1.24.0 (aarch64-apple-darwin)
file:///ffi_bench.js
benchmark time (avg) (min … max) p75 p99 p995
------------------------------------------------------------------------- -----------------------------
nop() 2.23 ns/iter (2.18 ns … 12.32 ns) 2.2 ns 2.36 ns 2.67 ns
add_u32() 4.79 ns/iter (4.7 ns … 11.91 ns) 4.73 ns 5.77 ns 10.05 ns
将来,我们将扩展这种优化以适用于 TypedArrays 和安全数字指针。
有关更多信息,请参阅 denoland/deno#15125 和 denoland/deno#15139。
Deno.UnsafePointer
已移除 之前,Deno 中的指针使用间接类 Deno.UnsafePointer
表示。Deno.UnsafePointer
已被 bigint
取代。
因此,Deno.UnsafePointer.of
现在返回 bigint
const ptr: bigint = Deno.UnsafePointer.of(new Uint8Array([1, 2, 3]));
call_symbol(ptr, 3);
deno test
改进
在配置文件中包含和排除路径
以前,在运行 deno test
时,如果要包含或排除特定路径,则需要将其指定为 CLI 参数。
例如
# only test these paths
deno test src/fetch_test.ts src/signal_test.ts
# exclude testing this path
deno test --ignore=out/
在某些项目中,每次都需要在命令行中提供这些参数并不是很理想,因此在这个版本中,您可以在 Deno 的 配置文件 中指定这些选项。
{
"test": {
"files": {
"include": [
"src/fetch_test.ts",
"src/signal_test.ts"
]
}
}
}
或者更可能是
{
"test": {
"files": {
"exclude": ["out/"]
}
}
}
然后,在与配置文件相同目录树中运行 deno test
将会考虑这些选项。
感谢 @roj1512 为此功能做出的贡献。
--parallel
标志
在之前的版本中,可以通过使用 --jobs
CLI 标志来并行运行测试。
deno test --jobs
# or specify the amount of parallelism
deno test --jobs 4
然而,这个名称的可发现性并不高。此外,它在设计上存在一个疏忽,即 --jobs
在提供数值时不需要在其后使用等号(例如:--jobs=4
),这意味着如果您没有提供数值,则该标志需要作为提供的最后一个参数(例如:deno test --jobs my_test_file.ts
会在解析 “my_test_file.ts” 为数值时出错,因此您需要编写 deno test my_test_file.ts --jobs
)。
在这个版本中,--jobs
标志已被软弃用,并用一个警告和一个新的 --parallel
标志代替。
deno test --parallel
此外,现在不再可以通过 CLI 参数来限制最大并行度,因为这个数值通常依赖于系统。出于这个原因,它已被移至 DENO_JOBS
环境变量(例如:DENO_JOBS=4
)。
感谢 Mark Ladyshau 为此功能做出的贡献。
新的子进程 API 的更新
在 Deno v1.21 中,我们 引入了一个新的不稳定子进程 API。此版本对该 API 做出了重大更新。
首先,我们更改了 stdio 流的类型;不再使用描述哪些流可用的复杂泛型类型,而是改用简单且始终可用的流。如果您尝试访问这些流之一,而它未设置为 “piped”,则会抛出一个 TypeError
。默认值已在此处记录 这里。
之前
// spawn.ts
const child = Deno.spawnChild("echo", {
args: ["hello"],
stdout: "piped",
stderr: "null",
});
const readableStdout = child.stdout.pipeThrough(new TextDecoderStream());
const readableStderr = child.stderr.pipeThrough(new TextDecoderStream());
$ deno check --unstable spawn.ts
Check file:///dev/spawn.ts
error: TS2531 [ERROR]: Object is possibly 'null'.
const readableStderr = child.stderr.pipeThrough(new TextDecoderStream());
~~~~~~~~~~~~
at file:///dev/spawn.ts:7:24
$ deno run --allow-run --unstable spawn.ts
error: Uncaught TypeError: Cannot read properties of null (reading 'pipeThrough')
const readableStderr = child.stderr.pipeThrough(new TextDecoderStream());
^
at file:///dev/spawn.ts:7:37
之后
const child = Deno.spawnChild("echo", {
args: ["hello"],
stdout: "piped",
stderr: "null",
});
const readableStdout = child.stdout.pipeThrough(new TextDecoderStream());
const readableStderr = child.stderr.pipeThrough(new TextDecoderStream());
$ deno check --unstable spawn.ts
Check file:///dev/spawn.ts
$ deno run --allow-run --unstable spawn.ts
error: Uncaught TypeError: stderr is not piped
const readableStderr = child.stderr.pipeThrough(new TextDecoderStream());
^
at Child.get stderr (deno:runtime/js/40_spawn.js:107:15)
at file:///dev/spawn.ts:7:30
接下来,我们更改了 SpawnOutput
类型的签名,使其扩展 ChildStatus
,而不是将其作为字段嵌入。
之前
const { status, stdout } = await Deno.spawn("echo", {
args: ["hello"],
});
console.log(status.success);
console.log(status.code);
console.log(status.signal);
console.log(stdout);
之后
const { success, code, signal, stdout } = await Deno.spawn("echo", {
args: ["hello"],
});
console.log(success);
console.log(code);
console.log(signal);
console.log(stdout);
最后,添加了两个新方法 Child.ref()
和 Child.unref()
,允许您告诉 Deno 在退出程序之前不要等待子进程完成。这些 API 对于需要在后台运行子进程的程序非常有用。例如,您可能希望在后台启动 esbuild
,以便能够按需转译文件,但您不希望该子进程阻止程序退出。
感谢 Nayeem Rahman 为此功能做出的贡献!
LSP 改进
此版本在编辑器中提供了更好的自动导入支持,并且在缓存依赖项之后不再需要重启 LSP,这是之前在某些情况下所必需的。
导入映射快速修复和诊断
LSP 现在提供了一个快速修复,可以将绝对说明符转换为使用导入映射中的条目(如果存在)。
例如,假设有以下导入映射
{
"imports": {
"std/": "https://deno.land/[email protected]/"
}
}
快速修复将更改诸如 "https://deno.land/[email protected]/path/mod.ts"
之类的导入说明符为 "std/path/mod.ts"
。
此外,导入映射诊断现在在 LSP 中可见,以帮助更快地发现问题。
semver
模块
添加 在这个版本中,semver
模块已添加到标准模块中。
您可以使用此模块来验证、比较和操作 semver
字符串。
import * as semver from "https://deno.land/[email protected]/semver/mod.ts";
semver.valid("1.2.3"); // "1.2.3"
semver.valid("a.b.c"); // null
semver.gt("1.2.3", "9.8.7"); // false
semver.lt("1.2.3", "9.8.7"); // true
semver.inc("1.2.3", "patch"); // "1.2.4"
semver.inc("1.2.3", "minor"); // "1.3.0"
semver.inc("1.2.3", "major"); // "2.0.0"
该模块还支持处理 semver 范围。这些表示法与 node-semver
npm 模块兼容。
semver.satisfies("1.2.3", "1.x || >=2.5.0 || 5.0.0 - 7.2.3"); // true
semver.minVersion(">=1.0.0"); // "1.0.0"
此模块是 node-semver
的分支,添加了适当的类型。
感谢 @justjavac 为此功能做出的贡献。
flags
模块中类型定义的改进
在这个版本中,flags
标准模块中 parse
方法的类型定义得到了很大改进。现在,解析后的对象根据给定的选项拥有正确类型的属性。
例如,让我们看下面的例子
import { parse } from "https://deno.land/[email protected]/flags/mod.ts";
const args = parse(Deno.args, {
boolean: ["help"],
string: ["n"],
});
这个例子定义了 help
作为布尔参数,n
作为字符串参数。解析后的对象 args
具有以下类型
type Result = {
[x: string]: unknown;
n?: string | undefined;
help: boolean;
_: (string | number)[];
};
这里 n
和 help
根据给定的选项对象具有正确的类型。
感谢 Benjamin Fischer 为此功能做出的贡献。
dotenv
模块中添加变量扩展
在 在这个版本中,dotenv
标准模块启用了变量扩展。
现在,像下面这样的 .env
文件是有效的
FOO=example
BAR=${FOO}
BAZ=http://${FOO}.com/
QUX=/path/to/${QUUX:-main}
这个 .env
文件的工作原理如下
import "https://deno.land/[email protected]/dotenv/load.ts";
console.log(Deno.env.get("FOO"));
console.log(Deno.env.get("BAR"));
console.log(Deno.env.get("BAZ"));
console.log(Deno.env.get("QUX"));
以上代码输出
example
example
http://example.com/
/path/to/main
感谢 @sevenwithawp 为此功能做出的贡献。