name: 依赖升级 描述: 管理主要依赖版本升级,包括兼容性分析、分阶段推出和全面测试。在升级框架版本、更新主要依赖或管理库中的破坏性更改时使用。
依赖升级
掌握主要依赖版本升级、兼容性分析、分阶段升级策略和全面测试方法。
何时使用此技能
- 升级主要框架版本
- 更新安全漏洞依赖
- 现代化遗留依赖
- 解决依赖冲突
- 规划增量升级路径
- 测试兼容性矩阵
- 自动化依赖更新
语义版本控制回顾
主版本.次版本.修订版本 (例如,2.3.1)
主版本: 破坏性更改
次版本: 新功能,向后兼容
修订版本: 错误修复,向后兼容
^2.3.1 = >=2.3.1 <3.0.0 (次版本更新)
~2.3.1 = >=2.3.1 <2.4.0 (修订版本更新)
2.3.1 = 精确版本
依赖分析
审计依赖
# npm
npm outdated
npm audit
npm audit fix
# yarn
yarn outdated
yarn audit
# 检查主要更新
npx npm-check-updates
npx npm-check-updates -u # 更新 package.json
分析依赖树
# 查看为什么安装某个包
npm ls 包名
yarn why 包名
# 查找重复包
npm dedupe
yarn dedupe
# 可视化依赖
npx madge --image graph.png src/
兼容性矩阵
// compatibility-matrix.js
const compatibilityMatrix = {
react: {
"16.x": {
"react-dom": "^16.0.0",
"react-router-dom": "^5.0.0",
"@testing-library/react": "^11.0.0",
},
"17.x": {
"react-dom": "^17.0.0",
"react-router-dom": "^5.0.0 || ^6.0.0",
"@testing-library/react": "^12.0.0",
},
"18.x": {
"react-dom": "^18.0.0",
"react-router-dom": "^6.0.0",
"@testing-library/react": "^13.0.0",
},
},
};
function checkCompatibility(packages) {
// 根据矩阵验证包版本
}
分阶段升级策略
阶段1: 规划
# 1. 识别当前版本
npm list --depth=0
# 2. 检查破坏性更改
# 阅读 CHANGELOG.md 和 MIGRATION.md
# 3. 创建升级计划
echo "升级顺序:
1. TypeScript
2. React
3. React Router
4. 测试库
5. 构建工具" > UPGRADE_PLAN.md
阶段2: 增量更新
# 不要一次性升级所有内容!
# 步骤1: 更新 TypeScript
npm install typescript@latest
# 测试
npm run test
npm run build
# 步骤2: 更新 React(一次一个主版本)
npm install react@17 react-dom@17
# 再次测试
npm run test
# 步骤3: 继续其他包
npm install react-router-dom@6
# 依此类推...
阶段3: 验证
// tests/compatibility.test.js
describe("依赖兼容性", () => {
it("应该有兼容的 React 版本", () => {
const reactVersion = require("react/package.json").version;
const reactDomVersion = require("react-dom/package.json").version;
expect(reactVersion).toBe(reactDomVersion);
});
it("不应该有对等依赖警告", () => {
// 运行 npm ls 并检查警告
});
});
破坏性更改处理
识别破坏性更改
# 直接检查更改日志
curl https://raw.githubusercontent.com/facebook/react/master/CHANGELOG.md
代码修改器用于自动修复
# 使用转换 URL 运行 jscodeshift
npx jscodeshift -t <transform-url> <路径>
# 示例: 重命名不安全生命周期方法
npx jscodeshift -t https://raw.githubusercontent.com/reactjs/react-codemod/master/transforms/rename-unsafe-lifecycles.js src/
# 对于 TypeScript 文件
npx jscodeshift -t https://raw.githubusercontent.com/reactjs/react-codemod/master/transforms/rename-unsafe-lifecycles.js --parser=tsx src/
# 干运行以预览更改
npx jscodeshift -t https://raw.githubusercontent.com/reactjs/react-codemod/master/transforms/rename-unsafe-lifecycles.js --dry src/
自定义迁移脚本
// migration-script.js
const fs = require("fs");
const glob = require("glob");
glob("src/**/*.tsx", (err, files) => {
files.forEach((file) => {
let content = fs.readFileSync(file, "utf8");
// 用新 API 替换旧 API
content = content.replace(
/componentWillMount/g,
"UNSAFE_componentWillMount",
);
// 更新导入
content = content.replace(
/import { Component } from 'react'/g,
"import React, { Component } from 'react'",
);
fs.writeFileSync(file, content);
});
});
测试策略
单元测试
// 确保升级前后测试通过
npm run test
// 如果需要,更新测试工具
npm install @testing-library/react@latest
集成测试
// tests/integration/app.test.js
describe("应用集成", () => {
it("应该渲染而不崩溃", () => {
render(<App />);
});
it("应该处理导航", () => {
const { getByText } = render(<App />);
fireEvent.click(getByText("导航"));
expect(screen.getByText("新页面")).toBeInTheDocument();
});
});
视觉回归测试
// visual-regression.test.js
describe("视觉回归", () => {
it("应该匹配快照", () => {
const { container } = render(<App />);
expect(container.firstChild).toMatchSnapshot();
});
});
端到端测试
// cypress/e2e/app.cy.js
describe("端到端测试", () => {
it("应该完成用户流程", () => {
cy.visit("/");
cy.get('[data-testid="login"]').click();
cy.get('input[name="email"]').type("user@example.com");
cy.get('button[type="submit"]').click();
cy.url().should("include", "/dashboard");
});
});
自动化依赖更新
Renovate 配置
// renovate.json
{
"extends": ["config:base"],
"packageRules": [
{
"matchUpdateTypes": ["minor", "patch"],
"automerge": true
},
{
"matchUpdateTypes": ["major"],
"automerge": false,
"labels": ["major-update"]
}
],
"schedule": ["before 3am on Monday"],
"timezone": "America/New_York"
}
Dependabot 配置
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
reviewers:
- "team-leads"
commit-message:
prefix: "chore"
include: "scope"
回滚计划
// rollback.sh
#!/bin/bash
# 保存当前状态
git stash
git checkout -b upgrade-branch
# 尝试升级
npm install package@latest
# 运行测试
if npm run test; then
echo "升级成功"
git add package.json package-lock.json
git commit -m "chore: 升级包"
else
echo "升级失败,正在回滚"
git checkout main
git branch -D upgrade-branch
npm install # 从 package-lock.json 恢复
fi
常见升级模式
锁文件管理
# npm
npm install --package-lock-only # 仅更新锁文件
npm ci # 从锁文件清洁安装
# yarn
yarn install --frozen-lockfile # CI 模式
yarn upgrade-interactive # 交互式升级
对等依赖解决
# npm 7+: 严格对等依赖
npm install --legacy-peer-deps # 忽略对等依赖
# npm 8+: 覆盖对等依赖
npm install --force
工作区升级
# 更新所有工作区包
npm install --workspaces
# 更新特定工作区
npm install package@latest --workspace=packages/app
资源
- references/semver.md: 语义版本控制指南
- references/compatibility-matrix.md: 常见兼容性问题
- references/staged-upgrades.md: 增量升级策略
- references/testing-strategy.md: 全面测试方法
- assets/upgrade-checklist.md: 逐步检查清单
- assets/compatibility-matrix.csv: 版本兼容性表
- scripts/audit-dependencies.sh: 依赖审计脚本
最佳实践
- 阅读更改日志: 理解变化内容
- 增量升级: 一次一个主版本
- 全面测试: 单元、集成、端到端测试
- 检查对等依赖: 早期解决冲突
- 使用锁文件: 确保可重现安装
- 自动化更新: 使用 Renovate 或 Dependabot
- 监控: 升级后监视运行时错误
- 文档: 保持升级笔记
升级检查清单
升级前:
- [ ] 查看当前依赖版本
- [ ] 阅读破坏性更改的更改日志
- [ ] 创建功能分支
- [ ] 备份当前状态(git 标签)
- [ ] 运行完整测试套件(基线)
升级中:
- [ ] 一次升级一个依赖
- [ ] 更新对等依赖
- [ ] 修复 TypeScript 错误
- [ ] 如果需要,更新测试
- [ ] 每次升级后运行测试套件
- [ ] 检查包大小影响
升级后:
- [ ] 全面回归测试
- [ ] 性能测试
- [ ] 更新文档
- [ ] 部署到暂存环境
- [ ] 监控错误
- [ ] 部署到生产环境
常见陷阱
- 一次性升级所有依赖
- 升级后不测试
- 忽略对等依赖警告
- 忘记更新锁文件
- 不阅读破坏性更改笔记
- 跳过主版本
- 没有回滚计划