本文为翻译
原文标题:Why one big repo⁈
原文作者:Rush official doc
开源 NPM 包似乎都是在多个小型 Github repo(repository,存储库,通常简称 repo) 里开发的。我不该这么做吗?
当然,如果你正在构建独立的组件,那么不同组件间协作的形式对于你来说就没有那么重要了。但是商业软件可不是以这种形式工作的。商业软件更像是这样:
大多数人从开发一个单独的 Web 应用程序开始,而不是一堆库。在应用程序发布之后,它的代码量会持续增长。然后,有一天你需要在另一个的项目中共享一些代码,你就会意识到这项目已经成了一个老鼠窝(杂乱不堪)。是时候重构了!
显然,你必须把项目分解成容易控制处理的组件。JavaScript 的 NPM 包 就是这么做的。简单思考一下,这个惯例似乎是说 “每个 NPM 包都使用一个 repo” 。你像个战神一样工作一两周,创建了 10 个 Git repo,拆分代码,然后尝试代码是否能正常工作…
…但是同时维护 10 个 Git repo 会带来巨大的痛苦!有太多令人头痛的问题了:
- 视野狭窄(Tunnel vision):如果你的同事基本只在 5号 和 6号 repo 上工作,他们可能会完全忽略掉其余 8 个 repo 的 pull request。如果哪天有新的 repo 出现,你甚至可能毫不知情。
- 级联发布:将 lib3 的 Bug 修复 传递到 你的应用程序项目 需要 以正确的顺序 更新/打包/发布 很多 Git repo:lib3 –> lib2 –> lib1 –> 应用程序 。当 lib3 频繁的变动时,这个过程就会变得相当乏味了。人们怎么才能记住发布正确的顺序呢?你倒是可以把这个问题抛给互联网上的一些机构,而不是你繁忙且有限的人手。
- 下游的受害者:当 张三 发布了 lib3 的更改,所有的下游项目不一定会立即升级使用。如果这个改动是一个 回归缺陷 ,可能 李四 一周之后才会在 lib1 中执行 “npm update” 并 发现存在问题。到那时,可能 张三 已经背上背包去欧洲旅游了。为什么 李四 需要承担修复别人 回归缺陷 的精神负担?可能以后他每次升级的时候,都会害怕项目会出故障。
- 疯狂的链接:刚刚提到的问题存在应对方案,可以使用 npm link 来直接将 lib3 符号连接(symlink) 到你的 应用程序 来测试。但是 NPM 会通过一个全局文件夹创建符号连接,如果你需要在同一台笔记本上处理 lib3 的多个分支,就会遇到麻烦。而且处理 10 个以上的库,那你会很难记住是 什么 符号连接了 什么。
“一个软件包一个 repo” 的模式对于那些 由协作能力偏弱的陌生人们维护的 独立项目 是有意义的。(另外,大多数库的更新频率相当低,这也使得解决问题更容易)然而在我们的例子中,人们都在一家公司工作,“库” 更像是整体架构中的一个组成部分。代码总是容易发生变动,系统中的一部分改动可以轻易破坏另一个部分。一起构建多个项目会迫使你为每个修改 运行全量的单元测试,这就将修复Bug的责任转移到了适合的地方:到了最初引入变更的人那里。
新的原则就变成了 “一个团队一个Git repo” , 或者更进一步 “使用尽可能少的 Git repo 来完成工作”。
大部分构建大型商业软件的人最终都会选择将所有的代码放在一个大型的 “monorepo” 中。JavaScript 只不过在最后才加入这股潮流。
这种策略最大的问题显然就是 构建时间 。JavaScript 工具的运行速度要慢于编译语言。假设一个项目需要 1 分钟 来构建,那么你有 75 个项目,理论上你需要荒谬的 75 分钟的构建时间。这似乎很吓人,但如果使用一个工业级的工具链,在构建时间成为一个问题之前,你可以将项目扩展得相当远。我们对 Rush 和 gulp-core-build 的后续规划都会聚焦在 构建时间上,而且我们乐观地认为依然有大量的优化空间。通过 子集(subset)/增量 构建,理论上你可以避免重新构建所有项目,除非你真的有一个改动影响到了所有项目 — 而对于这种改动,如果能够帮助你在早期发现缺陷,也许更长的构建时间也是值得的。