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

Deno 2.4:deno bundle 回归

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

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.4 中的新功能

deno bundle

Deno 2.4 恢复了 deno bundle 子命令,用于创建单文件 JavaScript 或 TypeScript 捆绑包。它支持服务器端和浏览器平台,兼容 npm 和 JSR 依赖项,并通过 esbuild 自动进行 tree shaking 和代码压缩。

# Bundle with minification
$ deno bundle --minify main.ts

# Set platform to browser (defaults to "deno")
$ deno bundle --platform browser --output bundle.js app.jsx 

# Also generate an external sourcemap
$ deno bundle --platform browser --output bundle.js --sourcemap=external app.jsx

使用 React 为客户端捆绑 JavaScript。

对于 Deno 的老用户来说,你们可能还记得我们以前有 deno bundle,后来被弃用了。JavaScript 捆绑是一个极其复杂的问题,我们当时认为无法正确构建 deno bundle。现在 deno bundle 在底层使用了 esbuild,它将永久保留。

我们正在开发一个运行时 API,以便将来以编程方式进行捆绑。在此基础上,我们计划添加插件,允许您在构建过程中自定义捆绑器如何处理模块。

有关更多信息,请访问deno bundle 参考捆绑文档页面

导入文本和字节

您是否曾想在 JavaScript 模块图中包含某种数据文件?也许是 Markdown 文件、图标或二进制 blob?直到现在,您都必须在运行时加载这些文件,并仔细确保资产与代码一起发布

const image = Deno.readFileSync(import.meta.resolve("./image.png"));
const text = await Deno.readTextFile(import.meta.resolve("./log.txt"));

在 Deno 2.4 中,您可以使用标志 --unstable-raw-imports 将这些文件链接到 JavaScript 图中

import message from "./hello.txt" with { type: "text" };
import bytes from "./hello.txt" with { type: "bytes" };
import imageBytes from "./image.png" with { type: "bytes" };

console.log("Message:", message);
// Message: Hello, Deno!

console.log("Bytes:", bytes);
// Bytes: Uint8Array(12) [
//    72, 101, 108, 108, 111,
//    44,  32,  68, 101, 110,
//   111,  33
// ]

Deno.serve((_req) => {
  return new Response(imageBytes, {
    status: 200,
    headers: {
      "Content-Type": "image/png",
      "Content-Length": imageBytes.byteLength.toString(),
    },
  });
});
// Shows image.png at localhost:8000

您可以将其与 deno bundle 结合使用,从而简化在应用程序中导入非 JavaScript 文件的过程,并避免手动文件 I/O 或中间件。

main.ts
import icon from "./icon.png" with { type: "bytes" };

console.log("Icon", icon);
$ deno bundle --unstable-raw-imports -o app.js main.ts
⚠️ deno bundle is experimental and subject to changes
Bundled 2 modules in 7ms
  app.js 268B

$ deno app.js
Icon Uint8Array(69) [
  137, 80, 78, 71, 13,  10,  26,  10,   0,  0,  0, 13,
   73, 72, 68, 82,  0,   0,   0,   1,   0,  0,  0,  1,
    8,  2,  0,  0,  0, 144, 119,  83, 222,  0,  0,  0,
   12, 73, 68, 65, 84, 120, 218,  99,  96, 96, 96,  0,
    0,  0,  4,  0,  1, 200, 234, 235, 249,  0,  0,  0,
    0, 73, 69, 78, 68, 174,  66,  96, 130
]

此外,此功能也适用于 deno compile,因此您可以将资产嵌入到最终编译的二进制文件中。

spellcheck.js
import dictData from "./dictionary.txt" with { type: "text" };

const DICT = dictData.split(" ");

while (true) {
  let input = prompt("> Enter the word to check: ");
  input = input.trim();
  if (!input) {
    continue;
  }

  if (!DICT.includes(input)) {
    console.error(`${input} is not a known word`);
  } else {
    console.log(`${input} is a known word`);
  }
}
$ deno compile --unstable-raw-imports spellcheck.js
Compile file:///dev/spellcheck.js to spellcheck

$ ./spellcheck
> Enter the word to check:  deno
deno is a known word
> Enter the word to check:  asdf
asdf is not a known word

字节和文本导入功能增强了 Deno 现有原生导入 JSONWasm 文件的能力

import file from "./version.json" with { type: "json" };
console.log(file.version);
// "1.0.0"

import { add } from "./add.wasm";
console.log(add(1, 2));
// 3

