All in one:项目级 monorepo 策略最佳实践

0. ???? 前言

在最近的项目开发中,出现了一个令我困扰的情况。我正在开发的项目 A,依赖了已经线上发布的项目 B,可是随着项目 A 的不断开发,又须要不时修改项目 B 的代码(这些修改暂时没必要发布线上),如何可以在修改项目 B 代码后及时将改动后在项目 A 中同步? 在项目 A 发布上线后,如何以一种优雅的方式解决项目 A,B 版本升级后的版本同步问题? 通过一番调研,我发现解决这些问题的最佳方案即是本篇要介绍的 monorepo 策略。前端

1. ???? 什么是 monorepo 策略?

monorepo 是一种将多个项目代码存储在一个仓库里的软件开发策略("mono" 来源于希腊语 μόνος 意味单个的,而 "repo",显而易见地,是 repository 的缩写)。将不一样的项目的代码放在同一个代码仓库中,这种「把鸡蛋放在同一个篮子里」的作法可能乍看之下有些奇怪,但实际上,这种代码管理方式有不少好处,不管是世界一流的互联网企业 Google,Facebook,仍是社区知名的开源项目团队 Babel (以下图)都使用了 monorepo 策略管理他们的代码。node

image-20210124225815627

<p style="text-align: center; color: #999;">babel 使用 monorepo 策略管理代码</p>git

使用 monorepo 策略究竟会给代码管理者和程序开发者带来哪些好处? 咱们又该如何在工做中尝试实践 monorepo 策略?这正是本文想要探讨的话题。但愿经过个人一番介绍,您可以对 monorepo 策略有更完整的认知,文章中介绍的工具和思想能够切实帮助到您和您所在的团队。github

2. ???? monorepo 策略的优劣

经过 monorepo 策略组织代码,您代码仓库的目录结构看起来会是这样:shell

.
├── lerna.json
├── package.json
└── packages/ # 这里将存放全部子 repo 目录
    ├── project_1/
    │   ├── index.js
    │   ├── node_modules/
    │   └── package.json
    ├── project_2/
    │   ├── index.js
    │   ├── node_module/
    │   └── package.json
    ...

乍看起来,所谓的 monorepo 策略就只是将不一样项目的目录聚集到一个目录之下,但实际上操做起来所要考虑的事情则远比看起来要复杂得多。经过分析使用 monorepo 策略的优劣,咱们能够更直观的感觉到这里面所隐晦涉及的知识点。npm

2.1 monorepo 方案的优点

  1. 代码重用将变得很是容易:因为全部的项目代码都集中于一个代码仓库,咱们将很容易抽离出各个项目共用的业务组件或工具,并经过 TypeScript,Lerna 或其余工具进行代码内引用;
  2. 依赖管理将变得很是简单:同理,因为项目之间的引用路径内化在同一个仓库之中,咱们很容易追踪当某个项目的代码修改后,会影响到其余哪些项目。经过使用一些工具,咱们将很容易地作到版本依赖管理和版本号自动升级;
  3. 代码重构将变得很是便捷:想一想到底是什么在阻止您进行代码重构,不少时候,缘由来自于「不肯定性」,您不肯定对某个项目的修改是否对于其余项目而言是「致命的」,出于对未知的恐惧,您会倾向于不重构代码,这将致使整个项目代码的腐烂度会以惊人的速度增加。而在 monorepo 策略的指导下,您可以明确知道您的代码的影响范围,而且可以对被影响的项目能够进行统一的测试,这会鼓励您不断优化代码;
  4. 它倡导了一种开放,透明,共享的组织文化,这有利于开发者成长,代码质量的提高:在 monorepo 策略下,每一个开发者都被鼓励去查看,修改他人的代码(只要有必要),同时,也会激起开发者维护代码,和编写单元测试的责任心(毕竟朋友来访以前,咱们从不介意本身的房子究竟有多乱),这将会造成一种良性的技术氛围,从而保障整个组织的代码质量。

