跳到主要内容
Deno 2.4 发布,带来 deno bundle、字节/文本导入、OTel 稳定版等功能
了解更多
Deno 2.2

Deno 2.2:OpenTelemetry、Lint 插件、node:sqlite

要升级到 Deno 2.2,请在您的终端中运行以下命令

deno upgrade

如果 Deno 尚未安装,请运行以下命令之一进行安装,或者在此处了解如何安装

# Using Shell (macOS and Linux):
curl -fsSL https://deno.land/install.sh | sh

# Using PowerShell (Windows):
iwr https://deno.land/install.ps1 -useb | iex

Deno 2.2 有哪些新功能

此版本中包含大量内容。以下是快速概述,可帮助您深入了解您最关心的内容

您还可以在v2.2 演示视频中查看所有这些功能的演示。

内置 OpenTelemetry 集成

Deno 2.2 内置了OpenTelemetry,用于监控日志、指标和跟踪。

Deno 自动监测诸如 console.logDeno.servefetch 等 API。您还可以使用 npm:@opentelemetry/api 来监测自己的代码。

让我们看看 Deno.serve API 的一些日志和跟踪

server.ts
Deno.serve((req) => {
  console.log("Received request for", req.url);
  return new Response("Hello world");
});

要捕获可观测性数据,您需要提供一个 OTLP 端点。如果您已经设置了可观测性系统,则可以使用它。如果没有,最简单的运行方式是在 Docker 中启动本地 LGTM 堆栈

$ docker run --name lgtm -p 3000:3000 -p 4317:4317 -p 4318:4318 --rm -ti \
    -v "$PWD"/lgtm/grafana:/data/grafana \
    -v "$PWD"/lgtm/prometheus:/data/prometheus \
    -v "$PWD"/lgtm/loki:/data/loki \
    -e GF_PATHS_DATA=/data/grafana \
    docker.io/grafana/otel-lgtm:0.8.1

现在我们准备好运行服务器并捕获一些数据

OpenTelemetry 集成的 API 仍可能更改。因此,它被指定为“不稳定 API”,需要使用 --unstable-otel 标志才能使用。

$ OTEL_DENO=true deno run --unstable-otel --allow-net server.ts
Listening on http://localhost:8000/

现在,使用浏览器或 curl 连接到我们的服务器

$ curl http://localhost:8000
Hello world

您现在可以在您的可观测性系统中查看日志和跟踪。如果您使用 LGTM 堆栈,可以在http://localhost:3000访问 Grafana 仪表板。

Demo of OTEL logs

来自服务器请求的日志示例

Demo of OTEL traces

Deno.serve() API 处理的请求跟踪

我们在这里只是触及了皮毛。Deno 还导出了自动监测的指标,您可以使用 npm:@opentelemetry/api 包创建自己的指标和跟踪范围。要了解更多信息,请访问Deno 文档

您可以在此 v2.2 演示视频中观看 OpenTelemetry 集成的演示

Linter 更新

Deno 2.2 对deno lint进行了重大升级,包括新的插件系统和 15 条新规则,特别适用于 React 和 Preact 用户。

新增内置 lint 规则

此版本增加了新的 lint 规则,主要针对 JSX 和 React 的最佳实践。

为了补充这些规则,新增了两个标签:jsxreact

请在Deno 文档中查看可用 lint 规则和标签的完整列表。

JavaScript 插件 API

deno lint 最大的更新是可以通过新的插件系统扩展其功能。

注意:插件 API 仍处于其 API 可能发生变化的阶段,因此目前被标记为不稳定功能。

虽然有许多内置规则,但在某些情况下,您可能需要针对特定项目量身定制的规则。

该插件 API 以ESLint 插件 API为模型,但并非 100% 兼容。实际上,我们预计一些现有的 ESLint 插件可以与 deno lint 无障碍地工作。

这是一个简单 lint 插件的示例。我们将创建一个插件,如果变量名为 foo,则报告错误