虽然导入各种文件类型的功能早已存在(例如在 Next.js 和其他框架中),但这些方法不符合规范,并通过修改语言的领域特定语言和预编译引入了不必要的复杂性。

我们之前就想添加导入其他文件类型的功能,但同时也希望与规范保持一致,避免引入破坏性变更。敏锐的观察者可能会注意到,此功能实际上领先于当前规范(这是导入属性的提案)。然而,由于关于此功能的正在进行的讨论提议的即将推出的功能,我们相信此实现方向是正确的。

内置 OpenTelemetry 现已稳定

2.2 中,我们引入了内置 OpenTelemetry 支持,它可以自动为您的项目收集日志、指标和跟踪。我们很高兴地宣布它现在在 2.4 中已稳定,这意味着您不再需要 --unstable-otel 标志

$ OTEL_DENO=1 deno --allow-net server.ts
Listening on http://localhost:8000/

Deno 的 OpenTelemetry 支持简化了 JavaScript 项目中的可观测性,因为它自动将日志与 HTTP 请求关联起来在 Node 中无需额外配置即可工作,并且在我们的新的 Deno Deploy 中可用于您的所有项目

Deno 内置的 OTel 自动对 console.log 进行自插桩,并将其与 HTTP 请求关联起来。在这里,我们点击查看日志中的跟踪,然后查看该 HTTP 请求中的所有日志。

请注意,您仍然需要设置环境变量 OTEL_DENO=1 来启用插桩,因为它会使用额外的资源,并且我们不希望它一直处于开启状态。

有关 Deno OpenTelemetry 支持的更多信息,请查阅以下资源

使用新的 --preload 标志修改 Deno 环境

我们引入了一个新的--preload 标志,它将在您的主脚本之前执行代码。如果您正在构建自己的平台并需要修改全局变量、加载数据、连接到数据库、安装依赖项或提供其他 API,这将非常有用

setup.ts
delete Deno;
globalThis.window = globalThis;
main.ts
console.log(Deno);
$ deno --preload setup.ts main.ts

error: Uncaught (in promise) ReferenceError: Deno is not defined
console.log(Deno);
            ^
    at file:///main.ts:1:13

虽然我们传统上对允许用户代码修改运行时持谨慎态度,但现在很清楚,在足够多的情况下,这是必要的。此标志可用于 deno rundeno testdeno bench 子命令。

使用 deno update 简化依赖管理

我们添加了一个新的子命令 deno update,它允许您将依赖项更新到最新版本

使用 deno update --latest 将依赖项更新到最新版本。

此命令会将 deno.jsonpackage.json 文件中列出的 npm 和 JSR 依赖项更新到最新兼容的语义化版本。

# Ignore semver constraints
deno update --latest

# Filter packages that match "@std/*"
deno update "@std/*"

# Include all workspace members
deno update --recursive

deno outdated --update 也能做同样的事情,但为了省去您输入额外字符的麻烦,我们添加了别名 deno update

有关 deno update 的更多信息,请查看 Deno 文档

使用 deno run --coverage 收集脚本覆盖率

deno test --coverage 将在收集覆盖率信息的同时执行您的测试套件,这非常容易使用。然而,在某些情况下,例如您需要将子进程作为测试套件的一部分生成时,deno test 将不会收集子进程的覆盖率,从而导致报告不完整。

Deno 2.4 通过引入 --coverage 标志解决了这个问题,您可以将其传递给 deno run

您也可以使用 DENO_COVERAGE_DIR 环境变量代替。

main.ts
function foo(a: number, b: number) {
  if (a > 0) {
    console.log(a);
  } else {
    console.log(a);
  }

  if (b > 0) {
    console.log(b);
  } else {
    console.log(b);
  }
}

const [a = 0, b = 0] = Deno.args;

foo(+a, +b);
$ deno run --coverage main.ts
0
0

$ deno coverage
| File      | Branch % | Line % |
| --------- | -------- | ------ |
| main.ts   |      0.0 |   57.1 |
| All files |      0.0 |   57.1 |

要了解有关覆盖率收集和呈现的更多信息,请访问Deno 文档

哦,顺便说一句,HTML 覆盖率报告现在支持深色模式 🤫。

DENO_COMPAT=1

我们引入了一个新的环境变量 DENO_COMPAT=1,它会告诉 Deno 启用以下标志,以在使用 Deno 的 package.json 优先项目时改善人体工程学

例如

# Before 2.4
deno --unstable-detect-cjs --unstable-node-globals --unstable-bare-node-builtins --unstable-sloppy-imports app.js

