背景
前端的发展很快,自从node.js的出现,打开前端新的大门,npm让js有了自己的包管理能力,能够让前端项目工程化,从而能够处理更加复杂的前端项目。
但是随之而来的是,同一个项目的npm依赖越来越多,有些是开源的,有些是自研的,尤其在同一个团队,当你开发一个新的npm包的时候,只是为了服务特定几个项目,但是这几个项目不在你管理范围内,当你需要更新的时候需要通知到他们,有时候会出现版本依赖问题,然后反复沟通和测试,最终达到协调。但是下次更新的时候又再次遇到这个问题,重复一次。
npm依赖
npm是什么
npm,Node Package Manager的缩写,也就是“Node的包管理器”。 npm(“Node 包管理器”)是 JavaScript 运行时 Node.js 的默认程序包管理器。
通俗的说,npm是管理js包的工具,它包括以下几个部分:
- npm源,存放各类npm包的网站,可以注册,上传npm包
- npm-cli 命令工具,允许你自由下载任何npm包
npm如何管理包
那么npm是如何管理包的呢?主要项目下package.json
对npm进行描述,主要有以下几个属性:
- name:JavaScript 项目或库的名称。
- version:项目的版本。
- scripts: 当作在项目本地运行的命令行工具
- dependencies:当项目被人依赖的时候,需要安装的npm包,描述:
npm包名: npm包版本
- devDependencies:本地开发的时候,需要安装的npm包,描述:
npm包名: npm包版本
同时package-lock.json
文件描述了 npm JavaScript 项目中使用的依赖项的确切版本,确保下次安装项目依赖的npm包升级版本后导致项目无法运行(这个是很多新手安装npm包的时候会遇到的一个错误)。
npm版本
npm包版本遵循major.minor.patch
版本模型规范,什么是major.minor.patch
版本模型规范,下面引用一下说明:
APR版本规范,major是当前的主版本号,minor则是次版本号,patch对应的则是APR的补丁号,同时还有版本所处阶段
base
,alpha
,beta
,RC
,release
。
因此在package.json描述的依赖npm包的版本号,如:~1.0.0
,^1.0.0
,几个符号代表的意思如下:
^
:表示最新的次版本,例如,^1.0.4
可能会安装主版本系列1
的最新次版本1.3.0
。〜
:表示最新的补丁程序版本,与^
类似,〜1.0.4
可能会安装次版本系列1.0
的最新次版本1.0.7
。
npm包版本依赖问题
即使有package-lock.json
或者yarn.lock
等约束文件解决项目依赖包的版本的问题,但是当项目越来越庞大,拆分的公共npm包越来越多,这些npm管理难度和数量成正比提升,团队需要面临npm包版本问题也越来越多,尤其当微前端
概念提出,当大项目被拆分成N个子项目的时候,团队成员需要面临以下几个问题:
- 公共包npm更新了,如何通知到其他子项目依赖的npm包更新版本
- 子项目有些公共的代码需要抽象到公共一起维护,是抽离成npm包,但是又需要新起一个项目
- 公共npm越来越多,子项目也越拆越多,如何管理这些项目的开发、测试和持续发布
- 其他各种开发细节问题...
因此,大家迫切需要去一个标准去统一维护管理这些项目,因此Monorepo
统一项目管理规范,就是我们的最佳选择。
Monorepo
是什么
Monorepo是包含多个不同项目的单一存储库,具有明确定义的关系。
通俗的讲,Monorepo
就是将几个不同项目放到一个git仓库里,通过制定一个大家遵循的管理规范和子项目之间依赖关系。
Monorepo
大仓项目与monolithic
单体巨石项目还是有很大的区别,下面简单说明:
- 1.Monorepo !== 单体巨石项目,monorepos 简化了代码共享和跨项目重构,它们显着降低了创建库、微服务和微前端的成本,不需要做到一起发布
- 2.能够有效解决子项目与子项目的之间版本依赖关系,而不是单纯将几个项目放在一起形成一堆代码山
为了更好的了解Monorepo
大仓,可以和对立面Polyrepo
多个标准式项目 作对比:
monorepo 工具
Monorepos 有很多优势,但要使它们发挥作用,您需要拥有合适的工具。随着工作空间的扩大,工具必须帮助您保持快速、易于理解和管理。
市面上主流的Monorepo
管理工具有:
- Bazel(谷歌)
- Gradle Build Tool(Gradle, Inc)
- Lage(微软)
- Lerna
- Nx(Nrwl)
- Pants(Pants Build 社区)
- Rush(由 Microsoft)
- Turborepo(由 Vercel)
工具应该具备以下几点能力:
- 1.本地计算缓存,能够提供本地构建缓存、单元测试缓存等能力
- 2.本地任务编排,能够以正确的顺序并行运行任务
- 3.分布式计算缓存,跨不同环境共享缓存工件的能力。这意味着你的整个组织,包括 CI 代理,永远不会构建或测试相同的东西两次。
- 4分布式任务执行,在多台机器上分发命令的能力,同时在很大程度上保留在单台机器上运行它的开发人体工程学。
- 5.透明远程执行,在本地开发时在多台机器上执行任何命令的能力。
- 6.检测受影响的项目/包,确定更改可能会影响什么,以仅运行构建/测试受影响的项目。
- 7.工作区分析,无需额外配置即可理解工作区项目图的能力。
- 依赖图可视化,可视化项目和/或任务之间的依赖关系。可视化是交互式的,这意味着您可以搜索、过滤、隐藏、聚焦/突出显示和查询图中的节点。
- 8.源码分享,促进分散的源代码片段的共享。
- 9.一致的工具,无论您使用什么来开发项目,如JavaScript 框架、Go、Rust、Java 等,工具都可以帮助您获得一致的体验
- 10.代码生成,本机支持生成代码
- 11.项目限制和可见性,支持定义规则以限制 repo 中的依赖关系。例如,开发人员可以将某些项目标记为他们团队的私有项目,这样其他人就无法依赖它们。开发人员还可以根据使用的技术(例如 React 或 Nest.js)标记项目,并确保后端项目不会导入前端项目。
monorepo单一原则
单一原则指的是单一版本(One Version)原则,具体定义如下:
单一版本(One Version)原则,是指在任意时间,代码库内的每一份组件、每一个依赖只有一个版本。
对内部库而言,这意味着使用主干开发(见下),并且必须在主干 HEAD 上依赖。这是一个非常强的约束——这意味着除了终端制品,任何一个内部被依赖的库都不能通过分支发布,而必须保持自己在单仓的主干上一直是发布状态。
对外部依赖而言,同一个第三方库在单仓中永远只会引入一个版本。
为什么?
因为原来的git flow开发流,从feature->dev->master,每个分支里面的依赖版本都可能会不一样,从而导致依赖版本难以维护。
monorepo挑战
- 并非所有服务都适用于 monorepos
- 需要更复杂的 CI 设置
- 需要考虑代码架构大规模的改变
monorepo实战
作为前端开发者,Lerna框架是肯定要尝试一番,同时功能比较齐全的Nx框架要去体验一番。
但是其实两者的关系非常紧密,都是同一个公司Nrwl
开发的,所以有很多类似点。
Lerna
快速开始安装
npx lerna init
PS: npx是什么?
- npx:Node Package Execute 即node包执行器
- npx 是npm v5.2.0版本之后随npm 一起打包安装的一个包执行器。
- 它会自动去寻找二进制命令文件且不必全局安装依赖包。
- npx 可以在不指定项目中的确切位置或使用别名的情况下运行正确版本的工具,比如
npx lerna init
命令会执行去npm源安装lerna-cli
命令工具到本地,然后执行lerna init
命令
安装完以后,项目的初始化架构如下:
packages/ header/ src/ ... package.json rollup.config.json jest.config.js footer/ src/ ... package.json rollup.config.json jest.config.js remixapp/ app/ ... public/ package.json remix.config.js package.json lerna.json # 需要自己手动新建, 用来描述
修改package.json
,去添加一个npm/yarn/pnpm workspace
{ "name": "root", "private": true, "workspaces": ["packages/*"], "devDependencies": { "lerna": "6.0.1" } }
常用的命令
lerna run xxx
: 统一执行所有子项目的scripts命令,如:lerna run buildnpx nx graph
:查看项目依赖图npx lerna add-caching
:设置子项目的一些缓存设置,会在根目录下生成nx.json
npx lerna publish --no-private
: 统一发布npm包npx lerna run xxx --scope=header
:允许只针对某个子项目header执行命令
其他命令可以到官网查看, Lerna命令
NX
官网里定义是:
NX是一个智能、快速和可扩展的构建系统,具有一流的Monorepo支持和强大的集成。
实战步骤如下:
创建一个新工作区:
npx create-nx-workspace@latest package-based --preset=npm
然后在packages
创建自己的子项目:
package-based/ ├── packages/ │ └── is-even/ │ ├── index.ts │ └── package.json │ └── is-odd/ │ ├── index.ts │ └── package.json ├── nx.json └── package.json
接下来就是配置 nx.json
,子项目之间的依赖,已经任务执行顺序:
// nx.json { ... "targetDefaults": { "build": { "dependsOn": ["^build"] } } }
执行构建命令npx nx build is-odd
可以看到要先构建is-event
项目
最后是构建全部项目npx nx run-many --target=build
CI/CD构建流程改造
这个按照各自团队的CI/CD构建流程去改造,但是主要有以下几点:
- CI/CD流水线职责分离
- 统一镜像NPM凭证管理
- 手动触发CD流水线发布,使用统一版本进行管理发布