deno.json
{
  "lint": {
    "plugins": ["./my-plugin.ts"]
  }
}
my-plugin.ts
export default {
  name: "my-lint-plugin",
  rules: {
    "my-lint-rule": {
      create(context) {
        return {
          VariableDeclarator(node) {
            if (node.id.type === "Identifier" && node.id.name === "foo") {
              context.report({
                node,
                message: "Use more descriptive name than `foo`",
              });
            }
          },
        };
      },
    },
  },
} satisfies Deno.lint.Plugin;
main.js
const foo = "foo";
console.log(foo);
$ deno lint main.js
error[my-lint-plugin/my-lint-rule]: Use more descriptive name than `foo`
 --> /dev/main.js:1:7
  | 
1 | const foo = "foo";
  |       ^^^^^^^^^^^


Found 1 problem
Checked 1 file

除了基于访问者的 API 之外,您还可以使用类似 CSS 的选择器来定位特定节点。让我们使用选择器语法重写上述规则。

my-plugin.ts
export default {
  name: "my-lint-plugin",
  rules: {
    "my-lint-rule": {
      create(context) {
        return {
          'VariableDeclarator[id.name="foo"]'(node) {
            context.report({
              node,
              message: "Use more descriptive name than `foo`",
            });
          },
        };
      },
    },
  },
} satisfies Deno.lint.Plugin;

Lint 插件可以用 TypeScript 编写,Deno 在 Deno.lint 命名空间下开箱即用地提供了完整的类型声明。

您可以使用本地 lint 插件,以及来自 npm 和 JSR 的插件

deno.json
{
  "lint": {
    "plugins": [
      "./my-plugin.ts",
      "jsr:@my-scope/lint-plugin",
      "npm:@my-scope/other-plugin"
    ]
  }
}

Deno 文档中阅读更多关于 deno lint 插件 API 的信息。

deno lint--rules 标志行为更新

此版本中,deno lint --rules 已更改为始终打印所有可用的 lint 规则,并标记当前配置中启用了哪些规则。

此外,deno lint --rules --json 不再打印原始 Markdown 文档,而是链接到Deno 文档中的相关规则页面。

您可以在此 v2.2 演示视频中观看 lint 插件 API 的更详细演示

deno check 的改进

Deno 用于类型检查的工具deno check在此版本中获得了两项重大改进

  • JSDoc 标签现在受到尊重
  • compilerOptions 的设置现在可以按工作区成员配置

让我们详细看看每个方面

JSDoc @import 标签现在受到尊重

类型检查时,@import JSDoc 标签现在受到尊重。这允许您内联定义导入,从而改进 JavaScript 文件中的类型检查。

add.ts
export function add(a: number, b: number): number {
  return a + b;
}
main.js
/** @import { add } from "./add.ts" */

/**
 * @param {typeof add} value
 */
export function addHere(value) {
  return value(1, 2);
}

addHere("");
$ deno check main.js
Check file:///main.js
error: TS2345 [ERROR]: Argument of type 'string' is not assignable to parameter of type '(a: number, b: number) => number'.
addHere("");
        ~~
    at file:///main.js:10:9

工作区范围的 compilerOptions 设置

以前,deno.json 将相同的 compilerOptions 应用于所有工作区成员,使得前端和后端难以单独配置。现在,工作区成员可以定义自己的设置。

由于新增的对每个工作区成员的 compilerOptions 支持,现在可以在前端代码的目录中指定不同的 compilerOptions.lib 设置。

deno.json
{
  "workspace": [
    "./server",
    "./client"
  ],
  "compilerOptions": {
    "checkJs": true
  }
}
client/deno.json
{
  "compilerOptions": {
    "lib": ["dom", "esnext"]
  }
}
client/main.js
document.body.onload = () => {
  const div = document.createElement("div");
  document.body.appendChild(div);
  document.body.appendChild("not a DOM element");
};
$ deno check client/main.js
Check file:///client/main.js
TS2345 [ERROR]: Argument of type 'string' is not assignable to parameter of type 'Node'.
  document.body.appendChild("not a DOM node");
                            ~~~~~~~~~~~~~~~~
    at file:///client/main.js:4:29

error: Type checking failed.

您可以在此 v2.2 演示视频中观看 deno check 更新的演示

deno lsp 的改进

Deno 2.2 使deno lsp更快、响应更及时,并为 Web 框架用户带来了重大改进。

