跳到主要内容
Deno 2 终于来了 🎉️
了解更多

Deno 1.20 版本发布说明

Deno 1.20 已被打标签并发布,其中包含以下新功能和更改

如果您已经安装了 Deno,则可以通过运行以下命令将其升级到 1.20

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

更快地调用 Rust

虽然这不是直接面向用户的功能,但当您的代码被执行时,它通常需要在 Javascript 引擎 (V8) 和 Deno 的其余部分(用 Rust 编写的)之间进行通信。

在这个版本中,我们优化了通信层,使其速度提高了约 60% - 我们利用了 Rust 的proc macros,从现有的 Rust 代码生成高度优化的 V8 绑定。

宏优化了对未使用的参数的反序列化,加快了指标收集速度,并为将来与 V8 Fast API 集成提供了基础,这将进一步提高 Javascript 和 Rust 之间的性能。

以前版本的 Deno 使用一个通用的 V8 绑定以及一个路由机制来调用 ops(我们称之为 Rust 和 V8 之间的消息)。使用这个新的 proc macro,每个 op 都获得了它自己的 V8 绑定,从而消除了对路由 ops 的需求。

以下是几个基线开销基准测试

a graph demonstrating the significant performance improvement of internal Deno ops

总的来说,由于这个更改和其他优化,Deno 1.20 将 1mb 字符串的 base64 往返时间从 Deno 1.19 中的 ~125ms 提高到 ~3.3ms!

有关更多详细信息,请深入了解这个 PR

HTTP 响应主体的自动压缩

Deno 的原生 HTTP 服务器现在支持对响应主体进行自动压缩。当客户端请求支持 gzip 或 brotli 压缩,而您的服务器响应了一个不是流的主体时,主体将在 Deno 中自动压缩,无需您进行任何配置。

import { serve } from "https://deno.land/[email protected]/http/server.ts";

function handler(req: Request): Response {
  const body = JSON.stringify({
    hello: "deno",
    now: "with",
    compressed: "body",
  });
  return new Response(body, {
    headers: {
      "content-type": "application/json; charset=utf-8",
    },
  });
}

serve(handler, { port: 4242 });

在内部,Deno 将分析 Accept-Encoding 标头,并确保响应主体的内容类型可压缩,并自动压缩响应。

> curl -I --request GET --url https://127.0.0.1:4242 --H "Accept-Encoding: gzip, deflate, br"

HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
vary: Accept-Encoding
content-encoding: gzip
content-length: 72
date: Tue, 15 Mar 2022 00:00:00 GMT

还有一些其他细节和注意事项。请查看手册中的自动主体压缩,以了解更多详细信息。

新子命令:deno bench

对代码进行基准测试是识别性能问题和回归的有效方法,此版本添加了新的 deno bench 子命令和 Deno.bench() API。deno bench 模仿了 deno test,并且以类似的方式工作。

首先,让我们创建一个文件 url_bench.ts,并使用 Deno.bench() 函数注册一个基准测试。

url_bench.ts

Deno.bench("URL parsing", () => {
  new URL("https://deno.land");
});

其次,使用 deno bench 子命令运行基准测试(目前需要 --unstable,因为它是一个新的 API)。

deno bench --unstable url_bench.ts
running 1 bench from file:///dev/url_bench.ts
bench URL parsing ... 1000 iterations 23,063 ns/iter (208..356,041 ns/iter) ok (1s)

bench result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (1s)

默认情况下,每个注册的基准测试案例将运行 2000 次:首先将执行一千次预热迭代,以允许 V8 Javascript 引擎优化您的代码;然后测量并报告另外一千次迭代。

您可以通过分别设置 Deno.BenchDefinition.warmupDeno.BenchDefinition.n 来自定义预热和测量迭代次数。

// Do 100k warmup runs and 1 million measured runs
Deno.bench({ warmup: 1e5, n: 1e6 }, function resolveUrl() {
  new URL("./foo.js", import.meta.url);
});

要了解有关 deno bench 的更多信息,请访问手册

我们计划在不久的将来为 deno bench 添加更多功能,包括

  • 带有百分位数排名的更详细的报告
  • JSON 和 CSV 报告输出
  • 与语言服务器集成,以便它可以在编辑器中使用

我们也正在寻求社区的反馈,以帮助塑造该功能并使其稳定。

新子命令:deno task

Deno 1.20 添加了一个新的任务运行器,以提供一种便捷的方式来定义和执行特定于代码库开发的自定义命令。这类似于 npm 脚本或 makefile,旨在以跨平台的方式工作。

假设我们有以下在代码库中经常使用的命令

deno run --allow-read=. scripts/analyze.js

我们可以将其定义在Deno 配置文件中

{
  "tasks": {
    "analyze": "deno run --allow-read=. scripts/analyze.js"
  }
}

然后,当在自动解析此配置文件的目录中时,可以通过调用 deno task <task-name> 来运行任务。因此,在本例中,要执行 analyze 任务,我们将运行

deno task analyze

或者一个更复杂的示例

{
  "tasks": {
    // 1. Executes `deno task npm:build` with the `BUILD`
    //    environment variable set to "prod"
    // 2. On success, runs `cd dist` to change the working
    //    directory to the previous command's build output directory.
    // 3. Finally runs `npm publish` to publish the built npm
    //    package in the current directory (dist) to npm.
    "npm:publish": "BUILD=prod deno task npm:build && cd dist && npm publish",
    // Builds an npm package of our deno module using dnt (https://github.com/denoland/dnt)
    "npm:build": "deno run -A scripts/build_npm_package.js"
  }
}

使用的语法是 POSIX 类 shell 的一个子集,可以在 Windows、Mac 和 Linux 上跨平台运行。此外,它还附带了一些内置的跨平台命令,例如 mkdircpmvrmsleep。此共享语法和内置命令的存在是为了消除对 npm 生态系统中出现的额外跨平台工具(例如 cross-envrimraf 等)的需求。

有关更详细的概述,请参阅手册

请注意,deno task 不稳定,将来可能会发生重大更改。我们感谢您的反馈,以帮助塑造此功能。

在配置文件中指定导入映射

以前,指定导入映射 文件需要始终在命令行上提供 --import-map 标志。

deno run --import-map=import_map.json main.ts

在 Deno 1.20 中,现在可以在Deno 配置文件中 指定它。

{
  "importMap": "import_map.json"
}

好处是,如果您的当前工作目录解析了一个配置文件,或者您通过 --config=<FILE> 指定了一个配置文件,那么您不再需要也记住指定 --import-map=<FILE> 标志。

此外,配置文件充当单一事实来源。例如,升级到 Deno 1.20 后,您可以将 LSP 导入映射配置移动到 deno.json(例如,在 VSCode 中,如果您希望将编辑器设置中的 "deno.importMap": "<FILE>" 配置仅保留在您的 deno.jsonc 配置文件中,可以将其移动到该文件中)。

不再要求 TLS/HTTPS 信息为外部文件

安全的 HTTPS 请求需要证书和私钥信息来支持 TLS 协议以保护这些连接。以前,Deno 仅允许将证书和私钥存储为磁盘上的物理文件。在这个版本中,我们为 Deno.listenTls() API 添加了 certkey 选项,并弃用了 certFilekeyFile

这允许用户从任何来源以字符串形式加载 PEM 证书和私钥。例如

const listener = Deno.listenTls({
  hostname: "localhost",
  port: 6969,
  cert: await Deno.readTextFile("localhost.crt"),
  key: await Deno.readTextFile("localhost.key"),
});

低级 API 用于升级 HTTP 连接

此版本添加了一个新的 Deno 命名空间 API:Deno.upgradeHttp()。它尚不稳定,需要使用--unstable 标志才能使用。

