19年,团队沉淀了组件库、图表库、工具库等基础建设相关内容。上述的内容均为独立工程维护,起初我们采用 Git Subtree npm install <folder>
来关联各个项目,带来了开发、调试的便利,同时也带了一些复杂性。
11月份,整个底层稳定性显著提高,宿主项目中调试等已不是主要问题;我们的新成员 fusion-utils 诞生,由于 fusion-charts 和 fusion-components 同时需要依赖 fusion-utils,使得依赖冗余&冲突,以及多个独立仓库提交繁琐等问题凸显出来。因此,我们再出发,选择了 monorepo 。
选型 monorepo 后,并不是无脑的按照其指导来全部切换,为了更平稳过度,我们规划了以下几步:
- 第一步:采用
yarn workspace
来解决依赖问题(npm install <folder>
就此落幕) - 第二步:深度利用
peerDependencies
等,来处理依赖版本问题 - 第三步:结合
package.json
中 bin字段,利用yarn link
,创建 node 交互式命令行。使用 Commander 开发了大量 CLI,来简化提交、构建等问题 - 第四步:多仓合一仓(已论证…)
monorepo
Monorepo is a unified source code repository used by an organisation to host as much of its code as possible.
Monorepo
它是一种管理 organisation 代码的方式,在这种方式下会摒弃原先一个 module 一个 repo 的方式,取而代之的是把所有的 modules 都放在一个 repo 内来管理。-- 这是宗旨,目前团队已尝试,但是否全线切换,仍待考量(因为 依赖问题 已解决,提交复杂性也已通过 CLI 统一)
目前诸如 Babel、React、Angular、Ember、Meteor、Jest 等等都采用了 Monorepo 方式来进行源码的管理。
monorepo 最终目标:将所有相关 module
都放到一个 repo
里,每个 module
独立发布,issue 和 PR 都集中到该 repo 中。不需要手动去维护每个包的依赖关系,当发布时,会自动更新相关包的版本号,并自动发布。
优点:
- 单一(统一)的校验、构建、测试和发布流程
- 模块之间的修改、测试更便捷
- 维护统一的 Issues 地址
- 更容易设置开发环境
缺点:
- 代码库体量更大
- 不能直接从 Github 安装模块 https://github.com/npm/npm/issues/2974
- monorepo 会产生大量的 commit、branch、tag、git 追踪的文件也会增多。从而导致:
- commit 增多:
git log
git blame
变慢 - refs 增多:
git clone
git branch
git push
变慢 - tracked 文件增多:
git status
git commit
变慢
- commit 增多:
google、Facebook 花费了大量时间在 monorepo 上。但,也有反对者,强力建议不要使用 monorepo,https://medium.com/@mattklein123/monorepos-please-dont-e9a279be011b
yarn workspace
只需运行一次 yarn install
就可以一次安装所有软件包,其具有 hoist 提升功能。
- 依赖关系可以链接在一起,这意味着可以相互依赖,同时始终使用最新的可用代码。比
yarn link
更好的机制,因为它只影响工作区树而不是整个系统(yarn link
会在全局/usr/local/bin
中增加相关记录,[见下述](###yarn link) - 所有的项目依赖项将一起安装,从而为 Yarn 提供了更大的自由度来更好地对其进行优化 – hoist
{
"private": true,
"workspaces": [
"src/charts",
"src/components",
"src/fusion-utils"
]
}
注意:private:true
是必需的!以确保意外地暴露它们。
这里,src/charts
、src/components
、src/fusion-utils
都是独立的工程,通过 Git Subtree
来关联这些项目,然后每个项目中都有独立的 package.json 文件,在宿主项目中使用 yarn install
统一安装即可。
{
"name": "fusion-charts",
"version": "1.2.0",
"private": true,
"description": "基于 Vue 和 ECharts 封装的图表组件",
"main": "./index.js",
"module": "./index.js",
"dependencies": {
"echarts": "^4.2.0-rc.2"
},
"devDependencies": {
"vue-template-compiler": "^2.6.10"
},
"peerDependencies": {
"fusion-utils": "^1.0.0",
"vue": "^2.6.10"
}
}
安装完成,具有类似的文件层次结构:
代码语言:javascript复制/package.json
/yarn.lock
/node_modules
/node_modules/echarts
/node_modules/vue
/node_modules/vue-template-compiler
/node_modules/fusion-charts -> src/charts
/node_modules/fusion-components -> src/components
/node_modules/fusion-utils -> src/fusion-utils
/src/charts/package.json
/src/components/package.json
/src/fusion-utils/package.json
echarts、vue 等均安装到了根目录下。代码中对于 fusion-charts 等引用要使用 /workspace-a/package.json#name
字段(上述,name 字段为 fusion-charts),而不是文件夹名称 charts。import {...} from 'fusion-charts'
hoist
独立项目
为了减少冗余,大多数程序包管理器采用某种提升方案将所有相关模块尽可能地提取并展平到一个集中位置。依赖关系树可能像这样:
能够消除重复的 A@1.0
和 B@1.0
,同时保留版本差异(B@2.0
)。通过从项目根目录遍历 “node_modules” 树,大多数模块 crawlers/loaders/bundlers 可以非常有效地定位模块。
monorepo 项目
通过将子模块提升到其父项目的node_modules:monorepo/node_modules
来在子项目/程序包之间共享模块。解决了相互依赖时的冗余度(如,fusion-charts
、fusion-components
都要引用 fusion-utils
)。
依赖丢失?
至此,可以从项目的根 node_modules
访问所有模块,但我们通常会在其本地项目中构建每个程序包,这些模块在其自己的 node_modules
下可能不可见。
- 在项目根目录 “monorepo” 中找不到模块 “B@2.0”(无法遵循符号链接 – symlink)
- “package-1” 中找不到模块
A@1.0
(不知道上面 “monorepo” 中的模块树)
为了使这个 monorepo 项目能够从任何地方可靠地找到任何模块,它需要遍历每个 “node_modules” 树:monorepo/nodemodules
和 monorepo/packages/package-1/node_modules
。
nohoist
禁止将选定的模块提升到项目根目录
代码语言:javascript复制"workspaces": {
"packages": ["packages/*"],
"nohoist": ["**/react-native", "**/react-native/**"]
}
yarn link
创建一个 nodejs 命令行包
cli.js
代码语言:javascript复制#!/usr/bin/env node
告诉*nix系统,我们的 JavaScript 文件的解释器应该是 /usr/bin/env
节点
现在我们可以在 Linux 或 Mac OS X 上以 ./cli.js
或在 Windows 中使用 node cli.js
来运行它
代码语言:javascript复制
package.json
bin 是一个让 Yarn 在包安装时给包创建 cli 命令(二进制)的映射表。
"bin": {
"fusion-smartV-build-cli": "./bin/cli.js"
}
yarn/npm link
命令允许我们在本地 “symlink a package folder”,它将在本地安装 package.json
的 bin 字段中列出的任何命令。根目录下直接执行 yarn link
即可。
yarn link
一个包可以链接到另一个项目
- 在你想连接的包里,运行
yarn link
- 使用
yarn link [package]
来链接另一个你想在当前项目里使用的本地包
$ cd project1
$ yarn link
$ cd project2
$ yarn link project1
这会创建一个符号链接 project2/node_modules/project1
连接到你本地的project1
项目副本
peerDependencies
代码语言:javascript复制
peerDependencies
的目的是提示宿主环境去安装满足插件peerDependencies所指定依赖的包,然后在插件import或者require所依赖的包的时候,永远都是引用宿主环境统一安装的npm包,最终解决插件与所依赖包不一致的问题。
"peerDependencies": {
"fusion-utils": "^1.0.0",
"vue": "^2.6.10"
}
它要求宿主环境安装 fusion-utils^@1.0.0
和 vue@2.6.10
的版本。组件中引入的 fusion-utils 和 vue 包其实都是宿主环境提供的依赖包。
参考地址
- https://juejin.im/entry/586f00bc128fe100580a6f78
- https://www.toptal.com/front-end/guide-to-monorepos
- https://yarn.bootcss.com/docs/workspaces/
- https://medium.com/netscape/a-guide-to-create-a-nodejs-command-line-package-c2166ad0452e