这里无法详细介绍,但让我们看看一些亮点

deno task 的实用更新

此版本为deno task带来了一些更新。首先将帮助 deno task 变得更健壮和可预测

还有两项功能使 deno task 更实用、更方便。我们将详细介绍这些功能

  • 任务名称中的通配符
  • 运行无命令的任务

任务名称中的通配符

您现在可以在任务名称中使用通配符运行 deno task,如下所示

deno.json
{
  "tasks": {
    "start-client": "echo 'client started'",
    "start-server": "echo 'server started'"
  }
}
$ deno task "start-*"
Task start-client echo 'client started'
client started
Task start-server echo 'server started'
server started

请务必用引号将包含通配符的任务名称括起来,否则您的 shell 将尝试扩展此字符,您将遇到错误。

通配符字符(*)可以放置在任何位置以匹配任务名称。所有匹配通配符的任务都将并行运行。

不带命令运行任务

任务依赖在v2.1中变得流行。现在,您可以通过定义不带命令的任务来更轻松地分组任务

deno.json
{
  "tasks": {
    "dev-client": "deno run --watch client/mod.ts",
    "dev-server": "deno run --watch sever/mod.ts",
    "dev": {
      "dependencies": ["dev-client", "dev-server"]
    }
  }
}

在上述示例中,dev 任务用于分组 dev-clientdev-server 任务,但它本身没有命令。这是一种方便的方式,可以将任务组合在一起,通过一个任务名称运行。

您可以在此视频中观看 deno task 在 v2.2 演示中的更新演示

依赖管理

Deno 2.2 附带了对 deno outdated 工具的更改,增加了一种新的交互式方式来更新依赖项。

deno outdated –update –interactive 演示

除了这项改进之外,还修复了多项错误,使得 deno installdeno outdated 更健壮、更快速。包括但不限于

支持 node:sqlite

此版本为 Deno 带来了备受期待的 node:sqlite 模块,使内存或本地数据库操作变得简单。

db.ts
import { DatabaseSync } from "node:sqlite";

const db = new DatabaseSync("test.db");

db.exec(`
CREATE TABLE IF NOT EXISTS people (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  name TEXT,
  age INTEGER
);`);

const query = db.prepare(`INSERT INTO people (name, age) VALUES (?, ?);`);
query.run("Bob", 40);

const rows = db.prepare("SELECT id, name, age FROM people").all();
console.log("People:");
for (const row of rows) {
  console.log(row);
}

db.close();
$ deno run --allow-read --allow-write db.ts
People:
[Object: null prototype] { id: 1, name: "Bob", age: 40 }

请查阅我们文档中的示例以及完整的 API 参考

Deno.cwd() 的权限检查放宽

Deno 2.2 在使用 Deno.cwd() API 时,不再需要完整的 --allow-read 权限。

main.js
console.log(Deno.cwd());
Deno v2.1
$ deno main.js
┏ ⚠️  Deno requests read access to <CWD>.
┠─ Requested by `Deno.cwd()` API.
┠─ To see a stack trace for this prompt, set the DENO_TRACE_PERMISSIONS environmental variable.
┠─ Learn more at: https://docs.deno.org.cn/go/--allow-read
┠─ Run again with --allow-read to bypass this prompt.
┗ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all read permissions) > y
/dev
Deno v2.2
$ deno main.js
/dev

在此更改之前,已经可以在没有权限的情况下获取 CWD 路径,例如通过创建错误并检查其堆栈跟踪。

这项更改最初计划在 Deno 2.0 中发布,但未能赶上。我们很高兴在 v2.2 中迎来它。

更小、更快的 deno compile

deno compile 的多项性能和用户体验改进

`deno compile` summary

使用 npm:cowsay 进行 deno compile 的摘要输出

更精确的 deno bench

deno bench 是一个内置工具,可让您快速轻松地对代码进行基准测试。Deno v1.21 更改了 deno bench 的行为,以自动执行基准测试的预热,并自动决定执行多少次迭代,当后续运行之间的时间差异在统计上不显著时停止。