# 2.4
DENO_COMPAT=1 deno app.js

未来,此环境变量的范围可能会进一步扩大——例如,在安装包时不需要 npm: 前缀。

我们还将 --unstable-sloppy-imports 标志稳定为 --sloppy-imports,它告诉 Deno 从不包含文件扩展名的导入中推断文件扩展名。虽然 Deno 通常鼓励要求文件扩展名以避免探测文件系统等低效率,但 --sloppy-imports 标志是一种运行使用无扩展名导入代码的方式。

权限变更

--allow-net 标志现在支持子域通配符和 CIDR 范围

subdomain_wildcards.ts
const res1 = await fetch("http://test.foo.localhost");
const res2 = await fetch("http://test.bar.localhost");
$ deno --allow-net=*.foo.localhost subdomain_wildcards.ts
Response {
  url: "http://test.localhost",
  ...
}
┏ ⚠️  Deno requests net access to "test.bar.localhost:80".
┠─ Requested by `fetch()` 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-net
┠─ Run again with --allow-net to bypass this prompt.
┗ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all net permissions) >
cidr_ranges.ts
const res1 = await fetch("http://192.168.0.128:8080");
const res2 = await fetch("http://192.168.0.1:8080");
$ deno --allow-net=192.168.0.128/25 main.ts
Response {
  url: ""http://192.168.0.128:8080"",
  ...
}
┏ ⚠️  Deno requests net access to "192.168.0.1:8080".
┠─ Requested by `fetch()` 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-net
┠─ Run again with --allow-net to bypass this prompt.
┗ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all net permissions) >

Deno 2 添加了 --allow-import 标志,它允许您指定 Deno 可以从哪些远程主机下载和执行代码。

此版本添加了辅助的 --deny-import 标志,允许明确阻止某些主机。

默认情况下,Deno 允许从以下几个主机导入代码

  • deno.land:443
  • jsr.io:443
  • esm.sh:443
  • cdn.jsdelivr.net:443
  • raw.githubusercontent.com:443
  • user.githubusercontent.com:443
main.ts
import * as cowsay from "https://cdn.jsdelivr.net.cn/npm/cowsay";
$ deno main.ts

现在,您可以禁止从其中一个默认主机导入代码

$ deno --deny-import=cdn.jsdelivr.net main.ts
error: Requires import access to "cdn.jsdelivr.net:443", run again with the --allow-import flag
    at file:///main.ts:1:25

为了进一步补充 --allow-import 的使用,Deno.permissions 运行时 API 已修复,以支持 "import" 类型

console.log(Deno.permissions.query({ name: "import" }));
// Promise { PermissionStatus { state: "prompt", onchange: null } }

最后,Deno.execPath() API 不再需要读取权限

main.ts
console.log(Deno.execPath());
# Deno 2.3
$ deno main.ts
┏ ⚠️  Deno requests read access to <exec_path>.
┠─ Requested by `Deno.execPath()` 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) >

# ✅ Now
$ deno main.ts
/dev/.deno/bin/deno

我们进行此更新是因为在常见场景中,要求读取权限最终会比不要求权限更不安全。例如,如果用户想使用当前可执行文件生成 Deno 子进程,他们会这样做:

spawn.ts
new Deno.Command(Deno.execPath(), { args: ["eval", "1+1"] }).outputSync();

并运行它

# Deno 2.3
$ deno --allow-read --allow-run=deno spawn.ts

# ✅ Now
$ deno --allow-run=deno

在 Deno 2.4 之前,可以限制 --allow-read 标志的范围,但这需要预先知道 Deno 可执行文件的位置或一些 shell 扩展技巧。大多数用户理所当然地选择提供一个通用的 --allow-read 标志。

不再需要读取权限允许上述程序仅使用 --allow-run=deno 标志运行。

条件 package.json 导出

此版本支持 npm 包中的条件导出

条件导出是一种机制,允许 npm 包根据用户提供的条件使用不同的导出。

这里的一个显著例子是 react-server 条件,React 生态系统中的库可以使用它来替换导出以用于服务器组件。

app.jsx
import react from "npm:react@19.1.0";

console.log(react.Component);
$ deno app.jsx
Component: [Function: Component]

$ deno --conditions=react-server app.jsx
undefined

您可以指定任意条件,并且数量不限。如果未设置该标志,Deno 默认使用以下条件:denonodeimportdefault

我们预计大多数用户不需要直接使用此标志,但对于您可能依赖的工具来说,这是一个至关重要的选项。

