什么是Monorepo
“单仓”: 将多个功能独立的项目放到一个git仓库中进行管理;实现更大程度的代码共享、更便捷的项目开发、管理、测试、发布等流程。本篇文章将主要介绍在前端工程中monorepo的应用。
前端中的Monorepo具备哪些能力
1、减少重复安装的依赖
2、更方便的开发流程: 当模块的功能需要更改时,直接在代码中进行修改,之后直接本地完成功能测试
减少重复依赖安装的方案: workspace(前端)
工作区(workspaces)是一个通用术语,指的是 npm cli 中的一组功能,它支持从单个顶级根包中管理本地文件系统中的多个包。
这个功能让我们在本地开发包,尤其是多个互相依赖的包时更加得心应手。它可以避免我们再手动的去执行
npm link命令,而是在npm install的时候,会自动把workspace下面的合法包,自动创建符号链接到根目录的node_modules里。
npm workspace
npm中的workspace方案在npm v7开始才有的。
通用命令:
// 初始化工作区中的一个项目
npm init -w ./packages/temp1
// 为工作区中的某个项目安装依赖
npm install abbrev -w temp1(项目的package属性值)
安装依赖时,默认会安装在顶层node_modules下,如果遇到版本冲突时,会根据项目的package.json文件中的版本号,决定是否在项目中创建node_modules;
上图中的temp1种abbrev的版本与全局安装的版本不一致,所有会在temp1种创建一个node_modules文件夹。
// 执行scripts
// 指定项目
npm run test --workspace=temp1
// 在所有项目中执行
npm run test --workspaces
yarn workspace
yarn workspace与npm workspace基本一致,最大的区别是在于yarn和npm的区别上; yarn workspace的基本使用:
- yarn workspace没有类似npm init -w ./packages/a的命令,所以需要自己在package.json中添加必要的字段:
常用命令:
// 依赖安装
// 指定项目
yarn workspace worspaceName add/remove package
// 整个工作区(顶层)
yarn add/remove package -W
// 执行scripts
// 指定项目
yarn workspace workspaceName run <command>
// 在所有项目中执行
npm workspaces run test
yarn/npm workspace存在的问题
“幽灵依赖”
“幽灵依赖”指: 可以在项目中使用本没有安装的依赖;出现这个现象的原因是yarn/npm在处理安装的npm包时,针对node_modules采用了拍平的方案:
上图中,我只在顶层安装了axios,但是因为node_modules是拍平的,导致axios的依赖form-data之类的npm包也被放在了顶层,由于nodejs的require逻辑,导致我们可以在项目中引用form-data这个包,尽管我并没有安装他。
幽灵依赖导致了一个问题,那就是我们没办法控制自己引用的包的版本,如果某次axios中的form-data版本发生了大变化,那很有可能导致我们的项目无法继续稳定的运行。
“分身依赖”
“分身依赖”: monorepo项目中存在的重复安装的npm包;
上图中的temp1、temp2、temp3都依赖了axios,但是temp1中axios的版本与temp2、temp3中的不同,然而temp2和temp3中的axios版本是相同的。但是,在npm workspace中,被提升到顶层的axios并不是被使用了更多次的0.27.2;而是先出现的temp1项目中的0.27.1;这就导致,在temp2和temp3中,同一版本的axios被安装了两次。yarn workspace也会出现这种情况,只不过提升公共npm包的策略有所不同,但是这并不会改变分身依赖存在的现状。
pnpm workspace
介绍pnpm workspace之前,需要先简单介绍一下pnpm;pnpm(performance npm)。pnpm的核心思想是:将所有项目需要安装的npm包统一安装到磁盘的一个位置,之后通过硬连接的方式将其链接到项目的node_modules中;
- 如果你用到了某依赖项的不同版本,只会将不同版本间有差异的文件添加到仓库。 例如,如果某个包有100个文件,而它的新版本只改变了其中1个文件。那么
pnpm update时只会向存储中心额外添加1个新文件,而不会因为仅仅一个文件的改变复制整新版本包的内容。- 所有文件都会存储在硬盘上的某一位置。 当软件包被被安装时,包里的文件会硬链接到这一位置,而不会占用额外的磁盘空间。 这允许你跨项目地共享同一版本的依赖。
简单来讲,pnpm可以解决掉yarn和npm中存在的幽灵依赖、分身依赖的问题。
上图中,三个子项目中分别安装了axios 0.27.2、0.27.1、0.27.1。所有的npm包通过硬链接的方式链接到顶层node_modules下的.pnpm文件夹下,之后,在该子项目中通过软链接的方式将对应的npm包链接到子项目的node_modules中。借助.pnpm文件夹解决了幽灵依赖的问题,通过将所有的npm包装到全局的.pnpm-store文件夹下解决了分身依赖的问题。
常用命令:
// 依赖安装
// 指定项目
pnpm i package --filter 项目名
// 整个工作区(顶层)
pnpm i package
// 执行scripts
// 指定项目
pnpm run <command> --filter 项目名
// 在所有项目中执行
pnpm run <command>
更方便的开发流程
无论是yarn、npm还是pnpm的workspace,都只是能实现工作区内多项目的依赖共享,解决的只是开发开始的问题,但是针对与开发结束的测试、版本号管理、发布等流程只能采用其他的工具。下面将会简单的介绍三种:
lerna
Lerna 是一个管理工具,用于管理包含多个软件包(package)的 JavaScript 项目。
使用方式:
1、全局安装lerna: npm i lerna -g
2、使用lerna init初始化一个monorepo项目
二、常用命令:
// 为所有package安装依赖,并将公共依赖提取到顶层目录下的node_modules中:
lerna bootstrap --hoist
// 执行scripts
// 会执行所有package的<command>命令
lerna run <command>
// 统一管理各个package的版本号
lerna version
// 发布npm包: 需要准确设置author字段
lerna publish
lerna可以使用npm/yarn来进行依赖的安装,因此,lerna中存在幽灵依赖、分身依赖
rush.js
Rush: 一个可伸缩的 web monorepo 管理器;具备以下几个功能:
-
子集构建和增量构建
-
循环依赖:当一个库间接依赖自己的旧版本时,处于循环中的项目会使用最后一个发布的版本,而其他项目仍然会获得最新的版本。
-
批量发布:发布时,Rush 可以检测哪些包发生了变动,同时会自动的提高相应的版本号,并在每个文件夹那执行 npm publish。
-
跟踪更新日志:日志整合
-
企业级政策:Rush 可以让多人开发和多项目混合时保持一致的生态。
基本使用
- 初始化:
rush init - 常用命令:
// 安装依赖
rush update
rush install
// 指定项目安装依赖
rush add --package 项目名
// 打包所有项目
rush rebuild => 在每一个项目下执行 npm run build
rush build 可增量构建
// 打包特定的项目,需进入特定项目目录下
rushx => npm run build
// 检查项目中是否存在不同的依赖版本
rush check
// 自动生成changelog
rush change
// 发布包
rush publish --apply --target-branch targetBranch --publish
rush 可以选择npm、yarn以及pnpm作为workspace的管理器,但是,使用rush时,你不可以直接使用npm/yarn/pnpm的指令,一切的操作,都建议采用rush自己的命令来完成。
除此之外,还有一个工具[:turborepo](Documentation | Turborepo);turborepo具备rush中类似增量构建的功能,同时拥抱pnpm;但是他还没有一个版本管理、npm包发布、changelog生成的功能,所以可以集成lerna使用。