Node.js 22 正式进入 LTS 阶段(代号 Jod),意味着从实验期走向生产可用。对还在 Node.js 18 或 20 上运行的服务来说,这一版带来的不只是安全补丁——有几项能力会直接改变你写日常代码的方式。
真正稳定下来的内置 fetch
Node.js 18 引入的 fetch 一直挂着 experimental 标签,到 22 才正式标记为 stable。这意味着你不再需要 undici 的 polyfill,也不必在启动时加 --experimental-fetch。
// 直接用,无需任何 flag
const res = await fetch('https://api.github.com/repos/nodejs/node');
const data = await res.json();
console.log(`Node.js repo stars: ${data.stargazers_count}`);
如果你之前用 axios 或 node-fetch 做轻量请求,现在可以逐步替换。注意:内置 fetch 基于 undici,不支持 HTTP/2 push、没有自动重试,复杂场景仍需第三方库。
同步 require ESM 模块
这是 Node.js 22 最具争议也最实用的改动之一。过去 CJS 文件 require() 一个 ESM 模块会直接抛错,逼你整文件改成 async 或用动态 import()。现在 require(esm) 在多数场景下可以同步工作。
// cjs-app.js (CommonJS 文件)
const { format } = require('date-fns'); // date-fns 是纯 ESM 包
console.log(format(new Date(), 'yyyy-MM-dd HH:mm:ss'));
限制:如果目标 ESM 模块包含顶层 await,require() 仍然会报错。对于大多数"只是用 ESM 格式发布"的工具库(如 date-fns、lodash-es 的重新导出),同步 require 已经够用。
这对还在 CJS 体系里、又想用纯 ESM 依赖的项目是解药——不用再为一个库把整个入口改成异步。
原生 glob 与 SQLite
fs 模块新增了 glob 和 globSync,不用再装 glob 或 fast-glob:
import { globSync } from 'node:fs';
// 找出 src 下所有 .test.js 文件
const testFiles = globSync('src/**/*.test.js');
console.log(`Found ${testFiles.length} test files:`);
testFiles.forEach(f => console.log(' ' + f));
node:sqlite(实验性)则给嵌入式场景开了条路:
// --experimental-sqlite 启动
import { DatabaseSync } from 'node:sqlite';
const db = new DatabaseSync('local.db');
db.exec(`
CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT);
INSERT INTO kv (key, value) VALUES ('hello', 'world');
`);
const row = db.prepare('SELECT value FROM kv WHERE key = ?').get('hello');
console.log(row); // { value: 'world' }
db.close();
SQLite 模块还在实验阶段,API 可能变动,生产环境暂不建议依赖。但对 CLI 工具、本地缓存、测试 fixture 这类轻量场景,它比装 better-sqlite3 简单得多。
原生测试运行器增强
node:test 在 22 里变得更实用:支持 --test-reporter spec 输出更可读的格式,describe/it 语法不再需要额外 flag,覆盖率报告也更稳定。
// math.test.mjs
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { add } from './math.mjs';
describe('add', () => {
it('adds two numbers', () => {
assert.equal(add(1, 2), 3);
});
it('handles zero', () => {
assert.equal(add(0, 5), 5);
});
});
运行:
node --test math.test.mjs --test-reporter spec
如果你项目测试不多、不想引入 Jest 的全套配置,原生 runner 已经够用。但缺少 snapshot testing、mock 生态不如 Vitest 丰富——大型项目仍建议用成熟框架。
直接跑 TypeScript(实验性)
--experimental-strip-types 让 Node.js 跳过类型检查、直接剥离类型注解后执行 .ts 文件。这不是编译,只是"擦掉类型然后跑"。
# 直接执行 .ts 文件
node --experimental-strip-types server.ts
// server.ts
import http from 'node:http';
const port: number = 3000;
const server: http.Server = http.createServer((_req, res) => {
res.end('Hello from stripped TypeScript!\n');
});
server.listen(port, () => {
console.log(`Listening on ${port}`);
});
注意:它不处理 enum、namespace、parameter properties 等需要真正编译的 TS 语法。适合"类型注解为主"的简单脚本,不适合重度 TS 项目。搭配 tsx 或 ts-node 仍是更稳妥的选择。
迁移建议
从 Node.js 18/20 升级到 22 LTS,建议按这个节奏走:
- 先跑 CI——在 22 环境下跑完整测试套件,关注 ESM/CJS 边界行为变化。
- 清理 experimental flag——
fetch、test runner、websocket已稳定,去掉启动参数中的--experimental-fetch等。 - 逐步替换外部依赖——
glob→node:fs.glob、轻量 HTTP 请求 → 内置fetch,减少依赖树体积。 - 评估
require(esm)——如果项目卡在 CJS 但依赖了纯 ESM 包,这是最低成本的解法,但先确认目标包没有顶层await。 - 实验性功能观望——
node:sqlite、--experimental-strip-types先在工具脚本和本地开发中试用,不要进生产依赖。
Node.js 22 LTS 的维护期会持续到 2027 年初。现在升级不急,但如果你计划在下半年做依赖大升级或新项目启动,22 是比 20 更值得锁定的基础版本。