deno run 裸标识符

Deno v2.4 现在支持在 deno run 中使用裸标识符作为入口点。

假设您有这样一个 deno.json

deno.json
{
  "imports": {
    "lume/": "https://deno.land/x/lume@v3.0.4/",
    "file-server": "jsr:@std/http/file-server",
    "cowsay": "npm:cowsay"
  }
}

您可能曾期望能够 deno run file-server 然后就完成了。不幸的是,在 Deno 2.4 之前,这不起作用

# Before Deno v2.4
$ deno run -ERW lume/cli.ts
error: Module not found "file:///dev/lume/cli.ts".

$ deno run file-server
error: Module not found "file:///dev/file-server".

$ deno run -ER cowsay "Hello there!"
error: Module not found "file:///dev/cowsay".

然而,在 2.4 中

# ✅ In Deno 2.4
$ deno run -ERW lume/cli.ts
🍾 Site built into ./_site

$ deno run file-server
Listening on ...

$ deno run -ER cowsay "Hello there!"
 ______________
< Hello there! >
 --------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

在大多数情况下,您还可以省略 run 子命令,例如 deno -ER cowsay "Hello there!"

deno fmt XML 和 SVG 文件

deno fmt 将自动为您格式化 .xml.svg 文件

circle.svg
<svg    width="100"    height="100">
  <circle cx="50" cy="50" 
    r="40" stroke="green" stroke-width="4" 
  fill="yellow" />
</svg>
$ deno fmt
/dev/circle.svg
Formatted 1 file

$ cat circle.svg
<svg width="100" height="100">
  <circle
    cx="50"
    cy="50"
    r="40"
    stroke="green"
    stroke-width="4"
    fill="yellow"
  />
</svg>

此外,现在支持格式化 .mustache 模板,但需要 --unstable-component 标志或 deno.json 中的 unstable.fmt-component 选项。

更好的 tsconfig.json 支持

Deno 会自动发现 deno.json 和/或 package.json 文件旁边的 tsconfig.json 文件。

此版本还带来了对 tsconfig.json 文件的更好处理,增加了对 "references""extends""files""include""exclude" 选项的支持。这意味着在使用流行的前端框架(如 Vue、Svelte、SolidQwik 等)时,您可以获得更好的体验。

更简单的 Node 全局变量

Deno v2.0 添加了 process 全局变量,以提高与现有 npm 生态系统的兼容性。但还有更多的全局变量在 Deno 中对用户代码不可用,但对 npm 包可用

  • Buffer
  • global
  • setImmediate
  • clearImmediate

此外,setTimeoutsetInterval(以及它们的 clear* 对应项)等全局变量根据上下文是用户代码还是 npm 依赖项而有所不同。

为了实现这种分叉,我们使用了相当复杂的设置,利用 V8 内部 API 来决定全局变量在特定调用站点是否可用。尽管这个解决方案很巧妙,但它的性能开销很高——每次访问这些全局变量时,我们都会遍历堆栈并检查调用站点是否来自 npm 包。最后,理解哪些全局变量在哪里可用的心智开销也不小。

在此版本中,我们还向用户代码提供了 BufferglobalsetImmediateclearImmediate。如果您正在运行依赖这些全局变量的现有 Node.js 项目,这将有所帮助,因为您不再需要使用 --unstable-node-globals 标志来公开它们,因为它们现在随时随地都可用。

本地 npm 包

Deno v2.3 中,我们引入了一种使用本地 npm 包的方式。我们收到了很多关于这个问题的积极反馈,但有些人担心 patch 选项很容易与 npm patch 混淆。

该选项已更名为 links(以更好地反映 npm link 等效功能)。当您使用 patch 选项而不是 links 时,您将收到弃用警告。

