Monorepo 面试题
本面试题基于《Monorepo 学习文档》编写,按难度分为基础、进阶、高级三个等级,每题均附详细参考答案与评分要点,适用于前端工程化岗位面试与技术评估。
一、基础题
第 1 题
请简述 Monorepo 与 Polyrepo 的主要区别,以及各自适合什么场景。
参考答案:
Monorepo:将多个相关项目放在同一个仓库中管理,共享工具链和规范。
优势:
- 代码共享方便;
- 原子化提交,保证多项目一致性;
- 统一 ESLint、Prettier、CI/CD 规范;
- 跨项目重构成本低;
- 构建缓存复用。
劣势:
- 仓库体积大;
- 权限控制复杂;
- 构建耦合;
- 工具链要求高。
Polyrepo:一个项目一个仓库,独立演进。
优势:
- 隔离性强;
- 权限简单;
- 工具简单。
劣势:
- 代码复用困难;
- 版本管理复杂;
- 重复配置;
- 跨项目重构困难。
适用场景:
- Monorepo 适合多个项目共享大量代码、需要频繁跨项目协作、希望统一规范的大型团队。
- Polyrepo 适合项目关联度低、团队独立、权限隔离要求高的场景。
评分维度:
- 能解释两者定义(30%)
- 能分别说出优劣势(40%)
- 能给出适用场景(30%)
第 2 题
什么是幽灵依赖(Phantom Dependencies)?pnpm 如何避免幽灵依赖?
参考答案:
幽灵依赖是指某个包没有在其 package.json 中声明依赖,却可以使用 node_modules 中其他包安装的依赖。这会导致依赖关系不透明,一旦上层依赖变化,代码可能无法运行。
pnpm 通过严格的依赖隔离机制避免幽灵依赖:
- 每个包只能访问自己
package.json中声明的依赖; - pnpm 使用软链接和硬链接组织
node_modules,不随意提升依赖; - 子包的 node_modules 结构清晰,不会出现未声明却可访问的依赖。
评分维度:
- 能解释幽灵依赖概念(40%)
- 说明危害(20%)
- 说明 pnpm 的严格隔离和软链接机制(40%)
第 3 题
请解释 workspace:* 协议的作用。
参考答案:
workspace:* 是 pnpm/yarn 等包管理器支持的 workspace 协议,用于引用同一个 Monorepo 中的内部包。
作用:
- 避免手动维护内部包的版本号;
- 发布到 npm 时,工具会自动将其替换为具体版本号;
- 保证开发时使用本地最新代码,而不是从 npm 下载。
评分维度:
- 能说明 workspace:* 引用内部包(40%)
- 提到发布时自动替换版本号(40%)
- 说明开发时使用本地代码(20%)
第 4 题
Changesets 的工作流程是什么?
参考答案:
Changesets 是现代 Monorepo 常用的版本管理工具,工作流程如下:
- 开发者提交代码后,运行
pnpm changeset; - 选择变更的包和变更类型(major/minor/patch),生成一个 changeset 文件;
- 发布时运行
pnpm changeset version,自动 bump 版本号并生成 changelog; - 最后运行
pnpm changeset publish,将包发布到 npm。
评分维度:
- 能按顺序说出四个步骤(60%)
- 提到 major/minor/patch 变更类型(20%)
- 说明版本号自动 bump 和 changelog 生成(20%)
第 5 题
在 Monorepo 中,根目录依赖和子包依赖应该如何划分?
参考答案:
- 根目录依赖:通用的开发工具,如 TypeScript、ESLint、Prettier、Jest、Turborepo。这些工具不随应用发布,只用于开发、构建、测试。
- 子包依赖:该包运行时真正需要的依赖,如 React、Vue、lodash 等。应该放在对应子包的
dependencies或devDependencies中。
划分原则:
- 能被多个子包共享的开发工具放根目录;
- 运行时依赖必须声明在子包中;
- 避免把所有依赖都堆在根目录,导致子包依赖关系不清晰。
评分维度:
- 能区分根目录和子包依赖(50%)
- 能举例说明(30%)
- 提到依赖关系清晰化原则(20%)
第 6 题
请对比 pnpm workspace、Turborepo、Nx、Rush 的适用规模。
参考答案:
| 方案 | 复杂度 | 构建缓存 | 任务编排 | 依赖管理 | 适用规模 |
|---|---|---|---|---|---|
| pnpm workspace | 低 | 无 | 无 | 强 | 中小型 |
| Turborepo | 中 | 强 | 强 | 依赖包管理器 | 中大型 |
| Nx | 高 | 强 | 强 | 强 | 大型 |
| Rush | 高 | 中 | 中 | 很强 | 超大型 |
评分维度:
- 能说出四种方案(40%)
- 能按规模给出建议(40%)
- 能说明 Turborepo/Nx 需配合包管理器(20%)
第 7 题
为什么在大型 Monorepo 中需要使用 Affected Commands?
参考答案:
Affected Commands 只构建和测试受代码改动影响的项目,而不是每次改动都全量构建所有子包。
原因:
- 大型 Monorepo 子包数量多,全量构建非常耗时;
- 很多改动只影响少量子包,无需重建全部;
- 可以显著缩短 CI 反馈时间,提高开发效率。
常见命令:
- Turborepo:
npx turbo run build --filter=[HEAD^1]或--filter=...origin/main。 - Nx:
nx affected:build --base=main --head=HEAD。
注意事项:
- 依赖图必须准确,否则可能漏构建或过度构建。
- 合并到主分支前,通常基于 origin/main 计算 affected。
- 远程缓存可以进一步加速 affected 构建。
评分维度:
- 能解释 Affected Commands 含义(25%)
- 说明大型仓库全量构建慢的问题(25%)
- 能举例命令并说明 base/head(25%)
- 提到 CI 时间优化和依赖图准确性(25%)
二、进阶题
第 8 题
Turborepo 的 Pipeline 中,dependsOn: ["^build"] 和 dependsOn: ["build"] 有什么区别?
参考答案:
dependsOn: ["^build"]:表示当前包的build任务依赖其上游依赖包的build任务。^表示上游依赖,即先构建被依赖的包。dependsOn: ["build"]:表示当前包的当前任务依赖当前包自己的build任务,即先完成本包的构建。
例如:
{
"pipeline": {
"build": { "dependsOn": ["^build"] },
"test": { "dependsOn": ["build"] }
}
}
build会先构建所有依赖包的 build;test会在本包 build 完成后执行。
评分维度:
- 能区分
^和未加^的含义(60%) - 能结合 build/test 场景说明(30%)
- 提到依赖图和任务调度(10%)
第 9 题
如何设计 Monorepo 中的测试策略?
参考答案:
- 统一测试框架:在根目录统一安装和配置 Vitest/Jest,子包只写测试用例。
- 测试范围控制:使用
--changed或 affected 命令只运行受改动影响的测试。 - 测试分层:单元测试、集成测试、E2E 测试合理分布,形成测试金字塔。
- CI 集成:每次提交自动运行测试,失败阻止合并。
- 测试环境一致:通过 Docker 或统一的 CI Runner 保证环境一致。
评分维度:
- 能说出统一框架和范围控制(40%)
- 提到测试分层和 CI 集成(40%)
- 强调环境一致性(20%)
第 10 题
请说明 Monorepo 迁移前应做哪些评估,以及为什么建议渐进式迁移。
参考答案:
迁移前评估:
- 当前项目之间的依赖关系;
- 团队是否接受新的工作方式;
- 现有工具链是否支持 Monorepo;
- 迁移成本与收益。
渐进式迁移原因:
- 一次性迁移风险高,容易破坏现有构建和发布流程;
- 可以先用最相关、最依赖公共代码的几个项目试点;
- 便于逐步建立治理规范和工具链;
- 降低团队学习成本和业务中断风险。
评分维度:
- 能说出至少 3 个评估维度(40%)
- 说明渐进式迁移的优势(40%)
- 强调风险和成本(20%)
第 11 题
TypeScript Project References 在 Monorepo 中有什么作用?
参考答案:
TypeScript Project References 用于在 Monorepo 中建立包之间的编译依赖关系。
作用:
- 明确包的编译依赖顺序;
- 支持增量编译,只检查变化的文件;
- 避免每个子包独立全量类型检查,提高性能;
- 保证 Monorepo 内类型一致性。
启用方式:
- 被引用包设置
compilerOptions.composite: true; - 引用包通过
references声明依赖。
评分维度:
- 能解释 Project References 作用(40%)
- 提到增量编译和类型一致性(30%)
- 能说出
composite和references配置(30%)
第 12 题
Monorepo 中如何治理代码共享,避免循环依赖?
参考答案:
代码共享正确姿势:
- 把公共逻辑拆成独立的子包,如
packages/utils、packages/ui; - 明确包的职责边界,一个包只做一件事;
- 使用 workspace 协议引用内部包;
- 使用 TypeScript Project References 管理编译依赖。
避免循环依赖:
- 定期审查依赖关系图;
- 出现循环依赖时,把公共部分抽成第三个包;
- 使用依赖注入或事件机制解耦;
- 借助工具(如 Madge)检测循环依赖。
评分维度:
- 能说明公共包拆分和职责边界(40%)
- 能给出循环依赖解决方案(40%)
- 提到工具检测和 workspace 协议(20%)
第 13 题
请描述 Monorepo 中的 CI/CD 最佳实践。
参考答案:
- 只构建受影响项目:使用 Affected Commands 减少 CI 时间。
- 缓存依赖和构建产物:利用 Turborepo/Nx 的远程缓存。
- 统一代码规范:根目录配置 ESLint、Prettier,CI 中自动检查。
- 自动化版本发布:使用 Changesets 管理版本和 changelog。
- 分支保护和 CODEOWNERS:设置 main 分支保护和目录负责人。
- 并行测试:单元测试、集成测试、E2E 测试并行执行。
评分维度:
- 能说出至少 4 条最佳实践(60%)
- 能结合 Monorepo 特点说明原因(40%)
第 14 题
什么是固定版本(Fixed)和独立版本(Independent)?
参考答案:
-
固定版本(Fixed/Locked):所有子包统一版本号。每次发布时,所有包的版本一起升级。例如 Babel 的
7.x.x。- 优点:版本管理简单,发布节奏统一。
- 缺点:即使某个包没有变更,也要跟着升级版本。
-
独立版本(Independent):每个子包单独发版,根据各自的变更决定版本号。例如 Lerna 的 independent 模式。
- 优点:各包独立演进,版本语义清晰。
- 缺点:版本管理复杂,需要工具(如 Changesets)辅助。
评分维度:
- 能解释固定版本和独立版本(50%)
- 能分别说明优缺点(40%)
- 能举例说明(10%)
三、高级题
第 15 题
请设计一个中大型 Monorepo 的完整技术方案,包括依赖管理、任务调度、版本发布、测试和 CI/CD。
参考答案:
-
依赖管理:
- 使用 pnpm workspace;
- 通用开发依赖放根目录,运行时依赖放子包;
- 使用
workspace:*引用内部包; - 统一常用依赖版本,避免版本碎片化。
-
任务调度:
- 使用 Turborepo 配置 pipeline;
- 定义 build/test/lint 任务依赖;
- 启用本地和远程构建缓存。
-
版本发布:
- 使用 Changesets 管理变更;
- 结合 GitHub Actions 自动生成 Release PR;
- 自动 bump 版本、生成 changelog、发布到 npm。
-
测试:
- 根目录统一配置 Vitest;
- 单元测试、集成测试、E2E 测试分层;
- CI 中只运行 affected 测试。
-
CI/CD:
- PR 时运行 lint、type-check、test、build;
- 合并到 main 后触发发布流程;
- 使用 CODEOWNERS 和分支保护保证代码质量。
评分维度:
- 五个方面均有涉及(60%)
- 每个方面给出具体工具或命令(30%)
- 方案整体连贯、可落地(10%)
第 16 题
如何排查 Monorepo 中某个子包构建失败的问题?
参考答案:
排查步骤:
- 查看错误日志:定位具体报错信息和失败任务。
- 检查依赖关系:确认该子包依赖的其他包是否已正确构建。
- 单独构建:进入该子包目录单独运行 build,排除其他子包干扰。
- 检查版本冲突:确认是否存在多个子包依赖同一库的不同版本。
- 检查循环依赖:使用工具检测是否存在 A→B→A 的循环依赖。
- 清理缓存:清理
node_modules/.cache、Turborepo 缓存等,排除缓存污染。 - 审查最近变更:查看 git diff,确认是否有破坏性的配置或代码变更。
评分维度:
- 能给出系统化的排查步骤(60%)
- 提到依赖、缓存、循环依赖等关键点(30%)
- 强调日志分析和单独构建(10%)
第 17 题
Monorepo 中根目录统一依赖版本有哪些好处?可能带来什么风险?
参考答案:
好处:
- 避免不同子包依赖同一库的不同版本,减少运行时冲突;
- 减少打包冗余,避免同一依赖被多次打包;
- 统一升级,降低维护成本;
- 保持技术栈一致性。
风险:
- 某些子包可能需要特定版本,统一版本会导致兼容性问题;
- 升级时需要全量验证所有子包;
- 过度统一可能降低子包的灵活性。
平衡做法:
- 对核心依赖(如 React、TypeScript)统一版本;
- 对特殊依赖允许子包单独声明。
评分维度:
- 能说出至少 3 个好处(40%)
- 能说出至少 2 个风险(30%)
- 提出平衡策略(30%)
第 18 题
请解释远程缓存(Remote Caching)在 Monorepo 中的价值,以及可能的安全风险。
参考答案:
价值:
- 团队成员可以共享构建结果,避免重复构建;
- CI 中可以直接复用缓存,大幅缩短流水线时间;
- 大型 Monorepo 中效果尤为明显。
安全风险:
- 缓存被篡改可能导致构建产物不一致;
- 敏感信息可能意外被打包进缓存;
- 需要控制缓存读写权限,防止未授权访问。
应对措施:
- 使用可信的远程缓存服务;
- 对缓存进行签名和校验;
- 限制缓存访问权限;
- 避免在缓存中包含密钥等敏感信息。
评分维度:
- 能说明远程缓存价值(40%)
- 能指出缓存篡改、敏感信息泄露等风险(30%)
- 能给出应对措施(30%)
第 19 题
在 Monorepo 中,如何处理一个公共 API 的破坏性变更?
参考答案:
处理流程:
- 评估影响范围:通过依赖图找出所有使用该 API 的子包。
- 制定迁移计划:
- 如果影响大,考虑先新增 API,保留旧 API 作为兼容层;
- 逐步推动各子包迁移。
- 原子化提交:在一次提交中同时修改公共包和所有依赖子包,保证仓库始终处于可构建状态。
- Changeset 标记:用
major标记该公共包的破坏性变更。 - 文档和沟通:更新 README 和迁移文档,通知相关团队。
- 分阶段发布:必要时通过预发布版本验证。
评分维度:
- 能评估影响并制定迁移计划(40%)
- 提到原子化提交和兼容层(30%)
- 说明 Changeset 标记和文档沟通(30%)
第 20 题
请从工程化角度总结 Monorepo 成功的关键因素。
参考答案:
Monorepo 成功的关键因素:
- 清晰的包边界:每个子包职责单一,避免循环依赖。
- 统一的工具链:统一的构建、测试、lint、格式化配置。
- 自动化任务调度:使用 Turborepo/Nx 管理任务依赖和缓存。
- 规范的版本管理:使用 Changesets 管理版本和发布。
- 完善的 CI/CD:只构建受影响项目,保证主分支稳定。
- 良好的治理机制:CODEOWNERS、分支保护、定期审计。
- 团队共识:所有成员理解 Monorepo 的工作方式并遵守规范。
Monorepo 不是简单地把项目放到一个仓库,而是一套需要持续投入和治理的工程化体系。
评分维度:
- 能说出至少 5 个关键因素(60%)
- 强调治理和团队共识(30%)
- 总结 Monorepo 是系统工程而非简单合并(10%)
难度分布总结
| 难度 | 题号 | 数量 |
|---|---|---|
| 基础 | 1-7 | 7 |
| 进阶 | 8-14 | 7 |
| 高级 | 15-20 | 6 |
| 合计 | — | 20 |
领域编号:E02 包管理与 Monorepo
最后更新:2026-06-18