翻译:一川
1写在前面
Node.js的每个版本都带有令人兴奋的新功能,v20也不例外。Node.js v20 于 2023 年 4 月 18 日发布。此版本附带了更新的功能,旨在通过使用稳定的内置测试运行程序减少依赖性,使 Node.js 比以往任何时候都更安全。Node.js v20还提供了创建单个可执行应用程序的功能,这些应用程序可以在Windows,macOS和Linux上执行,而无需在其系统上安装Node.js。
在本教程中,我们将探讨 Node.js v20 中提供的一些功能。您需要在计算机上安装 Node.js v20 或更高版本,并熟悉创建和运行 Node.js 程序才能遵循。
2实验性权限模型
Node.js v20 中引入的主要功能之一是实验性的权限模型,旨在使 Node.js 更安全。长期以来,Node.js没有权限系统。任何应用程序都可以与文件系统交互,甚至可以在用户计算机上生成进程。
这为攻击打开了大门,第三方软件包在未经用户同意的情况下访问了用户的计算机资源。为了降低风险,权限模型限制Node.js应用程序访问文件系统、创建工作线程和生成子进程。
启用权限模型后,用户可以运行应用程序,而不必担心恶意第三方包可以访问机密文件、删除或加密文件,甚至运行有害程序。权限模型还允许用户在运行应用程序或运行时向 Node.js 应用授予特定权限。
实现权限模型
让我们看看如何使用权限模型。使用您选择的名称创建一个目录:
mkdir example_app
创建一个 package.json 文件:
npm init -y
添加到 type:module 支持 ESM 模块:
{
...
"type": "module"
}
然后,创建包含以下内容的: data.txt
Text content that will be read in a Node.js program.
接下来,创建一个 index.js 文件并添加以下代码来读取 data.txt 该文件:
import { readFile } from "fs/promises";
async function readFileContents(filename) {
const content = await readFile(filename, "utf8"); //
在这里,定义一个readFileContents函数,该函数接受 并从 filename 文件系统读取文件。在函数中,调用readFile() fs模块的方法读取data.txt文件内容,然后将它们记录在控制台中。
现在,使用 node 以下命令运行文件:
node index.js
我们将在控制台中看到如下所示的输出:
Text content that will be read in a Node.js program.
若要启用实验性权限模型,请使用以下--experimental-permission标志运行文件:
node --experimental-permission index.js
这次我们收到如下所示的错误:
// output
node:internal/modules/cjs/loader:179
const result = internalModuleStat(filename);
^
Error: Access to this API has been restricted
at stat (node:internal/modules/cjs/loader:179:18)
at Module._findPath (node:internal/modules/cjs/loader:651:16)
at resolveMainPath (node:internal/modules/run_main:15:25)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:76:24)
at node:internal/main/run_main_module:23:47 {
code: 'ERR_ACCESS_DENIED',
permission: 'FileSystemRead',
resource: '/home//example_app/index.js'
}
错误消息让我们知道我们没有读取文件的权限。权限模型限制了文件系统访问。如果尝试生成工作线程和子进程,也会收到一条错误消息。
要授予对文件或目录的读/写访问权限,可以使用该 --allow-fs-read 标志。以下是可以使用的一些选项:
- --allow-fs-read=* :通配符 * 提供对文件系统上所有目录/文件的读取访问权限
- --allow-fs-read=/home/
/ :指定 /home/ 目录应具有读取访问权限 - --allow-fs-read=/tmp/filename.txt :这只允许对给定文件名的读取访问,即 filename.txt
还可以使用标志 --allow-fs-write 授予写入访问权限。它还接受通配符、目录路径或文件名,如上所述。如前所述,权限模型还阻止 Node.js 程序创建子进程。要授予权限,我们需要传递: --allow-child-process
node --experimental-permission --allow-child-process index.js
为了允许创建工作线程来并行执行任务,可以改用该 --allow-worker 标志:
node --experimental-permission --allow-worker index.js
因此,回到前面的示例,授予Node.js读取data.txt.执行此操作的更灵活的方法是提供 驻data.txt留的完整目录路径,如下所示:
node --experimental-permission --allow-fs-read=/home//example_app index.js
现在,程序可以毫无问题地读取文件,尽管它提供了警告:
(node:8506) ExperimentalWarning: Permission is an experimental feature
(Use `node --trace-warnings ...` to show where the warning was created)
Text content that will be read in a Node.js program.
启用权限模型后,可能不知道应用程序是否具有写入或读取文件系统的权限。为了防止运行时 ERR_ACCESS_DENIED 错误,权限模型允许在运行时检查权限。
要检查是否具有读取权限,可以执行以下操作:
if(process.permission.has('fs.read')) {
// proceed to read the file
}
或者,可以检查目录的权限。在下面的代码中,检查是否对给定目录具有写入权限:
if (process.permission.has('fs.write', '/home/username/') ) {
//do your thing
}
有了这个,我们现在就可以创建安全的应用程序并保护我们机器的资源在未经我们同意的情况下不被访问。若要探索权限模型中的更多功能,请访问文档。
3稳定的测试运行器
在Node.js v18发布之前,Node.js 中的所有测试运行器都是第三方软件包,例如 Jest 和 Mocha。虽然它们为 Node.js 社区提供了良好的服务,但第三方库可能是不可预测的。
首先,Jest有一个错误,它会破坏 instanceof 控制器,产生误报。解决方案是安装另一个第三方软件包。内置工具倾向于按预期工作并且更加标准化,如 Python 或 Go,它们都附带内置测试运行器。
甚至像Deno和 Bun 这样的更新的 JavaScript 运行时也带有测试运行器。Node.js 一直被抛在后面,直到Node.js v18发布,它附带了一个实验性测试运行器。现在,随着 Node.js v20 版本的发布,测试运行程序是稳定的,可以在生产中使用。以下是测试运行程序中提供的一些功能:
- mocking
- skipping tests 跳过测试
- filtering tests 过滤测试
- test coverage collection 测试覆盖率收集
- 监视模式(实验性),在检测到更改时自动运行测试
使用测试运行程序
让我们详细探讨测试运行程序。首先, calculator.js 使用以下代码创建一个:
// calculator.js
export function add(x, y) {
return x + y;
}
export function divide(x, y) {
return x / y;
}
之后,创建一个 test 目录中的文件目录 calculator_test.js 。在文件中,添加以下代码以使用内置测试运行程序测试函数:
// calculator_test.js
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { add, divide } from "../calculator.js";
describe("Calculator", () => {
it("can add two numbers", () => {
const result = add(2, 5);
assert.strictEqual(result, 7);
});
it("can divide two numbers", () => {
const result = divide(15, 5);
assert.strictEqual(result, 3);
});
});
在上面的代码中,我们从 导入 describe node:test / it 关键字,如果您使用过 Jest,应该很熟悉。我们也 assert 从 . node:assert/strict 然后,我们测试 divide() 和 add() 函数是否按预期工作。按如下方式运行测试:
node --test
运行测试将生成与以下内容匹配的输出:
▶ Calculator
✔ can add two numbers (0.984478ms)
✔ can divide two numbers (0.291951ms)
▶ Calculator (5.135785ms)
ℹ tests 2
ℹ suites 1
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 158.853226
当我们运行测试时,内置运行器会搜索所有后缀为 .js 、 .cjs 的JavaScript测试文件,前提是 .mjs :
- 它们驻留在名为 test
- 文件名以 test-
- 文件名以 、 -test 或_test结尾 _test
我们还可以在运行时 node --test 提供包含测试的目录。如果我们想跳过一些测试,我们需要提供该 skip: true 选项作为 it 块的第二个参数:
...
describe("Calculator", () => {
it("can add two numbers", () => {
const result = add(2, 5);
assert.strictEqual(result, 7);
});
// skip test
it("can divide two numbers", { skip: true }, () => {
const result = divide(15, 5);
assert.strictEqual(result, 3);
});
}
)
当重新运行测试时,将看到只有一个测试运行:
▶ Calculator
✔ can add two numbers (0.954955ms)
﹣ can divide two numbers (0.214886ms) # SKIP
▶ Calculator (5.111238ms)
...
Node.js v20 还附带了一个实验性监视模式,一旦检测到测试文件中的更改,它就可以自动运行测试。我们需要传递 --watch 标志并提供目录以在监视模式下运行测试:
node --test --watch test/*.js
如果更改文件,Node.js 将自动选取更改并重新运行测试。
只是触及了测试运行程序可以做什么的表面。查看文档以继续探索它。
4V8 JavaScript 引擎更新到 v11.3
Node.js建立在高性能的V8 JavaScript引擎之上,该引擎也为Google Chrome提供支持。它实现了更新的 ECMAScript 特性。当新版本的 Node.js 发布时,它附带了最新版本的 V8 JavaScript 引擎。最新版本是V8 v11.3,它具有一些显着的功能,包括:
- 可 ArrayBuffer 调整大小:根据给定的大小(以字节为单位)调整大小 ArrayBuffer
- 可 SharedArrayBuffer 增长: ShareArrayBuffer 根据给定的大小(以字节为单位)增长
- String.prototype.isWellFormed() :如果字符串格式正确且不包含单独的代理项,则返回 true
- String.prototype.toWellFormed() :修复并返回没有单独代理项问题的字符串
- 正则表达式 -v 标志:改进了不区分大小写的匹配
让我们探索用于调整 ArrayBuffer、SharedArrayBuffer 最令人兴奋的新功能之一是调整 ArrayBuffer 。在 Node.js v20 之前,在创建缓冲区后调整缓冲区大小以容纳更多数据是不可能的。使用 Node.js 20,我们可以使用该resize()方法调整它的大小,如以下示例所示:
//resize_buffer.js
const buffer = new ArrayBuffer(4, { maxByteLength: 10 });
if (buffer.resizable) {
console.log("The Buffer can be resized!");
buffer.resize(8); // resize the buffer
}
console.log(`New Buffer Size: ${buffer.byteLength}`);
首先,创建一个大小为字节的缓冲区,其缓冲区限制为 10 使用该 maxByteLength 属性指定的 4 字节数。有了缓冲区限制,如果我们要将其大小调整为超过 10 字节,它将失败。如果需要更多字节,可以将 修改 maxByteLength 为所需的值。
接下来,我们检查缓冲区是否可调整大小,然后调用该方法 resize() 将缓冲区的大小从字节调整 4 为 8 字节。最后,我们记录缓冲区大小。像这样运行文件:
node resize_buffer.js
下面是输出:
The Buffer can be resized!
New Buffer Size: 8
缓冲区的大小已成功从字节调整 4 为 8 字节。也有 SharedArrayBuffer 相同的限制 ArrayBuffers ,但现在我们可以使用以下 grow() 方法将其增长到我们选择的大小:
// grow_buffer.js
const buffer = new SharedArrayBuffer(4, { maxByteLength: 10 });
if (buffer.growable) {
console.log("The SharedArrayBuffer can grow!");
buffer.grow(8);
}
console.log(`New Buffer Size: ${buffer.byteLength}`);
输出如下所示:
The SharedArrayBuffer can grow!
New Buffer Size: 8
在示例中,我们检查 是否 buffer 可增长。如果它的计算结果为 true,我们调用该方法 grow() 以增加 SharedArrayBuffer 8 字节数。与 ArrayBuffer类似,我们不应该将其增长到 maxByteLength。
5递归读取目录
目录通常被认为是树结构,因为它们包含子目录,子目录也包含子目录。从历史上看, fs 该模块readdir()的方法仅限于列出给定目录的文件内容,并且不会递归地遍历子目录并列出其内容。因此,开发人员转向第三方库,如readdirp,recursive-readdir,klaw和fs-readdir-recursive。
Node.js v20 为 和 方法添加了一个recursive选项,允许 readdirSync 方法递归读取给定目录和 readdir 子目录。
假设有一个类似于以下内容的目录结构:
├── dir1
│ ├── dir2
│ │ └── file4.txt
│ └── file3.txt
├── file1.txt
└── file2.txt
可以添加 recursive: true 列出所有文件的选项,包括子目录中的文件,如下所示:
// list_directories.js
import { readdir } from "node:fs/promises";
async function readFiles(dirname) {
const entries = await readdir(dirname, { recursive: true });
console.log(entries);
}
readFiles("data"); // <- "data" is the root directory name
运行文件后,输出将匹配以下内容:
[
'dir1',
'file1.txt',
'file2.txt',
'dir1/dir2',
'dir1/file3.txt',
'dir1/dir2/file4.txt'
]
如果不将选项传递给 readdir() 该方法,输出将 recursive 如下所示:
[ 'dir1', 'file1.txt', 'file2.txt' ]
虽然这是一个次要的补充,但它可以帮助我们减少项目中的依赖关系。
6单个可执行实验性应用程序
我们将在本文中探讨的最后一个功能是Node.js v20中引入的实验性单可执行应用程序 (SEA)。它允许我们将应用程序捆绑到Windows上的单个可执行文件.exe或可以在macOS / Linux上运行的二进制文件中,而无需用户在其系统上安装Node.js。在撰写本文时,它仅支持使用commons模块系统的脚本。
让我们创建一个二进制文件。本节中的说明仅适用于 Linux。在macOS和Windows上,某些步骤会有所不同,因此最好查阅文档。首先,创建一个不同的目录来包含代码并移动到其中:
mkdir sea_demo && cd sea_demo
在此之后,创建一个 list_items.js 包含以下内容的文件:
const items = ["cameras", "chargers", "phones"];
console.log("The following are the items:");
for (const item of items) {
console.log(item);
}
接下来,创建一个配置文件 sea-config.json ,用于创建可注入可执行文件的 Blob:
{ "main": "list_items.js", "output": "sea-prep.blob" }
生成 Blob,如下所示:
node --experimental-sea-config sea-config.json
// Output:
Wrote single executable preparation blob to sea-prep.blob
复制可执行文件并为其指定一个适合您的名称:
cp $(command -v node) list_items
将 Blob 注入二进制文件:
npx postject list_items NODE_SEA_BLOB sea-prep.blob \
--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2
像这样在系统上运行二进制文件:
The following are the items:
cameras
chargers
phones
(node:41515) ExperimentalWarning: Single executable application is an experimental feature and might change at any time
(Use `list_items --trace-warnings ...` to show where the warning was created)
这负责创建 SEA。如果您想了解如何为 macOS 或 Windows 创建二进制文件,请查看文档[https://nodejs.org/api/single-executable-applications.html]。
7写在最后
在这篇文章中,我们探讨了 Node.js v20 中引入的一些功能。首先,我们研究了如何使用实验性权限模型。然后,我们了解了如何使用现在稳定的内置测试运行程序。从那里,我们了解了 V8 JavaScript 引擎中可用的新功能。
之后,我们探索了如何递归读取目录,最后,我们使用实验性的单一可执行应用程序(SEA)功能创建了一个二进制文件,该功能允许用户在不安装Node.js的情况下运行Node.js程序。