deno.json
{
+ "links": [
- "patch": [
    "../cowsay"
  ]
}

未来我们可能会开箱即用地支持 npm patch 的等效功能。如果您依赖此工具,请告诉我们

Node.js API 支持

我们再次改进了对 Node.js API 的支持。除了下面列表中您可以深入了解的众多新增功能和修复之外,亮点是对 glob API 的支持,以及对 node:buffernode:eventsnode:querystringnode:quicnode:wasm 等模块实现超过 95% 的兼容性。

我们计划在下一个版本中继续提高这些兼容性数字,重点关注 node:cryptonode:httpnode:tlsnode:workernode:zlib 等模块。

此外,Deno 2.4 将在类型检查时默认使用 @types/node 版本 22.15.14。

最后,这是 Node.js API 修复的完整列表

LSP 改进

Deno LSP 进行了多项改进,包括

其他有趣的事

  • fetch 现在可以通过 Unix 和 Vsock 套接字工作——使用适当的 proxy 选项创建一个 Deno.HttpClient 实例
fetch.ts
const client = Deno.createHttpClient({
  proxy: {
    transport: "unix",
    path: "/path/to/unix.sock",
  },
  // or
  proxy: {
    transport: "vsock",
    cid: 2,
    port: 80,
  },
});

await fetch("http://localhost/ping", { client });
  • deno serve 现在支持 onListen() 回调
server.ts
export default {
  fetch(req) {
    return new Response("Hello world!");
  },
  onListen(addr) {
    console.log(
      `😎 Hello world server started on ${addr.hostname}:${addr.port}`,
    );
  },
} satisfies Deno.ServeDefaultExport;
$ deno serve server.ts
😎 Hello world server started on 0.0.0.0:8000
  • 使用 deno jupyter 更好地管理 Jupyter 内核
# Give kernel an explicit name
$ deno jupyter --install --name="deno_kernel"
✅ Deno kernelspec installed successfully at /Jupyter/kernels/deno_kernel.

# Don't overwrite existing kernel by mistake
$ deno jupyter --install --name="deno_kernel"
error: Deno kernel already exists at /Jupyter/kernels/deno_kernel, run again with `--force` to overwrite it

# Give a display name to the kernel
deno jupyter --install --display="Deno 2.4 kernel"
# Now you will see "Deno 2.4 kernel" in the kernel selector UI
  • Lint 插件现在可以访问注释
my-plugin.ts
export default {
  name: "my-lint-plugin",
  rules: {
    "my-lint-rule": {
      create(context) {
        return {
          Program(node) {
            console.log("Top level comments", node.comments);
          },
          FunctionDeclaration(node) {
            const all = ctx.sourceCode.getAllComments();
            const before = ctx.sourceCode.getCommentsBefore(node);
            const after = ctx.sourceCode.getCommentsAfter(node);
            const inside = ctx.sourceCode.getCommentsInside(node);

            console.log({ program, all, before, after, inside });
          },
        };
      },
    },
  },
} satisfies Deno.lint.Plugin;
  • deno benchdeno coverage 表现在兼容 Markdown

您现在可以直接复制粘贴到 Markdown 文档中,并正确渲染

# Before
-----------------------------------
File          | Branch % | Line % |
-----------------------------------
 parser.ts    |     87.1 |   86.4 |
 tokenizer.ts |     80.0 |   92.3 |
-----------------------------------
 All files    |     86.1 |   87.4 |
-----------------------------------

# After
| File         | Branch % | Line % |
| ------------ | -------- | ------ |
| parser.ts    |     87.1 |   86.4 |
| tokenizer.ts |     80.0 |   92.3 |
| All files    |     86.1 |   87.4 |
  • 在 Windows 上,现在可以正确监听 Ctrl+Close (SIGHUP)
signal.ts
// This will now work correctly on Windows
Deno.addSignalListener("SIGHUP", () => {
  console.log("Got SIGHUP");
});

致谢

没有社区的帮助,我们无法构建 Deno!无论是通过在我们的社区 Discord 服务器回答问题还是 报告 bug,我们都非常感谢您的支持。在此,我们要特别感谢以下对 Deno 2.4 做出贡献的人:林炳权、André Lima、Andy Vu、Asher Gomez、Boye Lillejord-Nygård、chirsz、Christian Svensson、Clarence Delmacio Manuel、ctrl+d、Cyan、Daniel Osvaldo R、David Emanuel Buchmann、Edilson Pateguana、Efe、Elias Rhouzlane、familyboat、Hajime-san、ikkz、James Bronder、Janosh Riebesell、JasperVanEsveld、Jeff Hykin、Jonh Alexis、Kenta Moriuchi、Kingsword、Laurence Rowe、Lino Le Van、LongYinan、Marshall Walker、MikaelUrankar、nana4gonta、narumincho、Nicholas Berlette、scarf、Scott Kyle、sgasho、Simon Lecoq、stefnotch、Timothy J. Aveni、ud2、Volker Schlecht 和 zino。

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

信不信由你,上面列出的更改仍然没有告诉您 2.4 中所有改进的内容。您可以在 GitHub 上查看 Deno 2.4 中合并的完整拉取请求列表

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

🚨️ Deno Deploy 已有重大更新! 🚨️

以及 更多功能!

立即获取早期访问权限。