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
类型检查和代码生成性能改进
此前,当指定 --check
标志时,Deno 内部使用 TypeScript 编译器将 TypeScript 代码转换为 JavaScript,否则使用 swc。在此版本中,所有代码生成都通过 swc 完成,速度大大加快。
此外,由于一些架构重构
- 使用
deno check
时不再进行代码生成。 - 用于存储生成的 JavaScript 的缓存更加健壮。
- 如果 Deno 在过去成功进行过类型检查,它会更智能地跳过类型检查。
总的来说,这些改进应该会带来显著的性能提升,但具体效果会因代码库而异。例如,在首次运行 deno check
时,对 oak 仓库进行无缓存的类型检查速度提高了 20%,而在代码发生更改后,后续运行的速度提高了 70%。在某些情况下,速度提高了 90% 以上,因为 Deno 更擅长识别它之前已成功进行过类型检查的代码。
unhandledrejection
事件
此版本增加了对 unhandledrejection
事件的支持。当一个没有拒绝处理器的 Promise(即没有 .catch()
处理器或 .then()
的第二个参数的 Promise)被拒绝时,会触发此事件。
示例
// 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 兼容层中 polyfill 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 兼容层中 polyfill 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/fresh@1.0.1/dev.ts"
}
}
…您现在可以解析
// resolve.js
console.log(import.meta.resolve("fresh"));
$ deno run resolve.js
https://deno.land/x/fresh@1.0.1/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 trampolines 实现的。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/std@0.148.0/"
}
}
快速修复会将诸如 "https://deno.land/std@0.148.0/path/mod.ts"
之类的导入说明符更改为 "std/path/mod.ts"
。
此外,导入映射诊断现在在 LSP 中显示,以帮助更快地发现问题。
semver
模块
新增 在此版本中,semver
模块已添加到标准模块中。
您可以使用此模块验证、比较和操作 semver
字符串。
import * as semver from "https://deno.land/std@0.149.0/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/std@0.149.0/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/std@0.149.0/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 贡献了此功能。