Deno 1.19 已标记并发布,包含以下新特性和变更
deno vendor
- 默认权限提示
- 文件、网络套接字和标准 I/O 现在是原生 Web 流
- 现在支持
CompressionStream
和DecompressionStream
Deno.test
中 ops 和资源清理器报错更优化console.log
现在显示日志对象中的循环引用Deno.File
已更名为Deno.FsFile
deno compile
现在工作更可靠- 允许在文件观察器重启时禁用清屏
deno coverage
获得--output
标志- 信号监听器 API 稳定化
- 通过 Unix 套接字提供 HTTP 服务
- 新的不稳定 API:
Deno.Conn#setNoDelay()
和Deno.Conn#setKeepAlive()
- 新的不稳定 API:
Deno.getUid
- 新的不稳定 API:
Deno.networkInterfaces
- LSP 改进
- V8 9.9
如果您已安装 Deno,可以通过运行以下命令升级到 1.19
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
新特性和变更
deno vendor
过去几个月,我们收到用户反馈,希望能够将程序依赖项放入代码仓库中。如果你想确保应用程序只执行一些非常特定的代码,这是一个非常有用的功能。
以前,我们建议用户将 DENO_DIR
检入仓库进行 vendoring。虽然这通常可行,但用户体验不佳。DENO_DIR
中的文件名称由十六进制字符串组成,非直观,在版本控制中看起来不美观。
在此版本中,我们引入了一种改进的依赖项 vendoring 方法:deno vendor
。此子命令可以带有一个或多个模块入口点来执行 vendoring。Deno 将通过分析模块的所有导入和导出,从这些文件中构建模块图。生成的模块列表随后会写入 vendor/
文件夹,其名称尽可能接近原始模块名称。有时我们必须去除或附加一些字符到名称,以使指定符成为有效的文件系统路径。在 vendor 文件夹中,我们生成一个 导入映射,将所有已 vendoring 的远程模块映射到本地 vendor 目录。
要使用您程序中已 vendoring 的依赖项,您只需在 Deno 调用中添加 --import-map=vendor/import_map.json
。您也可以在调用中添加 --no-remote
以完全禁用远程模块的获取(仅允许导入 vendor 目录中的模块)。
$ deno vendor main.ts
$ tree
.
├── main.ts
└── vendor
├── deno.land
├── import_map.json
└── raw.githubusercontent.com
$ deno run --no-remote --import-map=vendor/import_map.json main.ts
vendor 目录应检入版本控制。文件名称在 deno vendor
运行之间保持尽可能一致,以最大程度地减少臃肿的 git diff。
此功能的另一个重要用例是允许您在调试时临时向依赖项代码添加一些 console.log
条目或类似内容。为此,您只需 vendoring 您要修改的特定模块,然后在 vendor 目录中编辑该源代码。调试完成后,您可以再次删除 vendor 目录并照常继续。
我们很乐意听取您对此功能的反馈。我们知道 一些问题,我们将在未来几周内解决,但在大多数情况下,它运行良好。
默认权限提示
一个抱怨是 Deno 需要太多命令行标志。通常这些标志是 --allow-*
权限允许。在此版本之前,如果权限检查失败,Deno 将抛出异常,要求用户每次都指定这些标志。现在在 Deno 1.19 中,如果未授予访问权限,命令行提示将允许用户交互式地接受或拒绝每个单独的访问检查。
# deno run https://deno.land/std/http/file_server.ts
⚠️ ️Deno requests read access to <CWD>. Run again with --allow-read to bypass this prompt.
Allow? [y/n (y = yes allow, n = no deny)] y
⚠️ ️Deno requests net access to "0.0.0.0:4507". Run again with --allow-net to bypass this prompt.
Allow? [y/n (y = yes allow, n = no deny)] y
HTTP server listening on http://localhost:4507/
此功能长期以来一直通过使用 --prompt
标志可用,但现在默认开启。要禁用提示,请使用 --no-prompt
。这些权限提示只会在您连接到 TTY 时发生,因此例如在 CI 脚本中,不应该需要提供 --no-prompt
。
文件、网络套接字和标准 I/O 现在是原生 Web 流
Deno.FsFile
和 Deno.Conn
接口现在分别具有 ReadableStream
和 WritableStream
类型的 readable
和 writable
属性。这使它们能够很好地与使用 Web 流的其他 Web API 集成。例如:
// Download a file from the web, and stream it into a file on disk.
const file = await Deno.create("./example.html");
const resp = await fetch("https://example.com");
await resp.body.pipeTo(file.writable);
// Read from stdin, and stream it to a remote server (streaming upload).
// Would be used like this: `cat file.txt | deno run --allow-net=example.com main.ts`
const resp = await fetch("https://example.com", {
method: "POST",
body: Deno.stdin.readable,
});
console.log("Upload succeeded?", resp.ok);
由于 Deno 中的所有 API 现在都支持开箱即用的 Web 流,包括我们的原生 HTTP 服务器,因此将所有这些 API 组合在一起现在变得非常简单
import { serve } from "https://deno.land/std/http/server.ts";
serve(async (req) => {
if (req.method === "POST") {
// Save the incoming request body to a file on disk
const path = await Deno.makeTempFile();
const file = await Deno.create(path);
await req.body.pipeTo(file.writable);
return new Response(`Saved file to ${path}`);
} else {
// Serve a file from disk
const path = "./example.html";
const file = await Deno.open(path);
return new Response(file.readable, {
headers: { "content-type": "text/html" },
});
}
});
新的 readable
和 writable
属性也可以与内置的流转换器(如 TextEncoderStream
或 CompressionStream
)组合使用。下面是一个程序示例,它从 stdin 读取,执行 gzip 压缩,然后将结果写入 stdout
await Deno.stdin.readable
.pipeThrough(new CompressionStream("gzip"))
.pipeTo(Deno.stdout.writable);
CompressionStream
和 DecompressionStream
现在支持 此版本增加了两个新的内置流转换器,名为 CompressionStream
和 DecompressionStream
。此 Web 标准 API 允许您以多种文件格式(当前为 gzip
和 deflate
)压缩和解压缩数据。
该 API 已在 Chrome 中发布,并有望很快在其他浏览器中推出。
以下是执行流式解压缩 .gz
文件的示例
const input = await Deno.open("./file.txt.gz");
const output = await Deno.create("./file.txt");
await input.readable
.pipeThrough(new DecompressionStream("gzip"))
.pipeTo(output.writable);
我们目前正与 Web 标准组合作,将 brotli
压缩算法支持添加到 CompressionStream
和 DecompressionStream
API。您可以关注 此问题 以获取更新。
Deno.test
中 ops 和资源清理器报错更优化
自 deno test
的第一个稳定版本以来,它就包含了资源和操作清理器。这些清理器检查测试是否泄漏资源(例如打开的文件句柄)或异步操作(例如计时器)。这样做是因为泄漏资源或异步操作通常会导致难以调试的测试失败或不稳定,并且它们通常指向代码中的逻辑错误。
此版本彻底修改了这些清理器的错误消息,使其更易于使用,并且对于新用户更易于理解。例如,以下是一个泄漏 Deno.FsFile
资源的测试:
Deno.test("leaky", () => {
const file = Deno.openSync("/etc/passwd");
});
它以前会失败并显示此错误
running 1 test from ./test.ts
test leaky ... FAILED (4ms)
failures:
leaky
AssertionError: Test case is leaking resources.
Before: {
"0": "stdin",
"1": "stdout",
"2": "stderr"
}
After: {
"0": "stdin",
"1": "stdout",
"2": "stderr",
"3": "fsFile"
}
Make sure to close all open resource handles returned from Deno APIs before
finishing test case.
at assert (deno:runtime/js/06_util.js:41:13)
at resourceSanitizer (deno:runtime/js/40_testing.js:153:7)
at async Object.exitSanitizer [as fn] (deno:runtime/js/40_testing.js:169:9)
at async runTest (deno:runtime/js/40_testing.js:427:7)
at async Object.runTests (deno:runtime/js/40_testing.js:540:22)
failures:
leaky
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out (11ms)
现在它会失败并显示一个更简洁且有用的消息
running 1 test from ./test.ts
test leaky ... FAILED (4ms)
failures:
leaky
AssertionError: Test case is leaking 1 resource:
- A file (rid 3) was opened during the test, but not closed during the test. Close the file handle by calling `file.close()`.
at assert (deno:runtime/js/06_util.js:46:13)
at resourceSanitizer (deno:runtime/js/40_testing.js:313:7)
at async Object.exitSanitizer [as fn] (deno:runtime/js/40_testing.js:329:9)
at async runTest (deno:runtime/js/40_testing.js:587:7)
at async Object.runTests (deno:runtime/js/40_testing.js:700:22)
failures:
leaky
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out (10ms)
异步操作清理器错误也得到了相同的处理。此测试泄漏了一个睡眠操作
Deno.test("leaky", () => {
setTimeout(() => {}, 1000);
});
之前它会失败并显示此错误
running 1 test from ./test.ts
test leaky ... FAILED (4ms)
failures:
leaky
AssertionError: Test case is leaking async ops.
Before:
- dispatched: 0
- completed: 0
After:
- dispatched: 2
- completed: 1
Ops:
op_sleep:
Before:
- dispatched: 0
- completed: 0
After:
- dispatched: 2
- completed: 1
Make sure to await all promises returned from Deno APIs before
finishing test case.
at assert (deno:runtime/js/06_util.js:41:13)
at asyncOpSanitizer (deno:runtime/js/40_testing.js:121:7)
at async resourceSanitizer (deno:runtime/js/40_testing.js:137:7)
at async Object.exitSanitizer [as fn] (deno:runtime/js/40_testing.js:169:9)
at async runTest (deno:runtime/js/40_testing.js:427:7)
at async Object.runTests (deno:runtime/js/40_testing.js:540:22)
failures:
leaky
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out (9ms)
新错误包含关于导致此问题的有用提示,甚至包括异步操作开始位置的堆栈跟踪,这样您就不必手动尝试使用控制台日志查找它
running 1 test from ./test.ts
test leaky ... FAILED (8ms)
failures:
leaky
Test case is leaking async ops.
- 1 async operation to sleep for a duration was started in this test, but never completed. This is often caused by not cancelling a `setTimeout` or `setInterval` call. The operation was started here:
at Object.opAsync (deno:core/01_core.js:155:42)
at runAfterTimeout (deno:ext/timers/01_timers.js:234:31)
at initializeTimer (deno:ext/timers/01_timers.js:200:5)
at setTimeout (deno:ext/timers/01_timers.js:337:12)
at ./test.ts:2:3
at testStepSanitizer (deno:runtime/js/40_testing.js:432:13)
at asyncOpSanitizer (deno:runtime/js/40_testing.js:145:15)
at resourceSanitizer (deno:runtime/js/40_testing.js:360:13)
at Object.exitSanitizer [as fn] (deno:runtime/js/40_testing.js:415:15)
at runTest (deno:runtime/js/40_testing.js:673:18)
failures:
leaky
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out (17ms)
console.log
现在显示日志对象中的循环引用
以前,对循环对象的检查只会告诉您存在某种循环性,但不会告诉您其中引用了哪些对象。通过此更改,我们现在提供一个计数器和引用指示器,用于显示导致循环引用的对象。
示例
const x = { a: {}, b: {}, foo: { deno: "land", bar: 1 } };
x.a.x = x;
x.b.y = x;
console.log(x);
<ref *1> { a: { x: [Circular *1] }, b: { y: [Circular *1] }, foo: { deno: "land", bar: 1 } }
这不仅适用于对象,也适用于 Error.cause
const x = new Error("foo");
const y = new Error("bar", { cause: x });
x.cause = y;
console.log(y);
<ref *1> Error: bar
at file:///dev/denoland/dotcom/test.ts:2:11
Caused by Error: foo
at file:///dev/denoland/dotcom/test.ts:1:11
Caused by [Circular *1]
Deno.File
已更名为 Deno.FsFile
Deno 对文件系统文件的抽象以前称为 Deno.File
。不幸的是,由于在浏览器和 Deno 中都可用的 File
Web API,此名称给用户带来了很多困惑。
为了避免将来的混淆,我们决定弃用 Deno.File
并将其替换为 Deno.FsFile
,这应该更好地传达它抽象文件系统上的文件的信息。Deno.File
API 将可用直到 Deno 2.0,但我们建议您立即迁移现有代码。
这纯粹是重命名——Deno.File
类上的所有方法都保持不变。只有名称从 Deno.File
更改为 Deno.FsFile
。
deno lint
中添加了一个新的 lint 规则,它将有助于捕获 Deno.File
的使用并建议更改以避免使用已弃用的 API。
deno compile
现在工作更可靠
以前 deno compile
会在编译期间将整个 JS 程序打包成一个 ES 模块。这种打包有时会导致代码行为与打包前不完全相同:堆栈跟踪可能会不准确,import.meta.url
不正确,执行顺序可能与打包前略有不同。
为了解决这个问题,deno compile
现在将您的 ES 模块图“原样”序列化到生成的二进制文件中,而无需打包。这意味着代码的执行顺序保持正确,并且 import.meta.url
等内容保持不变。
为此,我们利用了我们的 eszip 库和文件格式,它允许非常快速地序列化和反序列化 ES 模块图。
感谢 @williamtetlow 贡献此功能。
允许在文件观察器重启时禁用清屏
Deno 附带一个内置的文件观察器,当文件更改时会重新启动正在运行的进程;它可以通过传递 --watch
标志与多个子命令一起使用。Deno 会自动发现哪些文件应该被监视,但您也可以传递您希望被监视的路径(deno run --watch=file1.ts,file2.js script.ts
)。
文件观察器在每次重启时都会自动清空您的终端屏幕,但用户反馈在某些情况下不希望这样。在此版本中,我们添加了 --no-clear-screen
标志,可以与 --watch
标志一起使用,告诉 Deno 不要清空终端屏幕
deno lint --watch --no-clear-screen
感谢 @ah-yu 贡献此功能。
deno coverage
获得 --output
标志
此版本为 deno coverage
子命令添加了 --output
标志,可在生成 lcov
报告时使用。
在此版本之前,报告会打印到标准输出,可以手动管道传输到文件
deno coverage --lcov cov/ > cov.profile
从这个版本开始,您可以直接使用 --output
标志写入文件
deno coverage --lcov --output=cov.profile cov/
感谢 @VishnuJin 贡献此功能。
信号监听器 API 稳定化
我们在此版本中稳定了信号监听器 API。这些 API 可用于使用自定义逻辑拦截和处理信号,例如 SIGINT(您 shell 中的 Ctrl-C
)。您不再需要传递 --unstable
标志即可使用这些 API。
const listener = () => {
console.log("Got SIGTERM!");
};
// Starts listening for SIGTERM
Deno.addSignalListener("SIGTERM", listener);
// Stops listening for SIGTERM
Deno.removeSignalListener("SIGTERM", listener);
注意:虽然此 API 现已稳定,但该 API 尚未在 Windows 上可用。请关注问题 #10236 以获取此更新。此处的稳定化意味着 API 表面在未来的次要版本中不会更改。
通过 Unix 套接字提供 HTTP 服务
Deno 的 HTTP 服务器 API 除了 TCP 之外,现在还支持通过 Unix 套接字建立的连接。
import { serveListener } from "https://deno.land/std/http/server.ts";
const listener = Deno.listen({ transport: "unix", path: "/path/to/socket" });
serveListener(listener, (req) => {
return new Response("Hello World");
});
与一般的 Unix 套接字一样,此 API 仍不稳定。您需要传递 --unstable
标志才能使用它。
感谢 @ylxdzsw 贡献此功能。
Deno.Conn#setNoDelay()
和 Deno.Conn#setKeepAlive()
新的不稳定 API:Deno.Conn
中添加了两个新 API
Deno.Conn.setNoDelay()
Deno.Conn.setKeepAlive()
这些 API 允许配置 TCP 连接的 Nagle 算法 和 keepalive 功能的使用。
示例
const conn = await Deno.connect({ hostname: "127.0.0.1", port: 3500 });
// Enable Nagle's algorithm
conn.setNoDelay(false);
// Disable Nagle's algorithm
conn.setNoDelay(true);
// Enable keep-alive
conn.setKeepAlive(true);
// Disable keep-alive
conn.setKeepAlive(false);
注意:该 API 仍不稳定。您需要传递 --unstable
标志才能使用它。
感谢 @yos1p 贡献此功能。
Deno.getUid
新的不稳定 API:Deno 1.19 添加了一个新的 Deno.getUid
API。调用此 API 后,它会返回 Deno 进程在 Linux 和 macOS 上的用户 ID。您需要传递 --allow-env
权限标志才能使用此 API。
const uid = Deno.getUid();
console.log(uid); // => Prints the user ID
$ deno run --unstable --allow-env getuid.ts
501
注意:该 API 仍不稳定。您需要传递 --unstable
标志才能使用它。
Deno.networkInterfaces
新的不稳定 API:此版本新增了 Deno.networkInterfaces
API。此 API 返回一个对象数组,其中包含有关可用网络接口的信息。您需要传递 --allow-env
权限才能使用此 API。
const interfaces = Deno.networkInterfaces();
console.log(interfaces); // => Prints network interfaces
$ deno run --unstable --allow-env network_ifs.js
[
{
family: "IPv4",
name: "lo0",
address: "127.0.0.1",
netmask: "255.0.0.0",
scopeid: null,
cidr: "127.0.0.1/8",
mac: "00:00:00:00:00:00"
},
...
]
注意:该 API 仍不稳定。您需要传递 --unstable
标志才能使用它。
LSP 的改进
一如既往,此版本带来了 LSP 的又一轮改进
替换重定向指定符的代码操作
当从 https://deno.land/x
等注册表导入模块时,如果不指定版本,注册表会自动将请求重定向到该模块的最新版本。如果发生这种情况,LSP 现在将提供用重定向到的指定符替换原始指定符。结合未版本化导入的警告,这将使用户更容易在其代码库中使用版本化导入。
导入补全现在考虑导入映射
导入补全现在与导入映射良好互动。您的导入映射中指定的指定符将作为补全项提供。
JSDoc 注释中的符号链接现在可在悬浮卡中点击
Deno LSP 提供的悬浮卡现在正确地将 JSDoc 注释中的 @link
标签渲染为可点击的符号链接。这是一个在弃用消息中链接到符号的示例
V8 9.9
此版本的 Deno 将 V8 升级到 9.9,新增了一些 Intl
功能
Intl.Locale
扩展
此版本为 Intl.Locale
对象添加了七个新属性:calendars
、collations
、hourCycles
、numberingSystems
、timeZones
、textInfo
和 weekInfo
。
这些可用于确定给定语言的这些属性的有效值。例如,以下是埃及语支持的所有日历
const arabicEgyptLocale = new Intl.Locale("ar-EG");
arabicEgyptLocale.calendars;
// ['gregory', 'coptic', 'islamic', 'islamic-civil', 'islamic-tbla']
Intl 枚举
此版本还新增了一个 Intl.supportedValuesOf(code)
函数,该函数返回 V8 中 Intl
API 支持的标识符列表。例如,要获取 Intl
API 支持的所有日历列表
Intl.supportedValuesOf("calendar");
// ['buddhist', 'chinese', 'coptic', 'dangi', ...]
或所有受支持的货币
Intl.supportedValuesOf("currency");
// ['ADP', 'AED', 'AFA', 'AFN', 'ALK', 'ALL', 'AMD', ...]