您可以使用此 API 对 HTTP 连接执行升级,从而允许基于 HTTP 实现更高级别的协议(如 WebSockets);该 API 适用于 TCP、TLS 和 Unix 套接字连接。

这是一个低级 API,大多数用户不需要直接使用它。

使用 TCP 连接的示例

import { serve } from "https://deno.land/[email protected]/http/server.ts";

serve((req) => {
  const p = Deno.upgradeHttp(req);

  // Run this async IIFE concurrently, first packet won't arrive
  // until we return HTTP101 response.
  (async () => {
    const [conn, firstPacket] = await p;
    const decoder = new TextDecoder();
    const text = decoder.decode(firstPacket);
    console.log(text);
    // ... perform some operation
    conn.close();
  })();

  // HTTP101 - Switching Protocols
  return new Response(null, { status: 101 });
});

FFI API 支持只读全局静态

此版本添加了在外部函数接口 API中使用全局静态的能力。

假设以下定义

#[no_mangle]
pub static static_u32: u32 = 42;

#[repr(C)]
pub struct Structure {
  _data: u32,
}

#[no_mangle]
pub static static_ptr: Structure = Structure { _data: 42 };

您现在可以在用户代码中访问它们

const dylib = Deno.dlopen("./path/to/lib.so", {
  "static_u32": {
    type: "u32",
  },
  "static_ptr": {
    type: "pointer",
  },
});

console.log("Static u32:", dylib.symbols.static_u32);
// Static u32: 42
console.log(
  "Static ptr:",
  dylib.symbols.static_ptr instanceof Deno.UnsafePointer,
);
// Static ptr: true
const view = new Deno.UnsafePointerView(dylib.symbols.static_ptr);
console.log("Static ptr value:", view.getUint32());
// Static ptr value: 42

感谢 Aapo Alasuutari 实现此功能。

使用 deno test 时跟踪操作

在 v1.19 中,我们引入了针对 Deno.test 中的操作和资源清理程序的更佳错误

对于调试具有操作或资源泄漏的测试代码来说,这是一个非常有用的功能。不幸的是,在发布此更改后,我们收到了关于deno test 性能明显下降的报告。性能下降是由过度的堆栈跟踪收集和代码源映射造成的。

由于没有明确的方法来避免对不需要这些带有跟踪的详细错误消息的用户造成性能影响,我们决定默认情况下禁用跟踪功能。从 v1.20 开始,清理程序只有在存在--trace-ops 标志时才会收集详细的跟踪数据。

如果您的测试泄漏操作且您没有使用--trace-ops 调用deno test,系统会提示您在错误消息中这样做

// test.ts
Deno.test("test 1", () => {
  setTimeout(() => {}, 10000);
  setTimeout(() => {}, 10001);
});
$ deno test ./test.ts
Check ./test.ts
running 1 test from ./test.ts
test test 1 ... FAILED (1ms)

failures:

test 1
Test case is leaking async ops.

- 2 async operations to sleep for a duration were started in this test, but never completed. This is often caused by not cancelling a `setTimeout` or `setInterval` call.

To get more details where ops were leaked, run again with --trace-ops flag.

failures:

    test 1

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out ([WILDCARD])

error: Test failed

再次使用--trace-ops 运行将显示泄漏发生的位置

$ deno test --trace-ops file:///dev/test.ts
Check file:///dev/test.ts
running 1 test from ./test.ts
test test 1 ... FAILED (1ms)

failures:

test 1
Test case is leaking async ops.