2.2 monorepo 方案的劣势

  1. 项目粒度的权限管理变得很是复杂:不管是 Git 仍是其余 VCS 系统,在支持 monorepo 策略中项目粒度的权限管理上都没有使人满意的方案,这意味着 A 部门的 a 项目如果不想被 B 部门的开发者看到就很难了。(好在咱们能够将 monorepo 策略实践在「项目级」这个层次上,这才是咱们这篇文章的主题,咱们后面会再次明确它);
  2. 新员工的学习成本变高:不一样于一个项目一个代码仓库这种模式下,组织新人只要熟悉特定代码仓库下的代码逻辑,在 monorepo 策略下,新人可能不得不花更多精力来理清各个代码仓库之间的相互逻辑,固然这个成本能够经过新人文档的方式来解决,但维护文档的新鲜又须要消耗额外的人力;
  3. 对于公司级别的 monorepo 策略而言,须要专门的 VFS 系统,自动重构工具的支持:设想一下 Google 这样的企业是如何将十亿行的代码存储在一个仓库之中的?开发人员每次拉取代码须要等待多久?各个项目代码之间又如何实现权限管理,敏捷发布?任何简单的策略乘以足够的规模量级都会产生一个奇迹(无论是好是坏),对于中小企业而言,若是没有像 Google,Facebook 这样雄厚的人力资源,把全部项目代码放在同一个仓库里这个美好的愿望就只能是个空中楼阁。

2.3 小结:如何取舍?

没错,软件开发领域历来没有「银弹」。monorepo 策略也并不完美,而且,我在实践中发现,要想完美在组织中运用 monorepo 策略,所须要的不只是出色的编程技巧和耐心。团队日程,组织文化和我的影响力相互碰撞的最终结果才决定了想法最终是否能被实现。编程

可是请别灰心的太早,由于虽然让组织做出改变,统一施行 monorepo 策略困难重重,但这却并不意味着咱们须要完全跟 monorepo 策略说再见(不然我这篇文章就该到此为止了)。咱们还能够把 monorepo 策略实践在「项目」这个级别,即从逻辑上肯定项目与项目之间的关联性,而后把相关联的项目整合在同一个仓库下,一般状况下,咱们不会有太多相互关联的项目,这意味着咱们可以免费获得 monorepo 策略的全部好处,而且能够拒绝支付大型 monorepo 架构的利息。json

本文的剩余篇幅就是对「项目级别 monorepo 实践」的一些总结,即便您最终没有选择 monorepo 策略组织您的代码,相信文章中提供的一些工程化工具或思路也同样会对您产生帮助。bootstrap

3. ????????‍???? monorepo 方案实践

3.1 锁定环境:Volta

Volta 是一个 JavaScript 工具管理器,它可让咱们轻松地在项目中锁定 node,npm 和 yarn 的版本。你只需在安装完 Volta 后,在项目的根目录中执行 volta pin 命令,那么不管您当前使用的 node 或 npm(yarn)版本是什么,volta 都会自动切换为您指定的版本。babel

所以,除了使用 Docker 和显示在文档中声明 node 和 npm(yarn)的版本以外,您就有了另外一个锁定环境的强力工具。

并且相较于 nvm,Volta 还具备一个诱人的特性:当您项目的 CLI 工具与全局 CLI 工具不一致时,Volta 能够作到在项目根目录下自动识别,切换到项目指定的版本,这一切都是由 Volta 默默作到的,开发者没必要关心任何事情。

3.2 复用 packages:workspace

使用 monorepo 策略后,收益最大的两点是:

  1. 避免重复安装包,所以减小了磁盘空间的占用,并下降了构建时间;
  2. 内部代码能够彼此相互引用;

这两项好处所有均可以由一个成熟的包管理工具来完成,对前端开发而言,便是 yarn(1.0 以上)或 npm(7.0 以上)经过名为 workspaces 的特性实现的(⚠️ 注意,支持 workspaces 特性的 npm 目前依旧不是 TLS 版本)。

为了实现前面提到的两点收益,您须要在代码中作三件事:

  1. 调整目录结构,将相互关联的项目放置在同一个目录,推荐命名为 packages;
  2. 在项目根目录里的 package.json 文件中,设置 workspaces 属性,属性值为以前建立的目录;
  3. 一样,在 package.json 文件中,设置 private 属性为 true(为了不咱们误操做将仓库发布);

通过修改,您的项目目录看起来应该是这样:

.
├── package.json
└── packages/
    ├── @mono/project_1/ # 推荐使用 `@<项目名>/<子项目名>` 的方式命名
    │   ├── index.js
    │   └── package.json
    └── @mono/project_2/
        ├── index.js
        └── package.json

而当您在项目根目录中执行 npm install 或 yarn install 后,您会发如今项目根目录中出现了 node_modules 目录,而且该目录不只拥有全部子项目共用的 npm 包,还包含了咱们的子项目。所以,咱们能够在子项目中经过各类模块引入机制,像引入通常的 npm 模块同样引入其余子项目的代码。

