跳到主要内容
Deno 2.4 已发布,带来 deno bundle、字节/文本导入、OTel 稳定版等新特性
了解更多
Deno 1.24 Release Notes

Deno 1.24 已打标签并发布,包含以下新特性和变更

如果您已经安装了 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 完成,速度大大加快。

此外,由于一些架构重构

  1. 使用 deno check 时不再进行代码生成。
  2. 用于存储生成的 JavaScript 的缓存更加健壮。
  3. 如果 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#15125denoland/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)[];
};

这里,nhelp 根据给定的选项对象进行了正确类型化。

感谢 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 贡献了此功能。