本文将站在一个实习生的角度,分享笔者在 LogicFlow 中的实习经验和对于所遇问题的一些分析解决思路。本篇文章并不是把实习总结一条条的罗列出来,而是把实习的经历和技术文章结合到一起,讲述实习生是如何在开源项目中快速成长的,与技术文章相比,阅读起来相对轻松。
前言
往前数算恰好一整年,当时大三的我刚刚加入春招大军,正在一场场面试中因被面试官拒绝而怀疑自我,又在一次次被拒后的自我反思中重拾自信,过了两个多月才在春招的末尾找到了第一份实习工作,随即跨越半个中国来到深圳的一家银行类公司,跟着师兄做了一些零零散散的小项目。在第一份实习中,最主要的收获仅仅是感受到了职场与校园之间的一些差异,而在技术上,由于受到项目体量的限制,成长速度较为缓慢。第一份实习接近尾声时恰逢秋招伊始,或许是受益于第一段实习的经历,我的简历和面试都获得了不错的反馈,最终从手里的 offer 中选择了滴滴,校招也就此顺利收官。
与滴滴签约后,想着早些熟悉一下以后的同事和项目,我提前来到北京实习。入职第一天,中午吃饭时从师兄口中得知,团队正在做一个开源项目(LogicFlow),我实习阶段的主要工作就是协助他们做一些开发。
听到开源项目的时候,我的内心一颤,在潜意识里,开源项目开发难度大,且对开发者的技术水平要求高,自觉有些难以胜任,同时又觉得异常幸运,从来没有想到可以在实习阶段赶上“造火箭”,未来会有大量的机遇推动我去学习新的知识,就这样带着一整年中最大的幸运开始了自己的实习生活。
启动项目
刚入职时,组内大佬们就已经完成了 LogicFlow 的核心功能以及对应文档,此时的 LogicFlow 还未开源,我的起步工作是为文档添加可操作的示例。
初次见面
拿到内部仓库的访问权限后,我把代码下载到本地,粗略浏览了一下项目目录的大致结构:
LogicFlow
├── docs // 文档
├── examples // 示例
├── packages
│ ├── core // 核心包
│ │ ├── src
│ │ └── package.json
│ ├── extension // 拓展包
│ │ ├── src
│ │ └── package.json
│ └── site // 调试示例
│ ├── src
│ └── package.json
├── .commitlintrc.js
├── babel.config.js
├── eslintrc.js
├── .gitignore
├── lerna.json
├── package.json
├── developer.md
├── README.md
└── yarn.lock
看完目录后我获取到了以下主要信息:
- LogicFlow 的文档在 docs 目录下
- 我要开发的文档示例在 examples 目录下
- packages 目录中包含了 LogicFlow 的核心包、拓展包、示例站点
- 一个从来没有见过的文件 - lerna.json
- 开发者上手指引 - developer.md
- LogicFlow 项目介绍 - README.md
产生疑惑
看完目录后确定了下一步要做的事情:跟着 developer.md 为项目安装依赖。
从 developer.md 中可以得知安装依赖的命令:
npm run bootstrap
执行完成之后,LogicFlow 的根目录和 examples、core、extension、site 目录下都多了一个 node_modules 文件夹,此时我产生了几个疑问:
- 为什么 LogicFlow 要在一个项目中存放多个项目?
- npm run bootstrap 命令是如何做到同时为多个项目安装依赖的?
好奇心驱使我查看了 npm run bootstrap 中的原始命令,在根目录的 package.json 中,bootstrap 是这样定义的:
{
"scripts": {
"bootstrap": "yarn && lerna bootstrap",
}
}
很明显,npm run bootstrap
先通过 yarn 根据根目录的 package.json 为项目安装了全局依赖,然后通过 lerna bootstrap 为 examples、core、extension 和 site 各自安装了依赖。
分析盲点(monorepo 与 lerna)
到目前为止,我可以确定 LogicFlow 使用 lerna 这个东西对项目进行了工程化管理,lerna 支持了一个项目中可以存放多个子项目,同时 lerna 对我来说是一个新的知识盲点。
带着疑惑,找到了 lerna 的代码仓库,在官方 README.md 中,我快速捕捉了以下关键信息:
- lerna 是一个 JavaScript 项目的管理工具,这些项目内部可以包含多个包
- 这种一个项目包含多个包的形式被称为 multi-package repository(或者 monorepo)
同时找到了 lerna 的核心命令 lerna bootstrap,它主要完成了以下内容:
- 在项目的每个包的目录下执行 npm install,为其安装外部依赖
- 为相互依赖的两个包之间建立软链(symlink)
软链关系如下:
/node_modules
/examples
└── node_modules
├── @logicflow/core --> /packages/core // 软链
└── @logicflow/extension --> /packages/extension // 软链
/packages
├── core
│ └── node_modules
├── extension
│ └── node_modules
│ └── @logicflow/core --> /packages/core // 软链
└── site
└── node_modules
├── @logicflow/core --> /packages/core // 软链
└── @logicflow/extension -> /packages/extension // 软链
现在就可以解释之前的几个疑问了:
- npm run bootstrap 命令是如何做到同时为多个项目安装依赖的?
- npm run bootstrap 在每个包目录下执行了 npm install
- 为什么 LogicFlow 要在一个项目中存放多个项目?
- extension 依赖于 core,而 examples 和 site 依赖于 extension + core,将它们放到一个项目中可以实现同时开发、同时发版,且 lerna 的 symlink 可以保证各个包直接依赖于其他包在本地的最新代码
- 根目录下那个从来没有见过的文件 lerna.json 是干啥的?
- 是 lerna 的配置文件,可以告诉 lerna 项目中存在哪些 packages
成功启动
已经知道,各个包之间可以直接引用本地的最新代码进行开发,现在我们应该在启动项目之前,先把各个包构建一下以便其他包引用,如 developer.md 中的构建命令:
# 先构建类型
# LogicFlow 使用 TS 进行开发,不同的包之间存在类型依赖
npm run build:types
# 构建源码
npm run build
在根目录执行完上面两个命令之后,就可以进入 examples 目录进行开发调试了:
cd examples
npm run start
工具优化
到了 LogicFlow 开源时,项目的多包管理形式发生了新的变化。在 LogicFlow 正式发布的过程中,师兄添加了一个 yarn workspaces 的功能,这同样又触及到了我的知识盲区。
新的疑问(workspaces)
有了 yarn workspaces 之后,package.json 发生了变更:
{
"workspaces": ["packages/*", "examples"],
"scripts": {
"bootstrap": "yarn",
}
}
在 package.json 中新增了一个 workspaces 的字段,而 npm run bootstrap 命令只通过执行 yarn 来安装依赖。安装依赖之后,与添加 yarn workspace 之前相比,唯一不同的点来自于各个包的 node_modules 内部,即各个包中如果有相同的依赖,那么这些依赖会被提取到根目录下的 node_modules 中,而整个项目在本地的体积就会大大缩小。
例如,在 core、extension、examples、site 这四个包中都使用了 @babel/core 这个工具,那么 @babel/core 就会被提取到根目录下的 node_modules 中,而那四个包的 node_modules 目录就缩减了下来。
/node_modules
├── @babel/core
├── @logicflow/core --> /packages/core // 软链
└── @logicflow/extension --> /packages/extension // 软链
/examples
└── node_modules
/packages
├── core
│ └── node_modules
├── extension
│ └── node_modules
└── site
└── node_modules
与添加 yarn workspaces 之前相比,最重要的一点是软链(symlink)的功能保存了下来,到此为止,这个 yarn workspaces 又让我产生了新的疑惑:
- 既然 yarn workspaces 和 lerna 都可以为各个包安装依赖并添加软链,那么这两者的区别是什么?
- 为什么有了 yarn workspaces 之后,仍然要保留 lerna?
带着这几个疑问,又开始了新的探索。
workspaces 与 lerna 的对比
同样的,我找到了 yarn 对 workspaces 的介绍,文档中表示,yarn workspaces 能够完成以下任务:
- 可以为本地相互依赖的包之间创建软链(symlink)
- 将各个包之间的依赖安装到一起,放到根目录下
- 各个包使用同一个 yarn.lock 文件,以减少冲突,方便查看
也就是说,yarn workspaces 同样也是一个 monorepo 的管理工具,但是它所提供的功能仅仅与 lerna 提供的底层功能一样,为本地相互依赖的包建立软链,并在此基础上进行优化,将各个包的共同依赖抽离出来到根目录。
yarn workspaces 除了提供 lerna 的底层功能以外,其他高级功能均不支持,这就是为什么有了 yarn workspaces 之后,LogicFlow 仍然保留 lerna 的原因:
- 使用 yarn workspaces 来代替 lerna bootstrap 安装依赖
- 仍然使用 lerna 所提供的各种高级功能,例如:
- 使用 lerna version 为各个包的变更代码添加 tag
- 使用 lerna publish 来同时发布多个包
所以,针对于 monorepo 的形式,我们可以同时使用 workspaces 和 lerna 来进行项目管理。另外,在 npm 7.x 版本中也已经支持了 workspaces 功能。
思考总结
从刚加入 LogicFlow 时,项目就已经开始使用 monorepo 的管理形式了,那么就会有对应的疑问,LogicFlow 为什么要用 monorepo 呢,它的优点在哪里,它有没有什么缺点呢?
monorepo 的优点
以 LogicFlow 为例,我总结了 monorepo 为项目带来的一些优点:
便于代码引用
monorepo 可以保证 extension 直接引用到本地 core 的最新代码,如果是 multirepo 的形式,需要先经过 core 的发包,然后在 extension 中下载使用。
monorepo 可以在开发过程中省去对 core 包的频繁发版与安装。
便于依赖管理
monorepo 可以一次为多个包安装依赖,如果是 multirepo,需要为每个项目都安装一次依赖。
使用前文中的 workspaces,还可以将相同的依赖从各个包中抽离出来,减少整个项目在本地的体积。
便于快速发版
在 LogicFlow 中,extension 依赖于 core 的代码,当 core 发生变更后,extension 常常也要随之改变,为了保证这两个包之间的依赖关系清晰稳定,LogicFlow 始终保持两者的版本号一致,这就需要同步地快速发版,monorepo 可以保证同时对多个包进行操作。
monorepo 的缺点
实际上,monorepo 的优点基本是由各种工具在 monorepo 的基础上带来的,其 monorepo 本身也存在缺点。
管理成本
在 monorepo 中进行多个包的开发、联调、上线时,仍然需要执行多次打包或上线的命令,例如,当 LogicFlow 的 core 和 extension 发生变动后,对应的 examples 也会变化,这就需要先分别对 core 和 extension 进行打包,然后供 examples 的开发使用,所以 monorepo 的每一个动作包含了多个项目的步骤,使得管理成本上升。
解决方案:引入 lerna 来管理 monorepo,可以实现对多个包的同时构建或上线等。
项目体积变大
当所有的包都放到一个项目里,安装依赖之后整个项目的体积会变大,多个相似的 node_modules 会使得项目体积飙升。
解决方案:使用 workspaces 将不同包中相同的依赖安装到根目录下,以便依赖的复用。
commits 易混乱
在一个 git repository 中修改多个包的代码,会导致多个包的 commits 掺杂在一起,在查看 commit log 的过程中会增加分析成本。
解决方案:规范 commit 格式,在 commit 中注明改动所属的包,例如:fix(core): something。
访问权限控制缺失
多个包放到同一个仓库中意味着它们的访问权限必须是一致的,对于 LogicFlow 这种开源项目并没有影响,但当某些项目拥有不同访问权限的包时,monorepo 并不适用。
梦的开始
每个程序员都会懂得,职业生涯中能够有一个好的项目作为磨练自己的机会是非常难得的,开源项目脱离了业务需求中逻辑的重复性和复杂性,保留了对行业内各种前沿技术的应用,是让开发者快速成长的好机会。能够有机会加入 LogicFlow,对我这个实习生而言是非常幸运的,如同做梦一样,在 LogicFlow 中所遇到的每一个疑问和难题,都像是有位老师在指引我前行。
最后
本篇文章以一个实习生的视角,讲述了在第一次面对开源项目 LogicFlow 时所遇到的“新概念” - monorepo,并分享了笔者对于 monorepo 学习和理解,monorepo 本身瑕不掩瑜,是开源项目可以考虑使用的一种包的管理形式,在后面的实习阶段,笔者还会将其他所遇到的有意思的新挑战分享给大家。
更多阅读资料:
- LogicFlow github:github.com/didi/LogicF…
- 官方文档:logic-flow.org/LogicFlow
- 设计原理总述:juejin.cn/post/693341…
- LogicFlow 拓展机制:juejin.cn/post/693831…
- 边的绘制与交互:juejin.cn/post/694272…
- 添加微信号进用户群:logic-flow