Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Monorepo 策略与方案选型小记 #24

Open
FE-Sadhu opened this issue Aug 18, 2021 · 0 comments
Open

Monorepo 策略与方案选型小记 #24

FE-Sadhu opened this issue Aug 18, 2021 · 0 comments

Comments

@FE-Sadhu
Copy link
Owner

由于最近负责抽象公司项目模块化成 SDK,故预研了 Monorepo 、Lerna、yarn workspaces 管理多模块的方案,记一笔调研结论。

方案调研

采用维基百科的解释:

In version control systems, a monorepo ("mono" meaning 'single' and "repo" being short for 'repository') is a software development strategy where code for many projects is stored in the same repository.

也就是说 Monorepo 是版本管理系统中的一种“开发策略”,宏观上说就是单 repository 管理多 projects(packages) 。

采用 Monorepo 管理多模块的优势以及与单 package 单 repo 的对比:

  1. 利于跨模块 (package) 调试
    若单模块 A 就是一个 repo,要 npm link 给别的 repo 调试,且 A 源码修改了,得重新 npm link 一次。 Monorepo 的话,则是动态引用的各模块。
  2. 利于各模块的版本发布管理
    各模块统一发布,有一个依赖模块改变了,会自动改变依赖与被依赖模块的版本号,且可以选择统一|分别控制版本号的处理。(Fixed | Independent mode)
    单模块单 repo 的话,改变了依赖模块 A,手动修改版本号发布后,得手动去找被依赖模块且改版本号然后发布。repo 越多越麻烦。
  3. 利于各模块的依赖管理
    不会装各 packages 间的重复依赖,所有依赖提升到根目录。(导致的缺点就是只调试一个包也要装全部依赖)
  4. 方便生成 CHANGELOG,管理 issue、pr
    因为就一个 repo,commit 可以借工具规范生成 changelog 。

缺点就是一个 repo 的体积过大。但相对以上前三点来说,可以接受。

根据调研,一个理想的 monorepo 结构:

.
├── packages
│      ├─ module-a
│      │    ├─ src            # 模块 a 的源码
│      │    └─ package.json   # 自动生成的,仅模块 a 的依赖
│      └─ module-b
│           ├─ src            # 模块 b 的源码
│           └─ package.json   # 自动生成的,仅模块 b 的依赖
├── tsconfig.json             # 配置文件,对整个项目生效
├── .eslintrc                 # 配置文件,对整个项目生效
├── node_modules              # 整个项目只有一个外层 node_modules
└── package.json              # 包含整个项目所有依赖

所有全局配置文件只有一个,这样不会导致 IDE 遇到子文件夹中的配置文件,导致全局配置失效或异常。

node_modules 也只有一个,既保证了项目依赖的一致性,又避免了依赖被重复安装,节省空间的同时还提高了安装速度。

兄弟模块之间通过模块 package.json 定义的 name 相互引用,保证模块之间的独立性,但又不需要真正发布或安装这个模块,通过 tsconfig.json 的 paths 与 webpack 的 alias 共同实现虚拟模块路径的效果。

再结合 Lerna 根据联动发布功能,使每个子模块都可以独立发布。

Lerna 是业界知名度最高的 Monorepo 管理工具,功能完整。

再根据对别的团队 Monorepo 方案调研结果,决定采用 yarn workspaces + lerna 方案管理,也是 yarn 官方推荐的方案,核心是:使用 yarn workspaces 来管理依赖,使用 lerna 来管理 npm 包的版本发布。

Lerna + yarn workspaces

以下是自己建 demo repo 跟着文档尝试后,小记的常用指令及含义。 也建议亲自折腾下体会更深。

Combine yarn workspaces and Lerna

// root -> lerna.json
{
  "npmClient": "yarn",
  "useWorkspaces": true
}
// root -> package.json
{
  "workspaces": [
    "packages/*"
  ],
}

因 lerna 本身就是基于 yarn 、npm、git 开发,所以在利用 lerna 构建好项目后开启 yarn workspace 功能仅作如上配置即可。

Lerna

  1. lerna init
    创建 lerna 仓库,默认 Fixed 模式。 lerna init --independent 可以创建 Independent 模式。

Fixed 模式 -> 所有 package 版本一致
Independent 模式 -> 可以独立控制各个 package 版本

  1. lerna create xxx
    创建新模块

  2. lerna add <package>[@version] [--dev] [--exact] [--peer]
    假如有两个 package-1, package-2,。
    lerna add babel , 该命令会在package-1和package-2下安装babel
    lerna add react --scope=package-1 ,该命令会在package-1下安装react
    lerna add package-2 --scope=package-1,该命令会在package-1下安装package-2 (软链)

  3. lerna bootstrap | yarn install
    安装所有依赖项并链接所有的交叉依赖

  4. lerna exec
    在 packages 中对应包下的执行任意命令。
    如要执行 package-A 下的 yarn start
    lerna exec --scope package-A -- yarn start
    如果不带 --scope package,则默认在根目录执行,如
    lerna exec -- rm -rf ./node_modules

  5. lerna run --scope my-component test
    执行 my-component 下的 npm scripts test

  6. lerna ls
    查看 packages
    lerna list --json 带路径一起查

  7. lerna changed/updated/diff
    查出待 publish 的 packages
    diff 的话会可视化修改
    不主动 git commit | tag 的话,lerna 不会检测到,lerna 底层就是基于 git npm 开发的

  8. lerna clean
    删除 packages 下的 node_modules
    (lerna clean 不会删除项目最外层的根 node_modules)

  9. lerna publish
    发布 packages 到 npm 仓库,发包前需要登录 npm 账号,否则会上传 git 成功,上传 npm 失败。
    (package.json 中的 ”private“: true 不会发布)

publish 内部做得事情:

  1. 运行 lerna updated 来决定哪一个包需要被 publish
  2. 如果有必要,将会更新 lerna.json 中的 version (Fixed 模式)
  3. 将所有更新过的的包中的 package.json 的 version 字段更新
  4. 将所有更新过的包中的依赖更新
  5. 为新版本创建一个 git commit 或 tag
  6. 将包 publish 到 npm 上

该命令也有许多的参数,例如 --skip-git 将不会创建 git commit 或 tag,--skip-npm 将不会把包 publish 到 npm 上。

yarn workspaces

  1. yarn install
    lerna bootstrap 效果一致,会自动帮忙解决安装和 link 问题

  2. yarn workspaces info
    各 package 依赖树关系

  3. 安装 | 删除依赖
    i. 给某个 package 安装 | 删除依赖:
    yarn workspace packageB add packageA@xx.xx.xx 将 packageA 作为 packageB 的依赖进行安装,如果想要不同 package 间的 link,必须明确指定版本号。
    yarn workspace packageB add -D react
    删除: yarn workspace packageB remove packageA
    ii. 给根目录 安装 | 删除依赖(适用所有 packages):
    yarn add -W -D commitizen root package 安装 commitizen
    yarn remove -W commitizen root package 移除 commitizen

  4. 执行 scripts
    运行 packageA 的 dev 命令: yarn workspace packageA dev
    每个工作区运行命令: yarn workspaces run xxx

最佳实践

除了以上 ,最佳实践决定参考 vivo 团队实现淘系团队实现,之后开发过程中,再根据项目自身情况进行调整。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant