常名斋

[译] 跟着例子学 monorepo (一)

[译] 跟着例子学 monorepo (一)
2019-01-25 · 6 min read
Translate Program

原文链接: https://codeburst.io/monorepos-by-example-part-1-3a883b49047e

让我们通过实例来探索 monorepos.

首先,关于 monorepo:

Definitions vary, but we define a monorepo as follows:
The repository contains more than one logical project (e.g. an iOS client and a web-application)
These projects are most likely unrelated, loosely connected or can be connected by other means (e.g via dependency management tools)
The repository is large in many ways:

  • Number of commits
  • Number of branches and/or tags
  • Number of files tracked
  • Size of content tracked (as measured by looking at the .git directory of the repository)

Atlassian — Monorepos in Git.

其次,我也赞同这些反对 monorepo 项目的论据:

Monorepos in the Wild - Markus Oberlehner - Medium

接下来,我们将通过示例来讨论几个实际的问题,使用 lerna 来管理一个 monorepo 项目( javascript 项目 ) .

这个系列的最终 monorepo 在这里,点击查看.

脚手架

在我们处理 monprepo项目 ( 特别是lerna所管理的monorepo项目 ) 的问题时, 我们需要先建立一个 lerna monorepo 项目, 幸运的是这十分的简单.

首先, 全局安装lerna:

sudo npm install --global lerna

注意: 本教程使用的是nodejs v8.9.4 与 Lerna v2.9.0

创建一个新的文件夹, 并在其中运行一下命令将其转换为一个Lerna monorepo 项目.

git init
lerna init

最后生成的文件夹结构应该是这个样子的:

创建第一个package

现在,我们已经有了一个 Lerna monorepo 项目, 就可以开始尝试创建 package 了. 在部署到 npm 仓库中时, 每个package 将会是以单独的一个 npm package 的形式存在的. 通俗来说就是, package 允许更松散的耦合以及独立的依赖管理( 稍后会做介绍 ).

我们可以通过在 packages 文件夹下新建一个文件的形式来创建一个 package, 并在对应目录下执行以下命令 :

npm init -y

这时,项目文件夹结构会类似这样:

第三方依赖

假设我们三个项目都依赖于 npm 包 sillyname@0.0.3 ( 特定版本 ), 我们可以运行命令: ( Lerna 命令可以在项目下的任何文件夹中执行 )

lerna add sillyname@0.0.3

然后, 项目的目录结构将会是类似于这样的:

显然:

  • 因为我们希望每个 package 都是独立的,所以每个 package 都会更新自己的 package.json 具有对 sillyname 的依赖.
  • 注意,他生成了 package-lock.json ( 很像 yarn , npm 现在锁定了二级依赖 ). 另外, Lerna 文档中提到, 他也是支持 yarn 的.
  • 使用 Lerna 我们可以使用一条命令将依赖安装到每个 package 中.
  • 每个 package 都有着自己的 sillyname 副本, 安装缓慢同时也浪费磁盘.
  • 一种更高效的方式便是手动更新每个包的 package.json 然后在父文件夹的 node_modules 中安装 sillyname ( nodejs 是会在父文件夹中查找依赖的 ), 不过这是令人乏味的.

对此, Lerna 也有着解决方法; 使用 hoist 参数

lerna add sillyname@0.0.3 --hoist

最后的项目文件夹结构将会是这样的 (截图中并没有展示庞大的 node_modules 文件夹) :

显然:

  • 每个 package.json 都将 sillyname 列为依赖项.
  • sillyname 安装在了项目根目录的 node_modules 文件夹中
  • node_modules 中的其他软件包是因为 Lerna 是软件的开发依赖.

有选择的升级

假如我们只想为 grocery 这个 package 升级 sillyname 的版本, 我们可以执行以下命令:

lerna add sillyname@0.1.0 --scope=grocery

然后, 项目的目录结构将会是类似于这样的:

显然:

  • grocery 的 package.json 会更新为依赖 sillyname@0.1.0
  • grocery 文件夹下会有一个 node_modules, 其中包含了新版本的 sillyname

内部依赖

现在, 加入我们希望 grocery 去依赖 apple 和 banana, 我们可以执行以下命令:

lerna add apple banana --scope=grocery

最后的项目文件夹结构将会是这样的

显然:

  • grocery 的 package.json 已更新, 依赖于 apple 和 banana.
  • Lerna 在 grocery 的 node_modules 文件夹中创建了软连接, 运行 grocery 透明的访问 apple 和 banana 中的文件.

相关代码

现在, 我们已经设置好了相关依赖, 可以开始编写一些代码了.

packages/apple/index.js

const sillyname = require('sillyname');
module.exports = `apple and ${sillyname()}`;

packages/banana/index.js

const sillyname = require('sillyname');
module.exports = `banana and ${sillyname()}`;

packages/apple/grocery.js

const sillyname = require('sillyname');
const apple = require('apple');
const banana = require('banana');
console.log(`grocery and ${sillyname()}`);
console.log(apple);
console.log(banana);

然后在 grocery 的目录下运行一下命令:

node index.js

最后的输出会是这样的:

grocery and Linenhiss Butterfly
apple and Trailspeaker Scribe
banana and Translucentpuma Kangaroo

完成编码后, 我们添加以下文件, 因为我们不希望将 node_modules 文件夹存储在源代码管理中.

.gitignore

**/node_modules

然后将所有的文件提交到 git 仓库之中.

Bootstrap

现在, 假如团队的另一名成员要加入开发. 首先, 他需要全局安装 Lerna.

sudo npm install --global lerna

然后, 他需要克隆仓库, 然后运行对应的名来安装所有依赖 ( 包括符号链接 ):

lerna bootstrap --hoist

现在, 这么成员就可以开始编码了. 遵循他们自己的工作流模式 (创建新分支等等).

下一步

在下一篇文章Monorepos By Example:Part 2 ( 未翻译 )中,我们将继续探讨一些更实际的问题。

至大无外,至小无内。