Node.js 项目的实用 CI 配置
每个 Node.js 项目都会达到这样一个阶段:手动测试变得不再可靠。有人在推送代码前忘记运行代码检查工具。测试在本地通过,但在队友的机器上却失败了。依赖项更新导致生产环境崩溃,因为没有人发现不兼容问题。
一个结构良好的 CI 流水线可以自动捕获这些问题。本文将解释一个可靠的 Node.js CI 基础配置应该是什么样子(使用 GitHub Actions),为什么需要每个组件,以及如何思考这些组成部分,使你的流水线经久耐用。
核心要点
- 在 CI 流水线中使用
npm ci而不是npm install,以基于锁文件实现确定性、可重现的构建 - 构建流水线以快速失败:安装依赖 → 代码检查和类型检查 → 运行测试
- 使用版本矩阵测试你实际支持的 Node.js 版本,重点关注活跃 LTS 版本
- 在测试之前运行静态分析,以快速发现错误并生成更清晰的失败消息
- 通过避免过度设计的缓存和过多的版本固定,保持流水线简单且易于维护
JavaScript CI 流水线的基础功能
一个实用的 Node.js 项目 CI 流水线需要处理三个关注点:确保依赖项一致性、验证代码质量,以及在相关的 Node 版本上运行测试。
流水线应该快速失败且失败可见。如果出现问题,开发人员需要立即知道并理解原因。
核心阶段
一个可靠的 GitHub Actions Node CI 工作流遵循以下顺序:
安装依赖 → 代码检查和类型检查 → 运行测试
每个阶段都是下一个阶段的门控。如果代码甚至无法正确解析,就没有必要运行完整的测试套件。
依赖项安装:为什么 npm ci 很重要
最重要的 npm CI 最佳实践是在流水线中使用 npm ci 而不是 npm install。
npm ci 做了两件对 CI 很重要的事情:
- 它精确安装锁文件中的内容——不进行版本解析,没有意外
- 它首先删除
node_modules,确保从干净状态开始
这种确定性行为意味着你的 CI 环境与锁文件指定的内容匹配。当构建失败时,你知道失败不是由依赖项漂移引起的。
你的锁文件(npm 的 package-lock.json、pnpm 的 pnpm-lock.yaml、Yarn 的 yarn.lock)必须提交到代码仓库。没有它,npm ci 将无法工作,你也会失去可重现性。
使用 Corepack 管理包管理器
如果你的团队使用 pnpm 或 Yarn,Corepack 可以处理包管理器的版本控制。在安装依赖项之前,在工作流中启用它。这确保每个人(包括 CI)都使用 package.json 中指定的相同包管理器版本。
版本矩阵:跨 Node 版本测试
版本矩阵允许你同时在多个 Node.js 版本上运行流水线。对于大多数项目,针对活跃 LTS 版本进行测试就足够了。兼容性要求更广泛的项目可能会添加当前的维护 LTS 版本。
矩阵方法可以及早发现兼容性问题。在较新 Node 版本中有效的语法特性可能在用户依赖的旧版本中不存在。
保持矩阵最小化。针对每个可能的版本进行测试会增加 CI 时间,但收益不成比例。专注于你的项目实际支持的版本。
Discover how at OpenReplay.com.
在测试之前进行代码检查和类型检查
在测试套件之前运行静态分析。ESLint 捕获代码质量问题。TypeScript(如果你使用它)捕获类型错误。两者的运行速度都比大多数测试套件快。
这种顺序很重要,原因有二:
- 更快的反馈:语法错误在几秒钟内就会显现,而不是几分钟
- 更清晰的失败:代码检查错误比由相同底层问题引起的隐晦测试失败更容易诊断
配置这些工具在出现错误时使构建失败。不会导致构建失败的警告会被忽略。
测试执行和失败可见性
你的测试阶段应该产生清晰的输出。当测试失败时,开发人员需要快速识别问题——理想情况下无需深入折叠的日志部分。
大多数测试运行器支持 CI 友好的输出格式。Jest、Vitest 和 Node 的内置测试运行器都能检测 CI 环境并相应调整其输出。
考虑以下实践:
- 仅在实际使用覆盖率数据时才运行带覆盖率的测试
- 如果运行器支持且测试是独立的,则并行化测试文件
- 在开发分支上快速失败,在主分支上运行完整套件
缓存:关于预期的说明
依赖项缓存可以减少安装时间,但收益各不相同。依赖项较少的小型项目可能改善很小。大型 monorepo 可能每次运行节省几分钟。
不要过度设计缓存。actions/setup-node 中的内置缓存可以处理常见情况。如果安装速度很慢,在添加复杂性之前先进行测量。
保持流水线的可维护性
需要持续更新的 CI 流水线会成为负担。避免将 action 版本固定到特定补丁——使用接收兼容更新的主版本标签。在可能的情况下,通过发布线而不是确切版本来引用 Node 版本。
目标是一个可靠运行且无需频繁维护的 JavaScript CI 流水线。当你确实需要更新它时,更改应该是有意为之的,而不是被动应对的。
结论
一个可靠的 Node.js CI 配置不需要复杂的配置。使用 npm ci 确定性地安装依赖项,在测试之前运行静态分析,并针对你支持的 Node 版本进行测试。使失败可见,并保持流水线足够简单以便维护。
从这个基线开始。仅在有特定问题需要解决时才增加复杂性。
常见问题
npm ci 精确安装锁文件指定的内容而不解析版本,并且首先删除 node_modules 以获得干净状态。npm install 可能会更新锁文件并解析不同的版本。对于 CI,npm ci 提供与提交的锁文件完全匹配的确定性构建。
测试你的项目实际支持的版本。对于大多数项目,活跃 LTS 版本就足够了。如果需要更广泛的兼容性,可以添加维护 LTS 版本。避免测试每个可能的版本,因为它会增加 CI 时间而收益不成比例。
代码检查比大多数测试套件运行得更快,可以在几秒钟内捕获语法和代码质量问题。首先运行它可以提供更快的反馈并产生更清晰的失败消息。代码检查错误比由相同底层问题引起的隐晦测试失败更容易诊断。
这取决于你的项目规模。依赖项较少的小型项目从缓存中获得的改善很小。大型 monorepo 每次运行可以节省几分钟。从 actions/setup-node 中的内置缓存开始,在添加复杂性之前测量实际的安装时间。
Understand every bug
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.