请注意咱们对子项目的命名,统一以 @<repo_name>/ 开头,这是一种社区最佳实践,不只可让用户更容易了解整个应用的架构,也方便您在项目中更快捷的找到所需的子项目。

至此,咱们已经完成了 monorepo 策略的核心部分,实在是很容易不是吗?可是老话说「行百里者半九十」,距离优雅的搭建一个 monorepo 项目,咱们还有一些路要走。

3.3 统一配置:合并同类项 - Eslint,Typescript 与 Babel

您必定赞成,编写代码要遵循 DRY 原则(Don't Repeat Yourself 的缩写)。那么,理所固然地,咱们应该尽可能避免在多个子项目中放置重复的 eslintrc,tsconfig 等配置文件。幸运的是,Babel,Eslint 和 Typescript 都提供了相应的功能让咱们减小自我重复。

3.3.1 TypeScript

咱们能够在 packages 目录中放置 tsconfig.settting.json 文件,并在文件中定义通用的 ts 配置,而后,在每一个子项目中,咱们能够经过 extends 属性,引入通用配置,并设置 compilerOptions.composite 的值为 true,理想状况下,子项目中的 tsconfig 文件应该仅包含下述内容:

{
  "extends": "../tsconfig.setting.json", // 继承 packages 目录下通用配置
  "compilerOptions": {
    "composite": true, // 用于帮助 TypeScript 快速肯定引用工程的输出文件位置
    "outDir": "dist",
    "rootDir": "src"
  },
  "include": ["src"]
}

3.3.2 Eslint

对于 Eslint 配置文件,咱们也能够如法炮制,这样定义子项目的 .eslintrc 文件内容:

{
  "extends": "../../.eslintrc", // 注意这里的不一样
  "parserOptions": {
    "project": "tsconfig.json"
  }
}

注意到了吗,对于通用的 eslint 配置,咱们并无将其放置在 packages 目录中,而是放在整个项目的根目录下,这样作是由于一些编辑器插件只会在项目根目录寻找 .eslintrc 文件,所以为了咱们的项目可以保持良好的「开发环境一致性」,请务必将通用配置文件放置在项目的根目录中。

3.3.3 Babel

Babel 配置文件合并的方式与 TypeScript 一模一样,甚至更加简单,咱们只需在子项目中的 .babelrc 文件中这样声明便可:

{
  "extends": "../.babelrc"
}

当一切准备就绪后,咱们的项目目录应该大体呈以下所示的结构:

.
├── package.json
├── .eslintrc
└── packages/
    │   ├── tsconfig.settings.json
    │   ├── .babelrc
    ├── @mono/project_1/
    │   ├── index.js
    │   ├── .eslintrc
    │   ├── .babelrc
    │   ├── tsconfig.json
    │   └── package.json
    └───@mono/project_2/
        ├── index.js
        ├── .eslintrc
        ├── .babelrc
        ├── tsconfig.json
        └── package.json

3.4 统一命令脚本:scripty

在上一步中,咱们尽量的将全部配置文件进行抽象,从而精简了代码,并提升了整个项目的一致性。咱们的整个仓库也所以有了「更浓郁的 monorepo 风味 ☕️」。但若是仔细审视咱们的整个工程文件,还有一处存在着明显的瑕疵和一些恼人的坏味道,当您仔细审视您的众多 package.json 文件时,您就知道我在说什么了 -- scripts 脚本。

若是您的子项目足够多,您可能会发现,每一个 package.json 文件中的 scripts 属性都大同小异,而且一些 scripts 充斥着各类 Linux 语法,例如管道操做符,重定向或目录生成。重复带来低效,复杂则令人难以理解,这都是须要咱们解决的问题。

这里给出的解决方案是,使用 scripty 管理您的脚本命令,简单来讲,scripty 容许您将脚本命令定义在文件中,并在 package.json 文件中直接经过文件名来引用。这使咱们能够实现以下目的:

  1. 子项目间复用脚本命令;
  2. 像写代码同样编写脚本命令,不管它有多复杂,而在调用时,像调用函数同样调用;

经过使用 scripty 管理咱们的 monorepo 应用,目录结构看起来将会是这样:

.
├── package.json
├── .eslintrc
├── scirpts/ # 这里存放全部的脚本
│   │   ├── packages/ # 包级别脚本
│   │   │   ├── build.sh
│   │   │   └── test.sh
│   └───└── workspaces/ # 全局脚本
│           ├── build.sh
│           └── test.sh
└── packages/
    │   ├── tsconfig.settings.json
    │   ├── .babelrc
    ├── @mono/project_1/
    │   ├── index.js
    │   ├── .eslintrc
    │   ├── .babelrc
    │   ├── tsconfig.json
    │   └── package.json
    └── @mono/project_2/
        ├── index.js
        ├── .eslintrc
        ├── .babelrc
        ├── tsconfig.json
        └── package.json

注意,咱们脚本分为两类「package 级别」与「workspace 级别」,而且分别放在两个文件夹内。这样作的好处在于,咱们既能够在项目根目录执行全局脚本,也能够针对单个项目执行特定的脚本。

经过使用 scripty,子项目的 package.json 文件中的 scripts 属性将变得很是精简:

{
  ...
  "scripts": {
    "test": "scripty",
    "lint": "scripty",
    "build": "scripty"
  },
  "scripty": {
    "path": "../../scripts/packages" // 注意这里咱们指定了 scripty 的路径
  },
  ...
}

大功告成!???? 至此,咱们尽己所能地删除了整个项目中的重复代码,让整个项目变得干净,清爽而且有极强的复用性。

???? 小贴士:

别忘了使用 chmod -R u+x scripts 命令使全部的 shell 脚本具有可执行权限,也千万别忘了把这条贴士写在您的 README.md 文件中!

3.5 统一包管理:Lerna

<p style="text-align: center; color: #999;">图片来源:https://github.com/lerna/lerna</p>

我有时会感慨本身的灵感匮乏,怎么就想不到 Lerna 这样既有神话色彩又能自我释义的好名字。您能够大胆想象,九头龙的每只龙头都在帮您管理着一个子项目,而您只须要骑在龙身上发号施令的场景,这基本上就是咱们使用 Lerna 时的直观感觉。

这也是为何当咱们提起 monorepo 策略,就几乎不得不提到 Lerna 的缘由了,它的确提供了一种很是便捷的方式供咱们管理 monorepo 项目。当子项目越多时,Lerna 就越能显示其威力。

当多个子项目放在一个代码仓库,而且子项目之间又相互依赖时,咱们面临的棘手问题有两个:

  1. 若是咱们须要在多个子目录执行相同的命令,咱们须要手动进入各个目录,并执行命令;
  2. 当一个子项目更新后,咱们只能手动追踪依赖该项目的其余子项目,并升级其版本。

经过使用 Lerna,这些棘手的问题都将不复存在。

当在项目根目录使用 npx lerna init 初始化后,咱们的根目录会新增一个 lerna.json 文件,默认内容为:

{
  "packages": ["packages/*"],
  "version": "0.0.0"
}

让咱们稍稍改动这个文件,使其变为:

{
  "packages": ["packages/*"],
  "npmClient": "yarn",
  "version": "independent",
  "useWorkspaces": true,
}

能够注意到,咱们显示声明了咱们的包客户端(npmClient)为 yarn,而且让 Lerna 追踪咱们 workspaces 设置的目录,这样咱们就依旧保留了以前 workspaces 的全部特性(子项目引用和通用包提高)。

除此以外一个有趣的改动在于咱们将 version 属性指定为一个关键字 independent,这将告诉 lerna 应该将每一个子项目的版本号看做是相互独立的。当某个子项目代码更新后,运行 lerna publish 时,Lerna 将监听到代码变化的子项目并以交互式 CLI 方式让开发者决定须要升级的版本号,关联的子项目版本号不会自动升级,反之,当咱们填入固定的版本号时,则任一子项目的代码变更,都会致使全部子项目的版本号基于当前指定的版本号升级。

Lerna 提供了不少 CLI 命令以知足咱们的各类需求,但根据 2/8 法则,您应该首先关注如下这些命令:

  • lerna bootstrap:等同于 lerna link + yarn install,用于建立符合连接并安装依赖包;
  • lerna run:会像执行一个 for 循环同样,在全部子项目中执行 npm script 脚本,而且,它会很是智能的识别依赖关系,并从根依赖开始执行命令;
  • lerna exec:像 lerna run 同样,会按照依赖顺序执行命令,不一样的是,它能够执行任何命令,例如 shell 脚本;
  • lerna publish:发布代码有变更的 package,所以首先您须要在使用 Lerna 前使用 git commit 命令提交代码,好让 Lerna 有一个 baseline;
  • lerna add:将本地或远程的包做为依赖添加至当前的 monorepo 仓库中,该命令让 Lerna 能够识别并追踪包之间的依赖关系,所以很是重要;
# 向 @mono/project2 和 @mono/project3 中添加 @mono/project1
lerna add @mono/project1 '@mono/project{2,3}'

3.5.1 Lerna 高级命令

除了上面介绍到的经常使用命令外,Lerna 还提供了一些参数知足咱们更灵活的需求,例如:

  • --concurrency <number>:参数可使 Lerna 利用计算机上的多个核心,并发运行,从而提高构建速度;
  • --scope '@mono/{pkg1,pkg2}':--scope 参数能够指定 Lerna 命令的运行环境,经过使用该参数,Lerna 将再也不是一把梭的在全部仓库中执行命令,而是能够精准地在咱们所指定的仓库中执行命令,而且还支持示例中的模版语法;
  • --stream:该参数可以使咱们查看 Lerna 运行时的命令执行信息;

3.5.2 npm 包本地发布:Verdaccio

看到这里,您可能想要亲自体验一把使用 Lerna 管理/发布 monorepo 项目的感受。但是很快您会发现,将示例代码发布到真实世界的 npm 仓库并不是一个好主意,这多少有些使人沮丧,可是别担忧,您可使用 Verdaccio 在本地建立一个 npm 仓库做为代理,而后尽情体验 Lerna 的种种强大之处。

安装运行 Verdaccio 很是简单,您只需运行:

npm install --global verdaccio

在全局安装 Verdaccio 应用,而后在 shell 中输入:

verdaccio

便可经过 localhost:4837 访问您的本地代理 npm 仓库,别忘了在您的项目根目录建立 .npmrc 文件,并在文件中将 npm 仓库地址改写为您的本地代理地址:

registry="http://localhost:4873/"

大功告成 ????!每当您执行 lerna publish 时,子项目所构建成的 package 将会发布在本地 npm 仓库中,而当您执行 lerna bootstrap 时,Verdaccio 将会放行,让您成功从远程 npm 仓库中拉取相应的代码。

3.6 格式化 commit 信息

至此,咱们已经掌握了组织一个项目级 monorepo 仓库的全部前沿技巧,最后,让咱们看看最后一个能够优化的地方:代码提交时,约束 commit 信息。

一个 monorepo 仓库可能被不一样的开发者提交不一样子项目的代码,若是没有规范化的 commit 信息,在故障排查或版本回滚时毫无心外会遭遇灾难。所以,千万不要小看 commit 信息格式化的重要性(固然,一样重要的还有代码注释!)。

为了咱们可以一目了然的追踪每次代码变动的信息,咱们使用 commitlint 工具做为格式化 commit 信息的不二之选。

顾名思义,commitlint 能够帮助咱们检查提交的 commit 信息,它强制约束咱们的 commit 信息必须在开头附加指定类型,用于标示本次提交的大体意图,支持的类型关键字有:

  • feat:表示添加一个新特性;
  • chore:表示作了一些与特性和修复无关的「家务事」;
  • fix:表示修复了一个 Bug;
  • refactor:表示本次提交是由于重构了代码;
  • style:表示代码美化或格式化;
  • ...

我强烈建议您遵循该规范编写您的 commit 信息,不要偷懒,坚持下去,您的 git 日志将会显得整齐,有条理,富有表现力,同时,您也会收到同行的交口称赞,人人都会以和您这样优雅的工程师合做为荣。

除了限定 commit 信息类型外,commitlint 还支持(虽然不是必须的)显示指定咱们本次提交所对应的子项目名称。假如咱们有一个名为 @mono/project1 的子项目,咱们针对该项目提交的 commit 信息能够写为:

git commit -m "feat(project1): add a attractive button" # 注意,咱们省略了 @mono 的项目前缀

毫无疑问,这将会使咱们的 commit 信息更具表现力。

咱们能够经过下面的命令安装 commitlint 以及周边依赖:

npm i -D @commitlint/cli @commitlint/config-conventional @commitlint/config-lerna-scopes commitlint husky lerna-changelog

注意到了吗?我偷偷安装了 husky,它可以帮助咱们在提交 commit 信息时自动运行 commitlint 进行检查,但在这以前,咱们须要再在根目录下的 package.json 文件里加点料,像这样:

{
 ...
 "husky": {
    "hooks": {
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  }
 ...
}

为了可以让 commitlint 感知咱们的子项目名称,咱们还需在项目根目录中增长 commitlint.config.js 文件,并设置文件内容为:

module.exports = {
  extends: [
    "@commitlint/config-conventional",
    "@commitlint/config-lerna-scopes",
  ],
};

至此,咱们统一并规范化了 monorepo 项目的 commit 信息,终于整个 monorepo 工程化的最后一块拼图被咱们拼上了!

(顺便一提,您能够经过在命令行执行 echo "build(project1): change something" | npx commitlint 命令便可验证您的 commit 信息是否经过 commitlint 的检查。)

4. ???? 如何从 multirepo 迁移至使用 monorepo 策略?

至此,咱们学会了如何采用 monorepo 策略组织项目代码的最佳实践,或许您已经开始跃跃欲试想要尝试前文提到的种种技巧。从 0 搭建一个 monorepo 项目,固然没问题!但是若是要基于已有的项目,将其转化为一个使用 monorepo 策略的项目呢?

还记得吗?成百里者半九十,您还有一些坑要踩。不过好在您在这里还可以获得个人帮助,没必要客气!

或许您注意到了,Lerna 为咱们提供了 lerna import  命令,用来将咱们已有的包导入到 monorepo 仓库,而且还会保留该仓库的全部 commit 信息。然而实际上,该命令仅支持导入本地项目,而且不支持导入项目的分支和标签 ????。

那么若是咱们想要导入远程仓库,或是要获取某个分支或标签该怎么作呢?答案是使用 tomono,其内容是一个 shell 脚本。

使用 tomono 导入远程仓库,您所须要作的只有两件事:

  1. 建立一个包含全部须要导入 repo 地址的文本文件;
  2. 执行 shell 命令:cat repos.txt | ~/tomono/tomono.sh(这里咱们假定您的文本文件名为 repos.txt,且您将 tomono 下载在用户根目录;

repo 文件内容示例以下:

// 1. Git仓库地址  2. 子项目名称  3. 迁移后的路径
git@github.com/backend.git @mono/backend packages/backend
git@github.com/frontend.git @mono/frontend packages/frontend
git@github.com/mobile.git @mono/mobile packages/mobile

至此,咱们也掌握了将现有项目迁移至 monorepo 项目的方法。到这时候,您已绝非再是 monorepo 界的门外汉!

恭喜您 !!????

5. ???? 小结

在本篇文章中,咱们共同了解了「什么是 monorepo 策略」以及「monorepo 策略的优劣」,而且一块儿学习实践了 monorepo 策略的一些最佳实践。您必定也意识到,即便您的工做场景暂时没法实践 monorepo 策略,阅读本篇文章所学习到的种种方法,工具和思想也能够运用到您当下的工做之中。

固然,本文所介绍的这些方法和思想总有过期的一天,而且社区也从未中止对更好地实践 monorepo 策略的探索,说不定您过一阵子就会有更好的想法 ,填补某个领域的空白。但愿到时候您也能总结出一篇文章,为 JavaScript 社区贡献一份力量。到时候请千万别忘了回到个人评论区留言,让我分享您的成就。

关于 monorepo 这个主题,我就暂且带您探索到这里,后会有期:)

6. ???? 参考文献

  1. ???? JavaScript and TypeScript Monorepos
  2. ???? Why you should use a single repository for all your company’s projects
  3. ???? Advantages of monorepos
  4. ???? lerna管理前端packages的最佳实践
  5. ???? 基于lerna和yarn workspace的monorepo工做流
  6. ???? Monorepos in the Wild
  7. ???? Monorepos: Please don’t!
  8. ???? Monorepo: please do!
  9. ???? Introduction to Lerna
  10. ???? monorepo 迁移实践

7. ???? 扩展阅读

  1. 介绍实践 monorepo 生态:awesome-monorepo
  2. 一篇介绍 Google 如何将数十亿代码经过 monorepo 方式组织的论文:Why Google Stores Billions of Lines of Code in a Single Repository
  3. 一篇针对 Google 的调研报告,详尽地分析了 monorepo 的优劣: Advantages and Disadvantages of a Monolithic Repository

8. ???? 招聘信息

阿里巴巴淘系用户增加团队正在如饥似渴的寻找志同道合的伙伴,若是您准备好迎接适度的挑战,在让更多人喜欢手淘的同时,也让本身快速成长,欢迎您发送简历至个人邮箱:kongtang.lb@alibaba-inc.com,我十分期待收到您的讯息。

相关文章
相关标签/搜索