Node.js 22 LTS 正式落地:从 require(esm) 到内置 WebSocket,生产环境该关注什么

2026-06-18 29 预计阅读时间: 1 分钟
来源: nodejs.org AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:7 分钟

Node.js 22 已进入 LTS 阶段,代号 Jod。从 22.0.0 到当前的 22.23.0,这条线累积了不少让日常开发更顺手的能力——require() 终于能直接加载 ESM 模块,WebSocket 客户端不再需要第三方库,node --run 替代了 npm run 的部分场景,TypeScript 文件也能直接跑起来。这些不是实验玩具,而是逐步稳定到可以上生产的特性。下面挑几个最影响日常工作的改动,逐个拆开看。

require(esm):CJS 与 ESM 的墙终于开了个门

过去 CJS 项目想引用 ESM 包,只能走动态 import(),异步传染整个调用链。Node.js 22 把 require(esm) 稳定化,同步引用 ESM 导出终于可行了。

实际效果:你的老 CJS 服务可以直接 require() 一个只提供 ESM 入口的 npm 包,不用把整个文件改成 async。

// old-service.js — CommonJS 项目引用纯 ESM 包
const { parse } = require('yaml'); // yaml 包已只提供 ESM 导出

const config = parse(fs.readFileSync('config.yaml', 'utf8'));
console.log(config.database.host);

注意几个边界: - 只支持 named exports,不支持 ESM 模块的 default 导出(需包作者显式 module.exports = { default })。实际遇到 default 不可用时,用 const mod = require('pkg'); mod.default 兜底。 - Top-level await 的 ESM 模块仍然不能被 require() 加载,会抛 ERR_REQUIRE_ASYNC_MODULE。 - 这是 Node.js 22 的稳定特性,不需要加 --experimental-require-module

内置 WebSocket 客户端:告别 ws 依赖

Node.js 22 把 WebSocket 加入全局对象,协议实现符合浏览器标准。对大多数后端场景,你不再需要安装 wswebsocket

// ws-client.js — 内置 WebSocket,零依赖
const ws = new WebSocket('ws://localhost:8080/echo');

ws.addEventListener('open', () => {
  ws.send('hello from node 22');
});

ws.addEventListener('message', (event) => {
  console.log('received:', event.data);
  ws.close();
});

ws.addEventListener('error', (err) => {
  console.error('connection failed:', err.message);
});

ws 库的差异:内置版本不支持自定义 headers(暂无 WebSocket(url, protocols, options) 的 headers 字段),也不支持 permessage-deflate 压缩扩展。如果你的服务端依赖压缩或需要传认证 header,暂时还得保留 ws

node --run:少一层 npm 间接调用

node --run <script> 直接执行 package.json 中定义的 script,跳过 npm 的脚本解析层。好处:更快、更干净,CI 环境里不用装 npm。

// package.json
{
  "scripts": {
    "build": "tsc && node build-post.js",
    "test": "node --test test/**/*.test.js",
    "lint": "eslint src/"
  }
}
# 直接用 node 执行,不依赖 npm
node --run build
node --run test

# CI Docker 镜像里只用 node,不装 npm
docker run node:22 node --run test

限制:不支持 npm 的 pre/post 钩子,不解析 npm_config_* 环境变量。复杂脚本链还是走 npm run

--experimental-strip-types:TypeScript 直接跑

Node.js 22.6.0 引入 --experimental-strip-types,Node.js 会剥离类型注解后直接执行,不做类型检查。22.x 后续版本逐步稳定了这个能力。适合开发阶段的快速验证,不适合替代 tsc 编译。

// server.ts — 直接用 node 运行
import http from 'node:http';

const port: number = Number(process.env.PORT ?? 3000);

const server = http.createServer((req: http.IncomingMessage, res: http.ServerResponse) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('running on node 22\n');
});

server.listen(port, () => {
  console.log(`listening on :${port}`);
});
# 开发阶段快速启动
node --experimental-strip-types server.ts

关键认知:这是类型剥离,不是类型编译。enumnamespaceparameter properties 等需要转换的 TS 特性不支持,遇到会报错。正式发布前仍应跑 tsc --noEmit 做类型检查。

fs.glob:文件搜索不再依赖 globby

fs.globfs.globSync 进入稳定 API,基于 Rust 库 fast-glob 的性能水准。

// find-sources.js
import { glob } from 'node:fs/promises';

const files = await glob('src/**/*.ts', {
  cwd: process.cwd(),
  exclude: ['**/*.d.ts', '**/*.test.ts'],
});

console.log('source files:', files);

升级到 22 LTS 的实操清单

  1. 版本选择:生产用 22.x LTS 镜像,别用 22.0.0,直接拉最新的 22.23.0,累积了半年多的 bug fix。
  2. require(esm) 迁移:先在测试环境验证你依赖的 ESM 包是否全部支持 named exports,遇到 default-only 包加一层适配。
  3. WebSocket 替换:新项目直接用内置 WebSocket;老项目如果用了压缩或自定义 header,暂不替换。
  4. TypeScript 开发流:开发用 --experimental-strip-types 加速,CI 保留 tsc --noEmit 守门。
  5. node --run 进 CI:Dockerfile 里把 npm run 替换为 node --run,镜像可以不装 npm,体积更小。
  6. 监控兼容性:APM 和 tracing 工具确认支持 Node.js 22 的 V8 12.4+,部分老版本 SDK 可能需要升级。

Node.js 22 LTS 不是激进重构,而是把过去两年实验的特性逐步收口到稳定态。对大多数项目,升级风险可控,收益具体——少装依赖、少写适配代码、CI 更轻量。现在是从 20 LTS 切过来的合理时机。


相关推荐