npm v12 是近年来变化最大的一个版本,直接影响全球数百万 JavaScript 项目的构建和部署流程。根据 npm 官方统计,npm 每周处理超过 400 亿次包下载,任何一个 breaking change 都会产生蝴蝶效应般的连锁反应。如果你还在用 require() 写 CommonJS 模块,或者对 peerDependencies 的报错选择性忽略,这篇文章会告诉你为什么现在必须行动了。
🔥 一、ESM 优先:npm 正式倒向 ES Modules
📌 从可选到默认:ESM 成为一等公民
npm v12 最具争议的变更是 默认将 type 字段视为 "module"。这意味着如果你的 package.json 没有显式声明 "type": "commonjs",npm 会假设你的包是 ESM。
⚠️ **警告:**这个变更会导致大量现有 CommonJS 包在没有修改的情况下无法被正确解析。务必在升级前检查所有依赖的
type字段。
变更前后的对比:
| 场景 | npm v11 行为 | npm v12 行为 | 推荐操作 |
|---|---|---|---|
package.json 无 type 字段 |
默认 CommonJS | 默认 ESM | ✅ 显式声明 "type" |
.js 文件无 type |
按 CommonJS 解析 | 按 ESM 解析 | ✅ 使用 .cjs 后缀 |
--experimental-detect-module |
可选启用 | 默认启用 | ✅ 无需操作 |
require() 导入 ESM 包 |
报错 | 同步加载(实验性) | ⚠️ 仍有兼容性问题 |
🔧 迁移实战:CommonJS 到 ESM 的三种策略
策略一:显式声明 CommonJS(最小改动)
// package.json — 最保守的迁移方式
{
"name": "my-legacy-package",
"type": "commonjs",
"version": "2.0.0"
}
这是最安全的方式,但只是把问题推给了未来。适合大型 monorepo 中暂时无法全面迁移的场景。
策略二:全面迁移到 ESM(推荐)
// src/utils.js — ESM 写法
import { readFile } from 'node:fs/promises';
import { createHash } from 'node:crypto';
export async function hashFile(filePath) {
const content = await readFile(filePath);
return createHash('sha256').update(content).digest('hex');
}
export function parseJSON(str) {
try {
return { ok: true, data: JSON.parse(str) };
} catch (e) {
return { ok: false, error: e.message };
}
}
// src/index.js — ESM 入口
export { hashFile, parseJSON } from './utils.js';
💡 提示:迁移到 ESM 时,所有相对路径导入必须包含文件扩展名(
.js)。这是最常见的报错来源。
策略三:Dual Package(同时支持 CJS 和 ESM)
// package.json — dual package 配置
{
"name": "my-dual-package",
"type": "module",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
}
}
// rollup.config.js — 构建 dual package
import { readFileSync } from 'node:fs';
const pkg = JSON.parse(readFileSync('./package.json', 'utf-8'));
export default {
input: 'src/index.js',
output: [
{ file: pkg.exports['.'].import, format: 'es' },
{ file: pkg.exports['.'].require, format: 'cjs' },
],
};
⚠️ **警告:**Dual Package 的
import和require会创建两份独立的模块实例,导致instanceof检查失败。如果包维护全局状态(如单例模式),请谨慎使用。
🔐 二、依赖安全:严格的 peerDeps 和审计强化
📌 peerDependencies 不再是"建议"
npm v12 将 peerDependencies 的默认安装行为从 自动安装 改为 严格校验。不匹配的 peer deps 会直接导致安装失败,而不是像以前那样只输出一个可忽略的警告。
# npm v11:警告但继续安装
npm warn react-dom@18.3.1 requires a peer of react@^18.0.0
# 安装成功 ✅
# npm v12:直接失败
npm error ERESOLVE unable to resolve dependency tree
npm error peer react@"^18.0.0" from react-dom@18.3.1
npm error Conflicting peer dependency: react@18.2.0
# 安装失败 ❌
解决方案矩阵:
# 方案 1:使用 --legacy-peer-deps(临时方案,不推荐长期使用)
npm install --legacy-peer-deps
# 方案 2:使用 --force(强制覆盖,有风险)
npm install --force
# 方案 3:使用 overrides 显式指定版本(推荐)
# package.json
{
"overrides": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
}
# 方案 4:升级依赖到兼容版本(最佳方案)
npm install react@latest react-dom@latest
📊 npm audit 增强:从警告到阻断
npm v12 引入了 audit.level 配置项,允许团队将安全审计从"可选报告"升级为"CI 门禁":
// .npmrc — 在 CI 环境中强制安全审计
audit=true
audit-level=moderate
# .github/workflows/ci.yml — CI 中的安全检查
name: CI
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npm audit --audit-level=moderate
漏洞等级对比:
| 等级 | 描述 | npm v11 行为 | npm v12 行为 |
|---|---|---|---|
info |
信息性 | 日志输出 | 日志输出 |
low |
低风险 | 警告 | 警告 |
moderate |
中风险 | 警告 | audit-level=moderate 时阻断 |
high |
高风险 | 警告 | audit-level=high 时阻断 |
critical |
严重 | 警告 | 默认阻断 |
📌 记住:npm v12 中
critical级别的漏洞会默认阻断安装。这是安全优先的设计哲学转变。
🚀 三、性能与工程化:lockfile v4 与 workspace 改进
📌 lockfile v4:更快的安装速度
npm v12 默认使用 lockfile v4 格式,相比 v3 格式有显著的解析性能提升:
# 对比测试(在一个 500+ 依赖的 monorepo 上)
# npm v11 (lockfile v3)
$ time npm ci
real 0m42.318s
# npm v12 (lockfile v4)
$ time npm ci
real 0m28.156s # 提升约 33%
lockfile v4 的核心改进:
// package-lock.json v4 结构(简化示例)
{
"lockfileVersion": 4,
"packages": {
"": {
"name": "my-project",
"dependencies": {
"express": "^4.18.0"
}
},
"node_modules/express": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz",
"integrity": "sha512-...",
// v4 新增:去重标记,减少文件体积
"deduped": true
}
}
}
💡 **提示:**升级到 lockfile v4 后,旧版本的 npm 无法解析新的 lockfile。团队中所有人必须同步升级 npm 版本。
迁移步骤:
# 1. 升级 npm
npm install -g npm@latest
# 2. 删除旧的 lockfile 和 node_modules
rm -rf node_modules package-lock.json
# 3. 重新生成 lockfile v4
npm install
# 4. 验证 lockfile 版本
head -5 package-lock.json
# 应该看到 "lockfileVersion": 4
📌 Workspace 改进:更智能的 monorepo 支持
npm v12 对 workspace 的改进主要集中在三个方面:
1. 拓扑排序安装
// 根 package.json
{
"workspaces": [
"packages/*"
],
"dependencies": {
"@myorg/core": "workspace:*",
"@myorg/utils": "workspace:*"
}
}
npm v12 会自动按照依赖拓扑顺序安装 workspace 包,避免了之前需要手动指定安装顺序的问题。
2. Workspace 协议增强
// packages/app/package.json
{
"dependencies": {
// 精确版本匹配
"@myorg/core": "workspace:*",
// 使用 caret 范围
"@myorg/utils": "workspace:^1.0.0",
// 发布时自动替换为实际版本(新功能)
"@myorg/config": "workspace:^"
}
}
3. npm run --workspaces 并行执行
# 并行运行所有 workspace 的 build 脚本
npm run build --workspaces --parallel
# 只运行受影响的 workspace(基于 git diff)
npm run build --workspaces --affected
⚠️ 四、其他重要变更速览
弃用与移除
# ❌ 已移除:npm explore 命令
npm explore lodash # npm v12 中不再可用
# ✅ 替代方案:直接使用 npx
npx -p lodash node -e "console.log(_.VERSION)"
# ❌ 已移除:--global-style 标志
npm install --global-style # 不再支持
# ❌ 已弃用:npm link(将在 v13 移除)
npm link # 建议迁移到 workspace 或 npm install <path>
新增安全特性
# 包完整性校验(默认启用)
npm install express
# 自动验证 tarball 的签名和 checksum
# 供应链攻击检测
npm audit signatures
# 检查包的注册表签名是否与 npm registry 一致
💡 五、迁移清单与最佳实践
✅ 升级前检查清单
# 1. 检查当前 Node.js 和 npm 版本
node -v # 需要 >= 20.0.0
npm -v # 当前版本
# 2. 检查项目中的 type 字段
grep -r '"type"' package.json
# 3. 检查 peer deps 警告
npm ls 2>&1 | grep "WARN"
# 4. 检查是否有 npm link 的使用
ls -la node_modules/ | grep "^l"
# 5. 检查 lockfile 版本
head -5 package-lock.json | grep lockfileVersion
❌ 常见陷阱
- ❌ 不要在 CI 中使用
--legacy-peer-deps作为长期方案,它会掩盖真正的依赖冲突 - ❌ 不要忽略
npm audit的critical级别告警,v12 会直接阻断安装 - ❌ 不要在没有同步升级 npm 的情况下提交 lockfile v4
- ❌ 不要假设所有 npm 包都已适配 ESM,部分老包仍需
require()加载
✅ 推荐做法
- ✅ 在
package.json中显式声明"type": "commonjs"或"type": "module" - ✅ 使用
overrides解决 peer deps 冲突,而不是--force - ✅ 在 CI 中配置
audit-level=moderate或更高 - ✅ 使用
npm exec替代npx以获得更好的安全性 - ✅ 在 monorepo 中充分利用 workspace 协议的新特性
⚡ **关键结论:**npm v12 的变更不是"渐进式改进",而是 JavaScript 包管理生态的一次范式转移。ESM 优先、严格依赖校验、供应链安全——这三个方向共同指向一个结论:JavaScript 正在从"脚本语言"的随意走向"工程语言"的严谨。尽早迁移,不要等到 v13 移除
--legacy-peer-deps时才被迫行动。
相关工具推荐:
- 🔧 pnpm — 更快的包管理器,天然支持严格依赖隔离
- 🔧 Bun — 内置包管理器,兼容 npm 生态
- 🔧 Socket.dev — 供应链安全检测平台
- 🔧 npm-check-updates — 批量升级依赖版本