- 2 async operations to sleep for a duration were started in this test, but never completed. This is often caused by not cancelling a `setTimeout` or `setInterval` call. The operations were started here:
    at Object.opAsync (deno:core/01_core.js:161:42)
    at runAfterTimeout (deno:ext/web/02_timers.js:234:31)
    at initializeTimer (deno:ext/web/02_timers.js:200:5)
    at setTimeout (deno:ext/web/02_timers.js:337:12)
    at test (file:///dev/test.ts:4:3)
    at file:///dev/test.ts:8:27

    at Object.opAsync (deno:core/01_core.js:161:42)
    at runAfterTimeout (deno:ext/web/02_timers.js:234:31)
    at initializeTimer (deno:ext/web/02_timers.js:200:5)
    at setTimeout (deno:ext/web/02_timers.js:337:12)
    at test (file:///dev/test.ts:5:3)
    at file:///dev/test.ts:8:27


failures:

    test 1

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out ([WILDCARD])

error: Test failed

支持 AbortSignal.timeout(ms)

在经过一定时间后中止 AbortSignal 是一种常见的模式。例如

// cancel the fetch if it takes longer than 2 seconds
const controller = new AbortController();
setTimeout(() => controller.abort(), 2_000);
const signal = controller.signal;

try {
  const result = await fetch("https://deno.land", { signal });
  // ...
} catch (err) {
  if (signal.aborted) {
    // handle cancellation here...
  } else {
    throw err;
  }
}

为了简化此模式,WHATWG 引入了一个新的 AbortSignal.timeout(ms) 静态方法。调用时,这将创建一个新的 AbortSignal,该信号将在指定毫秒数内使用“TimeoutError” DOMException 中止。

try {
  const result = await fetch("https://deno.land", {
    // cancel the fetch if it takes longer than 2 seconds
    signal: AbortSignal.timeout(2_000),
  });
  // ...
} catch (err) {
  if (err instanceof DOMException && err.name === "TimeoutError") {
    // handle cancellation here...
  } else {
    throw err;
  }
}

感谢 Andreu Botella 实现此功能。

TCP 和 Unix 连接的专用接口

此版本添加了两个新接口:Deno.TcpConnDeno.UnixConn,它们用作 Deno.connect() API 的返回值类型。

这对类型检查您的代码来说是一个很小的生活质量改进,并使您更容易发现两种连接类型都可用的方法。

重大变更:测试和工作线程中程序化权限的更严格默认值

使用 Worker 接口生成 Web 工作线程或使用 Deno.test 注册测试时,您可以指定要应用于该工作线程或测试的特定权限集。这可以通过将“permissions”对象传递到调用的选项包来实现。这些权限必须是当前权限的子集(以防止权限升级)。如果选项包中没有明确指定权限,则将使用默认权限。

以前,如果您只为某个功能(例如 "net")指定了新权限,那么所有其他功能都将从当前权限继承其权限。以下是一个示例

// This test is run with --allow-read and --allow-write

Deno.test("read only test", { permissions: { read: true } }, () => {
  // This test is run with --allow-read.
  // Counterintuitively, --allow-write is also allowed here because "write" was
  // not explicitly specified in the permissions object, so it defaulted to
  // inherit from the parent.
});

我们避免在次要版本中进行重大变更,但我们强烈认为我们已经将此现有行为弄错了。

我们将省略的功能的默认权限从 "inherit" 改为 "none"。这意味着如果省略了某个权限,但指定了一些权限,则将假定用户不希望在后续范围内授予此权限。再次强调,虽然这是一个重大变更,但我们认为新行为不那么令人意外,并且减少了意外授予权限的情况。

如果您依赖此之前的行为,请更新您的权限包以明确为所有未设置显式值的 功能指定 "inherit"

此版本还添加了一个 Deno.PermissionOptions 接口,该接口统一了为 WorkerOptionsDeno.TestDefinitionDeno.BenchDefinition 指定权限的 API。以前这三个都有单独的实现。

TypeScript 4.6

Deno 1.20 附带了最新稳定的 TypeScript 版本。有关 TypeScript 新功能的更多信息,请参阅TypeScript 的 4.6 博客文章

V8 10.0

此版本升级到最新版本的 V8(10.0,先前为 9.9)。V8 团队 尚未发布有关该版本的信息。当他们发布时,我们将在此处链接它。在没有博客文章的情况下,请查看差异:https://github.com/v8/v8/compare/9.9-lkgr…10.0-lkgr





HN 评论