在大多数情况下,这工作得很好,但有时,您希望对执行多少次预热运行和测量运行进行精细控制。为此,Deno v2.2 带回了 Deno.BenchDefinition.nDeno.BenchDefinition.warmup 选项。指定它们将使 deno bench 执行所需次数的运行

url_bench.ts
Deno.bench({ warmup: 1_000, n: 100_000 }, () => {
  new URL("./foo.js", import.meta.url);
});

上述基准测试将精确执行 1000 次“预热运行”——这些运行不进行测量,仅用于“预热”V8 引擎的 JIT 编译器。之后,基准测试将执行 100,000 次测量运行,并根据这些迭代显示指标。

WebTransport 和 QUIC API

Deno 2.2 附带了对 `WebTransport` API 的实验性支持以及新的、不稳定的 Deno.connectQuicDeno.QuicEndpoint API。如果您不熟悉,QUIC(快速 UDP 互联网连接)是一种旨在取代 TCP+TLS 的现代传输协议,也是 HTTP/3 的基础。

由于这些是实验性的,它们的 API 在未来可能会发生变化,因此需要使用 --unstable-net 标志。

让我们看看这些 API 的实际应用。这是一个 QUIC 回显服务器和 WebTransport 客户端的示例。

请注意,WebTransport 需要使用 HTTPS。这些示例使用证书/密钥对;您可以使用 OpenSSL 生成自签名证书:openssl req -x509 -newkey rsa:4096 -keyout my_key.pem -out my_cert.pem -days 365

server.js
const cert = Deno.readTextFileSync("my_cert.crt");
const key = Deno.readTextFileSync("my_cert.key");

const server = new Deno.QuicEndpoint({
  hostname: "localhost",
  port: 8000,
});
const listener = server.listen({
  cert,
  key,
  alpnProtocols: ["h3"],
});

// Run server loop
for await (const conn of listener) {
  const wt = await Deno.upgradeWebTransport(conn);

  handleWebTransport(wt);
}

async function handleWebTransport(wt) {
  await wt.ready;

  (async () => {
    for await (const bidi of wt.incomingBidirectionalStreams) {
      bidi.readable.pipeTo(bidi.writable).catch(() => {});
    }
  })();

  (async () => {
    for await (const stream of wt.incomingUnidirectionalStreams) {
      const out = await wt.createUnidirectionalStream();
      stream.pipeTo(out).catch(() => {});
    }
  })();

  wt.datagrams.readable.pipeTo(wt.datagrams.writable);
}
client.js
import { decodeBase64 } from "jsr:@std/encoding/base64";
import { assertEquals } from "jsr:@std/assert";

const cert = Deno.readTextFileSync("my_cert.crt");
const certHash = await crypto.subtle.digest(
  "SHA-256",
  decodeBase64(cert.split("\n").slice(1, -2).join("")),
);

const client = new WebTransport(
  `https://localhost:8000/path`,
  {
    serverCertificateHashes: [{
      algorithm: "sha-256",
      value: certHash,
    }],
  },
);

await client.ready;

const bi = await client.createBidirectionalStream();

{
  const writer = bi.writable.getWriter();
  await writer.write(new Uint8Array([1, 0, 1, 0]));
  writer.releaseLock();
  const reader = bi.readable.getReader();
  assertEquals(await reader.read(), {
    value: new Uint8Array([1, 0, 1, 0]),
    done: false,
  });
  reader.releaseLock();
}

{
  const uni = await client.createUnidirectionalStream();
  const writer = uni.getWriter();
  await writer.write(new Uint8Array([0, 2, 0, 2]));
  writer.releaseLock();
}

{
  const uni =
    (await client.incomingUnidirectionalStreams.getReader().read()).value;
  const reader = uni!.getReader();
  assertEquals(await reader.read(), {
    value: new Uint8Array([0, 2, 0, 2]),
    done: false,
  });
  reader.releaseLock();
}

await client.datagrams.writable.getWriter().write(
  new Uint8Array([3, 0, 3, 0]),
);
assertEquals(await client.datagrams.readable.getReader().read(), {
  value: new Uint8Array([3, 0, 3, 0]),
  done: false,
});
$ deno run -R --unstable-net server.js
...

