Jonathan Saring 原作,授权 New Frontend 翻译。
如何在采用多个仓库、monorepo、微服务的项目间复用代码,关于这个话题的讨论最近越来越多。
跨项目、跨仓库复用代码是达成更好的模块化、更快开发的关键因素,但也很复杂。我以前根据我们团队的经验写过一篇文章。
本文盘点了跨项目复用前端代码的 5 种方法。别忘了,代码复用归根结底是一个关于人际沟通的文化问题,最重要的是不忘模块化这一初心。
1. Bit
Bit(GitHub)是一个流行的 JS 工具,同时管理代码变更和组件依赖,让团队内部跨项目复用代码更方便。
Bit 支持在多个仓库间分离和复用代码,统一控制变更,因此有利于在团队内部增进代码复用,减少代码复用的额外负担。
Bit 支持无缝分离、复用任何仓库中的模块,自动定义相应的环境和依赖树。无需重构,即刻发布来自任意仓库的组件。
发布组件之后,可以通过 npm/yarn 在其他项目中安装组件,也可以在其他项目中直接基于 Bit 导入、修改代码。变更代码后,可以通过 Bit 更新版本、合并变更。
Bit 既是一个开源项目,也是一个组件平台,帮助团队将可复用的代码单元转换为共用组件。
Bit(bit.dev)也提供了发现、协作的平台,支持组织、测试、构建、渲染等功能。
2. NPM with / without Lerna
npm 是 JavaScript 生态的一大亮点,通过复用模块和库开启了复用代码和协作开发的大门。尽管人们对这一生态系统多有抱怨,却无法想象没有 npm 的日子该怎么过。
你多半已经对 npm 有所了解,因此这里重点讨论 npm 的一些限制以及相应的应对措施。开始行动之前进行明智的决策能为以后省下时间。
首先,为多个软件包配置和维护多个仓库可能很困难。
所以有些项目采用「多软件包仓库」(也叫 monorepo)。
Bit、Lerna(详见下文)之类的工具可以帮助我们将项目转换为多软件包仓库。
你可以浏览这篇文章了解详情。
这也导致许多团队分发多个小组件时采用了公共库方案,因为每个小组件发布一个软件包比较麻烦。
其次,使用其他人发布的库限制了你改动的能力,通常你需要给软件包的仓库提一个 pull request。像 Bit 这样的工具可以让你引入任意仓库的组件,修改后发布新版本,从而缓解这一问题。
第三,项目扩张时存在可发现性问题。在大量细碎的软件包中寻找、选择组件很困难,常常需要访问大量 wiki 站点和长篇文档。正如 Rollup 作者 Rich Harris 所说:
为了缓解在 npm 中寻找需要的包这一难题,人们写下了大量博客文章(划掉,创建了大量完整网站)……你需要自行评估软件库:写了测试吗?代码易于理解吗?维护是否活跃?文档易于查找和参考吗?
Lerna
不同的包放不同的仓库,这样做很快就会超出掌控范围,很难在项目间更新变更。
Lerna可以帮助你在单个仓库中管理、配置多个软件包,统一构建和测试流程,从而减少不同的包放不同的仓库带来的麻烦。这样你就不用为不同的软件包配置、维护不同的仓库了。
可以参考下面这篇文章了解更多关于 monorepo 的信息:Monorepos Made Simpler
3. 公共库
公共库的优势在于可以把所有需要复用的代码放在一个仓库里,这样比采用多个小软件包更容易维护和分发。和 Lerna monorepo 的区别在于,公共库会作为一个软件包使用。
把所有需要复用的代码放在一个仓库里有一个问题,使用时需要将整个公共库引入项目,会引入冗余的代码、依赖,也会增加项目的大小和复杂度。
这也导致更新和修改非常笨拙,任何变更都需要项目所有者更新整个软件包。这阻碍了在组织内部采用公共库。公共库内组件的可发现性也不好。
这些问题导致 Lodash 之类的社区花了很长时间和很多精力将组件拆成单独软件包发布到 NPM 上。Google 的 Polymer 项目(由 Eric Bidelman 等开源巨擘主导)也把超过一百个 web 元素放在超过一百个仓库里。
Lerna 可以将公共库仓库中的组件拆成多个软件包。Bit 可以用来分发公共库中的组件。
下面两篇文章可以参考:
4. Git sub-module
submodule 实际上是从父仓库的目录树中独立出来的仓库,与父仓库的唯一联系是 submodule 签出的 SHA 值,这个 SHA 值存在父仓库的提交中。这个储存的 SHA 值的变动不会自动反映在 submodule 中。
然而,submodule 有许许多多问题。直接 Google「git submodules」,就能在搜索结果中看到很多 submodule 的问题。submodule 不会管理模块间的依赖关系,这是它的主要问题。还有其他一些问题,比如在父目录下 pull
不会自动更新 submodule。
另外,git 在解决冲突时不会保存 submodule 的指针。也就是说,如果没有手动更新指针,合并更改时会丢失已解决的冲突。在 submodule 提交后,父仓库中的 submodule 会是一个 detached head,因为它指向旧 head,而不是当前的 head。你猜怎么着?在父仓库或者 submodule 下 push,不会发布其他父仓库的改动。
有各种工具为 submodule 添加了额外的自动化,比如 git-subtree、gitslave、braid、giternal。不过,目前而言,跨项目、跨仓库的 JS 模块,唯一能够同时管理源码变更和依赖关系的工具是 Bit。
5. 复制粘贴代码
因为让我们面对现实吧,未来的我又为我做过什么呢?
说实话,在实践中,这也许是世界上最常用的代码复用方案。我想,在大多数情况下,这是因为缺少「廉价的」、有效的替代方案,以及混乱的交付周期。
问题是,代码重复一点也不廉价。远非如此。
代码重复就像不断增长的债务,很快会让项目失控,让项目维护成为噩梦,让交付周期越来越长。许多问题只有在交付生产环境后才会被发现。
最近的一项研究表明,GitHub 上有一半的代码是重复的。比如,在一万个仓库中,is-string 这个 JavaScript 函数的一百多种不同的实现被重复了一千次。
想一想,如果有更多的人愿意复用代码,而不是复制或重新实现代码,省下的时间可以实现多少新功能。想想代码重复给你的组织增加的维护成本和加长的交付周期。
代码复用以人为本
每个项目和每个开发者在乎的事情、使用的工具和流程不尽相同。但复用代码仍是真正实现模块化的关键。在当今的生态系统中,代码复用提供了非常大的优势,正变得越来越流行。
我们创建了 Bit。无论你选择什么样的方法和工具,重要的是鼓励一种倡导共享和协作的文化。
毕竟,项目之间的共享从人与人之间的共享开始。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。