原文链接:10 modern Node.js runtime features to start using in 2024, 2024.5.29, by Liran Tal。翻译时有删改。
服务器端 JavaScript 运行时进来充满了创新,例如 Bun 凭借兼容的 Node.js API 取得了长足进步,而 Node.js 运行时则进一步提供了丰富的标准库和运行时功能。
时间进入 2024 年,是时候了解 Node.js 运行时所提供的最新特性和功能了。这样做不仅是为了“与时俱进”,更是为了利用现代 API 的力量来编写更高效、性能更安全的代码。
下面我们就将详细探讨每个开发人员在 2024 年都应该开始使用的 10 项现代 Node.js 运行时功能。
先决条件:Node.js LTS 版本
在开始探索这些现代功能之前,请确保您使用的是 Node.js LTS(长期支持)版本。在撰写本文时,最新的 Node.js LTS 版本是 v20.14.0。
使用以下指令,检查 Node.js 版本:
$ node --version v20.14.0
如果您当前没有使用 LTS 版本,可以考虑使用 fnm 或 nvm 等版本管理器在不同 Node.js 版本之间轻松切换。
Node.js 20 有哪些新功能?
以下我们将介绍 Node.js 最新版本中引入的一些新功能。有些是稳定的,有些仍处于实验阶段,还有一些在之前的版本中就已经得到了支持,只不过你可能还还没有听说过。
讨论的主题包括:
Node.js 测试运行器(test runner)
Node.js 原生 mocking
Node.js 原生测试覆盖率
Node.js 监听模式(watch mode)
Node.js corepack
Node.js import.meta.file:访问
__dirname
和__file
Node.js 原生计时器 Promise
Node.js 权限模块(permissions module)
Node.js 策略模块(policy module)
Node.js 测试运行器
在 Node.js 引入原生测试运行支持之前,你会用什么工具进行测试呢?当然,这里有一些流行的选项:vitest、jest、mocha 或者 node-tap。
下面,我们将了解如何在开发工作流程中使用 Node.js 原生测试运行器进行测试。
首先,你需要将 Node.js 中的 test
模块导入到测试文件中,如下所示:
import { test } from 'node:test';
使用 node:test 运行单个测试
要创建单个测试,你可以使用 test 函数,传入测试的名称和回调函数。回调函数是你定义测试逻辑的地方。
import { test } from "node:test"; import assert from "node:assert"; import { add } from "../src/math.js"; test("should add two numbers", () => { const result = add(1, 2); assert.strictEqual(result, 3); }); test("should fail to add strings", () => { assert.throws(() => { add("1", "2"); }); });
要运行此测试,请使用 node --test [测试文件]
命令:
node --test tests/math.test.js
Node.js 测试运行程序可以自动检测并运行项目中的测试文件。按照约定,这些文件应以 .test.js
结尾,但这个约定并不需要严格遵守。
如果省略测试文件位置参数(positional argument),那么 Node.js 测试运行程序将应用一些启发式和 glob 模式匹配来查找测试文件——例如 test/
/tests/
中的所有文件,或是带有 test-
前缀或 .test
后缀的文件夹或文件。
你还可以通过 glob 语法匹配测试文件:
node --test '**/*.test.js'
通过 node:assert 使用断言
Node.js 测试运行程序通过内置的 assert 模块支持断言。你可以使用 assert.strictEqual 等不同方法来验证测试结果。
import assert from 'node:assert'; test('Test 1', () => { assert.strictEqual(1 + 1, 2); });
运行测试套件 & 使用测试 hooks 函数
describe 函数用于将相关测试分组到测试套件中。这使您的测试更有组织性并且更易于管理。
import { test, describe } from "node:test"; describe('My Test Suite', () => { test('Test 1', () => { // Test 1 logic }); test('Test 2', () => { // Test 2 logic }); });
测试 hooks 函数是在测试之前或之后运行的特殊函数,它们对于设置或清理测试环境很有用。
test.beforeEach(() => { // Runs before each test }); test.afterEach(() => { // Runs after each test });
你还可以选择使用 test.skip 函数跳过测试。这在某些你想暂时忽略特定测试时很有帮助。
test.skip('My skipped test', () => { // Test logic });
此外,Node.js 测试运行器提供了不同的报告器(reporter),以各种方式格式化和显示测试结果,使用 --reporter 选项指定。
node --test --test-reporter=tap
Jest 的缺陷
虽然 Jest 是 Node.js 社区中流行的测试框架,但它具有某些缺点,让原生 Node.js 测试运行器成为更具吸引力的选择。
Jest 即使作为一个开发依赖项安装后,你将拥有一个包含不同许可证的 277 个其他依赖项,这些许可证包括 MIT、Apache-2.0、CC-BY-4.0 和 1 个未知许可证。你知道吗?
Jest 会修改全局变量,这可能会导致测试中出现意外行为。
instanceof 运算符在 Jest 中并不总是按预期工作
Jest 为你的项目引入了大量的依赖项,你需要关心使用过程中的安全问题和开发时依赖项可能会引起的其他问题
Jest 可能比原生 Node.js 测试运行器要慢
原生 Node.js 测试运行器的其他强大功能包括运行子测试和并发测试。
子测试允许每个 test() 回调接收一个 context 参数,这个参数允许你使用 context.test 方式创建嵌套测试。
如果你知道如何很好地使用并发测试并能避免竞争条件(racing condition),那么并发测试是一个很棒的功能。只需将 concurrency: true 作为第二个参数传递给 describe() 测试套件即可。
什么是测试运行器?
测试运行器是一种软件工具,允许开发人员对其代码进行管理并执行自动化测试。 Node.js 测试运行程序帮助你与 Node.js 可以无缝协作,为在 Node.js 应用程序上编写和运行测试提供了一条龙服务。
Node.js 原生 mocking
Mocking(模拟)是开发人员用来隔离代码进行测试的一种策略,Node.js 运行时引入了原生模拟功能,这对开发人员更加有效地理解和操作代码帮助很多。
在此之前,你可能使用过其他测试框架的模拟功能,例如 Jest 的 jest.spyOn
或 mockResolvedValueOncel
。当你想要避免在测试中运行实际代码(例如 HTTP 请求或文件系统 API)并期望稍后可以调用过程时,模拟就非常有用了。
与其他 Node.js 运行时功能(例如监听和测试覆盖率功能)不同,模拟并未声明为实验性的。但是,它后续可能会迎来更多更改,因为它实在 Node.js 18 中才引入的新功能。
使用 node:test 中的 mock 进行原生模拟测试
让我们看看如何在实际示例中使用 Node.js 原生模拟功能。测试运行器和(模块)模拟功能现已作为稳定功能在 Node.js 20 LTS 中提供了。
我们会用到一个工具模块 dotenv.js
,它的作用是从 .env
文件加载环境变量。下面,我们将使用一个测试文件 dotenv.test.js
来测试 dotenv.js
模块。
dotenv.js 内容如下:
import fs from "node:fs/promises"; export async function loadEnv(path = ".env") { const rawDataEnv = await fs.readFile(path, "utf8"); const env = {}; rawDataEnv.split("\n").forEach((line) => { const [key, value] = line.split("="); env[key] = value; }); return env; }
在 dotenv.js 文件中,我们有一个异步函数 loadEnv ,它使用 fs.readFile 方法读取文件并将文件内容拆分为键值对保存在 env 对象中并返回。
现在,让我们看看如何使用 Node.js 中的原生模拟功能来测试这个函数。
// dotenv.test.js import { describe, test, mock } from "node:test"; import assert from "node:assert"; import fs from "node:fs/promises"; import { loadEnv } from "../src/dotenv.js"; describe("dotenv test suite", () => { test("should load env file", async () => { const mockImplementation = async (path) => { return "PORT=3000\n"; }; const mockedReadFile = mock.method(fs, "readFile", mockImplementation); const env = await loadEnv(".env"); assert.strictEqual(env.PORT, "3000"); assert.strictEqual(mockedReadFile.mock.calls.length, 1); }); });
在测试文件中,我们从 node:test 导入 mock 方法,用它来创建 fs.readFile 的模拟实现。在模拟实现中,无论传递的文件路径如何,我们都会返回一个字符串 "PORT=3000\n"。
然后我们调用 loadEnv 函数,并使用 assert 模块,我们检查 2 个地方:
返回的对象有一个值为 "3000" 的 PORT 属性
fs.readFile 方法只被调用了一次
通过使用 Node.js 中的原生模拟功能,我们能够有效地将 loadEnv 函数与文件系统隔离并单独测试。Node.js 20 的模拟功能还支持模拟计时器。
什么是 mocking?
在软件测试中,模拟是用自定义实现替换特定模块的实际功能的过程。主要目标是将正在测试的代码与外部依赖项隔离,确保测试仅验证单元的功能而不验证依赖项。模拟还能帮助你你模拟不同的测试场景,例如来自依赖项的错误,这可能很难在真实环境验证。
Node.js 原生测试覆盖率
什么是测试覆盖率?
测试覆盖率是软件测试中使用的一个指标。它可以帮助开发人员了解应用程序源代码的测试程度。这很重要,因为它展示了代码库中未经测试的区域,使开发人员能够识别其软件中的潜在弱点。
为什么测试覆盖率很重要?因为它是通过减少 BUG 数量和避免回归来确保软件的质量。此外,它还可以深入了解测试的有效性,并帮助指导我们构建更强大、可靠和安全的应用程序。
使用原生 Node.js 测试覆盖率
从 v20 开始,Node.js 运行时包含测试覆盖率的功能。不过,原生 Node.js 测试覆盖率目前被标记为实验性功能,表示虽然现在可用,但在未来版本中可能会发生一些变化。
要使用原生 Node.js 测试覆盖率,你需要使用 --experimental-coverage 命令行标志。以下示例说明了如何在运行项目测试的 package.json 脚本字段中添加 test:coverage 脚本:
{ "scripts": { "test": "node --test ./tests", "test:coverage": "node --experimental-coverage --test ./tests" } }
test:coverage 脚本中通过添加 --experimental-coverage 标志就能在执行测试期间生成覆盖率数据了。
运行 npm run test:coverage 后,你会看到类似于以下内容的输出:
ℹ tests 7 ℹ suites 4 ℹ pass 5 ℹ fail 0 ℹ cancelled 0 ℹ skipped 1 ℹ todo 1 ℹ duration_ms 84.018917 ℹ start of coverage report ℹ --------------------------------------------------------------------- ℹ file | line % | branch % | funcs % | uncovered lines ℹ --------------------------------------------------------------------- ℹ src/dotenv.js | 100.00 | 100.00 | 100.00 | ℹ src/math.js | 100.00 | 100.00 | 100.00 | ℹ tests/dotenv.test.js | 100.00 | 100.00 | 100.00 | ℹ tests/math.test.js | 94.64 | 100.00 | 91.67 | 24-26 ℹ --------------------------------------------------------------------- ℹ all files | 96.74 | 100.00 | 94.44 | ℹ --------------------------------------------------------------------- ℹ end of coverage report
这个报告展示了目前的测试所覆盖的语句、分支、函数和行的百分占比。
Node.js 原生测试覆盖率是一个强大的工具,可以帮助你提高 Node.js 应用程序的质量。尽管它目前被标记为实验性功能,但它可以为你的测试覆盖范围提供有价值的见解并指导你的测试工作。通过了解和利用这个功能,可以确保你的代码健壮、可靠且安全。
Node.js 监听模式
Node.js 监听模式是一项强大的开发人员功能,允许实时跟踪 Node.js 文件的更改并自动重新执行脚本。
在深入了解 Node.js 的原生监听功能之前,有必要了解一下 nodemon,它是一个流行的工具程序,有助于满足 Node.js 早期版本中的这一需求。Nodemon 是一个命令行界面 (CLI) 工具程序,用于在文件目录中检测到任何更改时重新启动 Node.js 应用程序。
npm install -g nodemon nodemon
这个功能在开发过程中特别有用,避免每次修改文件时手动重新启动,可以节省时间并提高工作效率。
随着 Node.js 本身的进步,平台本身提供了内置功能来实现相同的结果,也不需要在项目中安装额外的第三方依赖项,例如 nodemon。
值得注意的是,Node.js 中的原生监听模式功能仍处于实验阶段,未来可能会发生变化。请始终确保你使用的 Node.js 版本支持这个功能。
使用 Node.js 20 原生监听功能
Node.js 20 使用 --watch 命令行标志引入了原生文件监听功能。这个功能使用起来很简单,甚至支持匹配模式来满足更复杂的文件监听需要。
node --watch app.js
同事,支持使用 glob 语法做匹配,当你想要监听一组与特定模式匹配的文件时,这特别有用:
node --watch 'lib/**/*.js' app.js
--watch 标志还可以与 --test 结合使用,这样修改测试文件时也能重新运行测试:
node --watch --test '**/*.test.js'
需要注意的是,从 Node.js v20 开始,监听模式功能仍被标记为实验性的,表示现在功能功能虽然齐全,但后续可能会有修改,不像其他非实验功能那么稳定或经过优化。在实际s使用时,可能会遇到一些 quirks 或 BUG。
Node.js Corepack
Node.js Corepack 是一个值得探索的有趣功能,它是在 Node.js 16 中引入的,至今仍被标记为实验性的。
什么是 Corepack?
Corepack 是一个零运行时依赖项目,充当 Node.js 项目和所要使用的包管理器之间的桥梁。安装后,它提供了一个名为 corepack
的程序,开发人员可以在他们的项目中使用它,确保使用了正确的包管理器,而不必担心其全局安装。
为什么要使用 Corepack?
作为 JavaScript 开发人员,我们经常处理多个项目,每个项目可能都有自己首选的包管理器。比如,一个项目使用 pnpm 管理其依赖项,而另一个项目使用 yarn 管理其依赖项,导致你需要在不同的包管理器之间切换。
这个可能就会导致冲突和不一致。Corepack 允许每个项目以无缝的方式指定和使用其首选的包管理器,从而解决了这个问题。
此外,Corepack 在你的项目和全局系统之间提供一层隔离,确保即使全局包升级或删除,你的项目也能正常运行,这提高了项目的一致性和可靠性。
安装和使用 Corepack
安装 Corepack 非常简单。由于它从版本 16 开始与 Node.js 捆绑在一起,因此你只需安装或升级 Node.js 到这个或更高版本即可。
安装后,你可以在 package.json 文件中为项目指定包管理器,如下所示:
{ "packageManager": "yarn@2.4.1" }
然后,你可以在项目中启用 Corepack,如下所示:
corepack enable
如果你在项目目录下的终端中输入 yarn
,即便当前没有安装 Yarn,Corepack 会自动检测并安装正确版本的。
这种做法会确保始终使用 Yarn v2.4.1 版本来安装项目的依赖项,无论系统上安装的全局 Yarn 版本如何。
如果你想全局安装 Yarn 或使用特定版本,您可以运行:
corepack install --global yarn@stable
Corepack 仍然是一个实验性功能
尽管在 Node.js 16 中引入了 Corepack,但它仍然被标记为实验性的。这表示虽然它现在设计并运行的良好,但仍在积极开发中,并且其行为的某些方面将来可能会发生变化。
总之,Corepack 易于安装、使用简单,能为你的项目提供额外的可靠性,这绝对是一个值得探索并融入到你的开发工作流程中的功能。
Node.js .env
加载器
应用程序的配置至关重要,作为 Node.js 开发人员,通常会面临管理 API 凭据、服务器端口号或数据库等配置的需求。
而我们需要一种方法,能在不改变源代码的情况下,为不同的环境提供不同的设置。在 Node.js 应用程序中实现这个目的的一种流行方法是使用存储在 .env
文件中的环境变量。
npm 包 dotenv
在 Node.js 引入对加载 .env 文件的原生支持之前,开发人员主要使用 dotenv npm 包。dotenv 包将环境变量从 .env 文件加载到 process.env
中,然后在整个应用程序中都可访问了。
以下是 dotenv 包的典型用法:
require('dotenv').config(); console.log(process.env.MY_VARIABLE);
很有用,但需要向你的项目添加额外的依赖项。而在通过引入原生 .env 加载器后,你现在可以直接加载环境变量,而无需任何外部包。
使用原生 .env
加载器
从 Node.js 20 开始,内置了从 .env 文件加载环境变量的功能。这个功能正在积极开发中,但对开发人员来说已经游戏改变者了。
要加载 .env 文件,我们可以在启动 Node.js 应用程序时使用 --env-file
CLI 标志。该标志指定要加载的 .env 文件的路径。
node --env-file=./.env index.js
这会将环境变量从指定的 .env 文件加载到 process.env 中。然后,这些变量就可以像以前一样在你的应用程序中那样使用了。
加载多个 .env
文件
Node.js .env 加载器还支持加载多个 .env 文件。当你针对不同环境(例如开发、测试、生产)有不同的环境变量集时,这非常有用。
你可以指定多个 --env-file 标志来加载多个文件。文件按照指定的顺序加载,后面文件中的变量会覆盖前面文件中的变量。
node --env-file=./.env.default --env-file=./.env.development index.js
在此示例中, ./.env.default 包含默认变量, ./.env.development 包含特定于开发环境的变量。 ./.env.development、./.env.default 中同时存在的变量,前者都将覆盖后者中的变量。
Node.js 中对加载 .env 文件的原生支持对于 Node.js 开发人员来说是一项重大改进,它简化了配置管理并消除了对额外软件包的需要。
开始在 Node.js 应用程序中使用 --env-file
CLI 标志并亲身体验便利吧!
Node.js import.meta
支持 __dirname
和 __file
__dirname
和 __file
其实是 CommonJS 模块中提供的变量,通常用来作为获取当前文件的目录名称和文件路径的方式。然而,直到最近,这些在 ESM 上还不容易获得,N你必须使用以下代码来提取 __dirname
:
import url from 'url' import path from 'path' const dirname = path.dirname(url.fileURLToPath(import.meta.url))
或者,如果你是 Matteo Collina 的粉丝,您可能已经选择使用 Matteo 的 desm npm 包。
Node.js 不断发展,为开发人员提供更有效的方法来处理文件和路径操作。Node.js v20.11.0 和 Node.js v21.2.0 中引入了一项对 Node.js 开发人员的重大更改,即内置了对 import.meta.dirname 和 import.meta.filename 的支持。
使用 Node.js import.meta.filename
和 import.meta.dirname
值得庆幸的是,随着 import.meta.filename 和 import.meta.dirname 的引入,这个过程变得更加容易,让我们看一下使用新功能加载配置文件的示例。
假设你需要加载的 JavaScript 文件所在目录中有一个 YAML 配置文件,你可以这样做:
import fs from 'fs'; const { dirname: __dirname, filename: __filename } = import.meta; const projectSetup = fs.readFileSync(`${__dirname}/setup.yml`, "utf8"); console.log(projectSetup);
在这个例子中,我们使用 import.meta.dirname 来获取当前文件的目录名,并将其分配给 __dirname
变量,以便符合 CommonJS 的命名约定。
Node.js 原生计时器 Promise
我们都知道,Node.js 是一种基于 Chrome V8 JavaScript 引擎构建的流行 JavaScript 运行时,始终致力于通过不断的更新和新功能让开发人员的生活变得更加轻松。
尽管 Node.js 在 Node.js v15 中就引入了原生计时器 Promise 的支持,但我承认我并没有经常使用它们。
JavaScript 计时器的简要回顾
在深入探讨本机计时器 Promise 之前,我们来简要回顾一下 JavaScript setTimeout() 和 setInterval() 计时器。
setTimeout() API 是一个 JavaScript 函数,一旦计时器到期,它就会执行函数或指定的代码段。
setTimeout(function(){ console.log("Hello World!"); }, 3000);
在上面的代码中,“Hello World!”将在 3 秒(3000 毫秒)后打印到控制台。
另一方面,setInterval() 则用于重复执行指定的函数,每次调用之间间隔指定延迟。
setInterval(function(){ console.log("Hello again!"); }, 2000);
在上面的代码中,“Hello again!”每 2 秒(2000 毫秒)就会打印到控制台。
旧方法:用 Promise 包装 setTimeout()
在过去,开发人员通常必须人为地包装 setTimeout() 函数,并通过 Promise 使用它,这样做是为了将 setTimeout() 和 async/await 一起使用。
下面是一个封装的示例:
function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function demo() { console.log('Taking a break...'); await sleep(2000); console.log('Two seconds later...'); } demo();
这将打印"Taking a break...",等待两秒钟,然后打印 "Two seconds later..."。
这么做没问题,却给代码增加了不必要的复杂性。
Node.js Native 计时器 Promise
使用 Node.js Native 计时器 Promise,我们不再需要将 setTimeout() 包装在 Promise 中。相反,我们可以直接将 setTimeout() 与 async/await 一起使用。
以下是如何使用 Node.js 原生计时器 Promise 的示例:
const { setTimeout, } = require('node:timers/promises'); setTimeout(2000, 'Two seconds later...').then((res) => { console.log(res); }); console.log('Taking a break...');
在上面的代码中,setTimeout() 是从 node:timers/promises 导入的。然后我们直接将其与 async/await 一起使用。它将打印"Taking a break...",等待两秒钟,然后打印 "Two seconds later..."。
这极大地简化了异步编程,并使代码更易于阅读、编写和维护。
Node.js 权限模型
目前加入 Node.js TSC 的 Rafael Gonzaga 恢复了 Node.js 权限模块的开发工作,这个模块与 Deno 类似,提供了一组进程级的可配置资源约束。
出于安全和合规性原因,管理和控制 Node.js 应用程序可以访问的资源变得越来越重要。因为有时候你也不知道项目使用的 npm 包突然被黑了,或是误下载了一个恶意 npm 包等情况的发生。
在这方面,Node.js 引入了一项称为权限模块的实验性功能,用于管理 Node.js 应用程序中的资源权限。使用 --experimental-permission 命令行标志可以启用这个功能。
Node.js 资源权限模型
Node.js 中的权限模型提供了一套抽象来管理对各种资源(如文件系统、网络、环境变量和工作线程等)的访问。当你想要限制应用程序的某个部分可以访问的资源时,这个功能就很有用。
你可以使用权限模型的常见资源约束,包括:
使用 --allow-fs-read=* 和 --allow-fs-write=* 进行文件系统读写,可以指定目录和特定文件路径,还可以通过重复标志来限定多种资源
使用 --allow-child-process 进行子进程调用
使用 --allow-worker 进行工作线程调用
Node.js 权限模型还通过 process.permission.has(resource, value) 提供运行时 API,以允许查询特定访问权限。
如果你尝试访问不能允许的资源,例如读取 .env 文件,就会看到 ERR_ACCESS_DENIED 错误:
> start:protected > node --env-file=.env --experimental-permission server.js node:internal/modules/cjs/loader:197 const result = internalModuleStat(filename); ^ Error: Access to this API has been restricted at stat (node:internal/modules/cjs/loader:197:18) at Module._findPath (node:internal/modules/cjs/loader:682:16) at resolveMainPath (node:internal/modules/run_main:28:23) at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:135:24) at node:internal/main/run_main_module:28:49 { code: 'ERR_ACCESS_DENIED', permission: 'FileSystemRead', resource: '/Users/lirantal/repos/modern-nodejs-runtime-features-2024/server.js' } Node.js v20.14.0
Node.js 权限模型示例
考虑这样一个场,有一个处理文件上传的 Node.js 应用程序。你希望限制应用程序的这一部分,以便它只能访问存储上传文件的特定目录。
使用 --experimental-permission 标志启动 Node.js 应用程序时启用实验性权限功能。
node --experimental-permission ./app.js
我们还想专门允许应用程序读取 2 个受信任的文件 .env 和 setup.yml ,因此我们需要将上面的内容更新为:
node --experimental-permission --allow-fs-write=/tmp/uploads --allow-fs-read=.env --allow-fs-read=setup.yml ./app.js
这样一来,如果应用程序对提供的上传路径之外的文件进行写入,就会因报错而终止。
请参阅以下代码示例,了解如何通过 try/catch 包装资源访问的地方,或通过权限检 API 来进行防御性编程,确保不会因为权限模型的引入导致出现的异常意外终止程序。
const { dirname: __dirname, filename: __filename } = import.meta; // @TODO to avoid the Node.js resource permission issue you should update // the path to be `setup.yml` in the current directory and not `../setup.yml`. // the outside path for setup.yml was only changed in the source code to // show you how Node.js resource permission module will halt if trying to access // something outside the current directory. const filePath = `${__dirname}/../setup.yml`; try { const projectSetup = fs.readFileSync(filePath, "utf8"); // @TODO do something with projectSetup if you want to } catch (error) { console.error(error.code); } // @TODO or consider using the permissions runtime API check: if (!process.permission.has("read", filePath)) { console.error("no permissions to read file at", filePath); }
值得注意的是,Node.js 中的权限功能仍处于实验阶段,未来可能会发生变化。
Node.js 策略模块
Node.js 策略模块是一项安全功能,旨在防止恶意代码在 Node.js 应用程序中加载和执行。虽然它不会追踪加载代码的来源,但它提供了针对潜在威胁的可靠防御机制。
策略模块利用 --experimental-policy CLI 标志来启用基于策略的代码加载。此标志采用策略清单文件(JSON 格式)作为参数。例如, --experimental-policy=policy.json。
策略清单文件包含 Node.js 在加载模块时遵循的策略,这提供了一种强大的方法来控制加载到应用程序中的代码的性质。
使用 Node.js 策略模块
让我们通过一个简单的示例来演示如何使用 Node.js 策略模块:
创建策略文件。这个文件应该是一个 JSON 文件,指定应用程序加载模块的策略。我们称之为 policy.json。
类似:
{ "resources": { "./moduleA.js": { "integrity": "sha384-xxxxx" }, "./moduleB.js": { "integrity": "sha384-yyyyy" } } }
这个策略文件指定 moduleA.js 和 moduleB.js 应加载特定的 integrity 值。
然而,为所有直接和间接依赖项生成策略文件并不简单。几年前,Bradley Meck 创建了 node-policy npm 包,它提供了一个 CLI 来自动生成策略文件。
使用 --experimental-policy 标志运行 Node.js 应用程序
node --experimental-policy=policy.json app.js
这个命令告诉 Node.js 在加载 app.js 中的模块时遵守 policy.json 中指定的策略。
为了防止篡改策略文件,你可以使用 --policy-integrity 标志为策略文件本身提供 integrity 值:
node --experimental-policy=policy.json --policy-integrity="sha384-zzzzz" app.js
这个命令可确保保持策略文件的完整性,即使磁盘上的文件发生更改也是如此。
integrity 策略的注意事项
Node.js 运行时没有内置功能来生成或管理策略文件,并且可能会带来困难,例如根据生产环境与开发环境以及动态模块导入来管理不同的策略。
另一个需要注意的是,如果你已经拥有处于当前状态的恶意 npm 包,那么生成模块完整性策略文件就为时已晚。
我个人建议你可以持续关注这方面的更新,并慢慢尝试逐步采用此功能。
有关 Node.js 策略模块的更多信息,你可以查看有关向 Node.js 引入实验性完整性策略这篇文章,其中提供了有关使用 Node.js 策略完整性的更详细的详细教程。
总结
当我们浏览了你应该在 2024 年开始使用的现代 Node.js 运行时功能时,很明显,这些功能帮助你简化开发流程、增强应用程序性能并加强安全性。这些功能不仅仅是时尚,也重新定义了 Node.js 开发方式的巨大潜力。