$ deno run -R --unstable-net client.js
...

Node.js 和 npm 兼容性改进

Deno 2.2 一如既往地带来了大量 Node.js 和 npm 兼容性改进。以下是亮点列表

process 更改

fs 更改

http 模块更改

zlib 模块更改

worker_threads 模块更改

crypto 模块更改

v8 模块更改

  • v8 模块现在处理 Float16Array 序列化
  • 添加缺少的 node:inspector/promises 模块
  • 阻止 node:child_process 总是继承父环境

其他更改

性能改进

性能改进是每个 Deno 版本的一部分,本次也不例外。以下是一些改进列表

WebGPU 改进

我们的 WebGPU 实现进行了重大改造,修复了许多遇到的问题,并且还应能提高可用 API 的整体性能。

除了这些修复之外,我们的 Jupyter 集成现在能够将 GPUTexture 显示为图像,并将 GPUBuffer 显示为文本

Demo of Jupyter GPUTexture and GPUBuffer

查看一些在 Deno 中使用 WebGPU 的示例.

更小的 Linux 二进制文件

得益于使用了完整的 链接时优化,我们成功地将二进制文件大小减少了近 15MB。这使得 deno 从 137MB 缩小到 122MB。

TypeScript 5.7 和 V8 13.4

Deno 2.2 升级到 TypeScript 5.7V8 13.4,带来了新的语言特性和性能改进。

TypedArray 现在是泛型的

TypeScript 5.7 的一个主要变化是 Uint8Array 和其他 TypedArray 现在是针对 ArrayBufferLike 的泛型。这在使用 SharedArrayBufferArrayBuffer 时提供了更好的类型安全性,但可能需要更新一些代码库。

// Before TypeScript 5.7
const buffer: Uint8Array = new Uint8Array(new ArrayBuffer(8));

// After TypeScript 5.7 (explicitly specifying the buffer type)
const buffer: Uint8Array<SharedArrayBuffer> = new Uint8Array(
  new SharedArrayBuffer(8),
);

此更改可能会引入类型错误。如果您看到类似以下错误

error TS2322: Type 'Buffer' is not assignable to type 'Uint8Array<ArrayBufferLike>'.
error TS2345: Argument of type 'Buffer' is not assignable to parameter of type 'Uint8Array<ArrayBufferLike>'.

您可能需要将 @types/node 更新到最新版本。

有关此更改的更多信息,请参阅微软的公告TypeScript PR

长期支持

Deno v2.1 仍是长期支持版本,并将在未来 6 个月内定期接收错误修复、安全更新和关键性能改进。

致谢

没有社区的帮助,我们无法构建 Deno!无论是通过在我们社区的 Discord 服务器中回答问题,还是报告错误,我们都非常感谢您的支持。在此特别感谢以下人员对 Deno 2.2 的贡献:Aaron Ang, Alvaro Parker, Benjamin Swerdlow, Bhuwan Pandit, Caleb Cox, Charlie Bellini, Cornelius Krassow, Cre3per, Cyan, Dimitris Apostolou, Espen Hovlandsdal, Filip Stevanovic, Gowtham K, Hajime-san, HasanAlrimawi, Ian Bull, Je Xia, João Baptista, Kenta Moriuchi, Kitson Kelly, Masato Yoshioka, Mathias Lykkegaard Lorenzen, Mohammad Sulaiman, Muthuraj Ramalingakumar, Nikolay Karadzhov, Rajhans Jadhao, Rano, Sean McArthur, TateKennington, Tatsuya Kawano, Timothy, Trevor Manz, ZYSzys, hongmengning, ingalless, jia wei, printfn, ryu, siaeyy, ud2。

您想加入 Deno 贡献者的行列吗?在此查看我们的贡献文档,我们期待下次在列表中看到您。

信不信由你,上面列出的更改仍未涵盖 2.2 版本中所有改进。您可以在 GitHub 上查看 Deno 2.2 合并的完整拉取请求列表

感谢您关注我们的 2.2 版本发布,希望您喜欢使用 Deno 进行构建!

TwitterDiscordBlueSkyMastodon 上获取技术支持、分享您的项目,或者只是打个招呼。