Rush StackShopBlogEvents
Skip to main content

为什么使用一个大仓库?!

开源的 NPM 包看起来是在许多小的 GitHub 仓库中开发的。听起来理所当然,对吧?

如果你正在构建毫无关系的组件,并且它们不适合整合在一起,当然可以。但是商业软件并不是这样的。它更像这样:

大多数人开始构建一个 web 应用,而不是一些库,当你的应用发布后,它的体积会逐渐增大, 直到有一天你需要在一些不同的仓库内共享一些代码,此时你发现代码已经乱成一窝,又需要花费时间重构!

显然,你必须将这些分割为可管理的组件。在 JavaScript 代码中,NPM 包是一种解决问题的方案。但查阅后才发现,NPM 似乎要求一个 GitHub 仓库对应一个 NPM 包。于是在一周乃至数周内,你创建了 10 个 Git 仓库来分割你的代码,并试着使用它们...

...但是使用 10 个 Git 仓库来管理代码后会出现一些让人头疼的问题:

  • 无暇顾及他人的工作:如果一个同事大部分时间在 5 号和 6 号仓库工作,他看起来完全忽略了来自其他 8 个仓库的 pull requests。每天都会有新的仓库出现,而你甚至却不知道它们的存在。

  • 层级式的发版: 从 lib3 发布一个修复到你的应用项目需要更新/构建/发布许多 Git 仓库,顺序是:lib3 --> lib2 --> lib1 --> application. 当 lib3 变动频繁时,上述过程会变的异常繁琐。人们如何记住正确的发版顺序?互联网上有地方可以询问这个问题,但是你人力有限,别人也很忙。

  • 下游的受害者:当 Bob 在 lib3 上做了一些改动并发布,所有的下游项目需要花费一定的时间才能升级并使用它。如果升级版本存在问题,那么可能是一周后 Alice 在 lib1 中尝试 "npm update" 时发现的。那时,Bob 可能动身去欧洲旅行了。为什么 Alice 要修复其他人升级导致的问题?貌似每次升级都会存在破坏性变动!

  • 疯狂的链接:为了直接测试 lib3 的变动,解决方法看起来是使用 npm link 将你的应用lib3 链接起来。但是 NPM 会创建一个全局范围内的符号连接,如果在同一台电脑上多个存在 lib3 多个分支,那么就会有问题。而且有 10+ 个库,很难记录哪两个库需要链接起来。

一个库对应一个包的方式对于陌生人之间维护孤立的项目是很有意义的(同样,这些库的更新频率也比较低,因此上述问题也更容易解决)。但是在我们的示例中,所有人都在同一个公司工作,这些库更多地扮演体系内的一个组件。代码会频繁的变动,某处的变动很可能破坏系统的其它部分。将多个项目整合起来构建,可以让你在每个变动中运行所有的单元测试,这样可以将修复问题的责任转移到最初更改代码的人身上。

于是,一个 Git 仓库一个团队的方式开始出现了,更贴切的描述是用尽可能少的 Git 仓库来完成工作

monorepo block diagram

Lots of people 许多开发大型业务软件的人,似乎最终都把所有代码放在一个大的 "monorepo" 中。JavaScript 是最后一个这么做的语言。

monorepo 策略下最大的担忧是显著的构建耗时。JavaScript 工具链明显比编译型语言慢,如果一个工程构建需要花费一分钟,那么假如你有 75 个工程,理论上构建将花费75 分钟。这看起来很吓人,但有了工业级的工具链,在构建耗时成为问题之前,你可以进行非常大的扩展。我们对Rush和gulp-core-build的大部分路线图都集中在构建耗时上,而且我们乐观的认为这里仍然有很大的优化空间。使用子集构建增量构建,理论上可以避免重建所有的东西,除非一个变化真的影响到所有的东西 ,—— 对于那种变化,为了能尽早发现故障而需要等待更长的构建时间,到底值不值得,这很难说。