Node.js 22 LTS 实战:新特性与迁移要点

2026-05-14 16 预计阅读时间:1 分钟
来源:nodejs.org AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:6 分钟

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}`);

如果你之前用 axiosnode-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 模块包含顶层 awaitrequire() 仍然会报错。对于大多数"只是用 ESM 格式发布"的工具库(如 date-fnslodash-es 的重新导出),同步 require 已经够用。

这对还在 CJS 体系里、又想用纯 ESM 依赖的项目是解药——不用再为一个库把整个入口改成异步。

原生 glob 与 SQLite

fs 模块新增了 globglobSync,不用再装 globfast-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}`);
});

注意:它不处理 enumnamespaceparameter properties 等需要真正编译的 TS 语法。适合"类型注解为主"的简单脚本,不适合重度 TS 项目。搭配 tsxts-node 仍是更稳妥的选择。

迁移建议

从 Node.js 18/20 升级到 22 LTS,建议按这个节奏走:

  1. 先跑 CI——在 22 环境下跑完整测试套件,关注 ESM/CJS 边界行为变化。
  2. 清理 experimental flag——fetchtest runnerwebsocket 已稳定,去掉启动参数中的 --experimental-fetch 等。
  3. 逐步替换外部依赖——globnode:fs.glob、轻量 HTTP 请求 → 内置 fetch,减少依赖树体积。
  4. 评估 require(esm)——如果项目卡在 CJS 但依赖了纯 ESM 包,这是最低成本的解法,但先确认目标包没有顶层 await
  5. 实验性功能观望——node:sqlite--experimental-strip-types 先在工具脚本和本地开发中试用,不要进生产依赖。

Node.js 22 LTS 的维护期会持续到 2027 年初。现在升级不急,但如果你计划在下半年做依赖大升级或新项目启动,22 是比 20 更值得锁定的基础版本。


相关推荐