You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Workspaces are a new way to set up your package architecture that’s available by default starting from Yarn 1.0. It allows you to setup multiple packages in such a way that you only need to run yarn install once to install all of them in a single pass.
那么 yarn workspaces 能给我们带来什么好处呢?
所有的依赖都会被下载到根目录下的 node_modules,同时,工作区的所有包都会被 link 到根目录下的 node_modules。而你只需要在根目录 yarn or yarn install 即可;
PS:目前笔者正准备开发一个开源工具,预计会拆分为多个包,于是准备使用
monorepo
模式来进行项目管理,本文的内容是笔者实践所得,如有不足之处欢迎指出!monorepo
是什么与每个项目/包各自存储在一个仓库的
multi-repo
模式相对,monorepo
是把所有包都存放到一个仓库集中管理。一个monorepo
的项目目录大概如下所示:所有的包都存放在
packages
(也可以是任意自定义的)目录下,每个包都是一个独立的项目,但他们之间又存在紧密的联系,或者说依赖关系,在笔者看来这是使用monorepo
的前提。为什么选择
monorepo
其实起因是笔者观察到很多开源项目,比如
Vue
、React
等都是使用的monorepo
方式进行项目管理。遂进行了一番了解,在实践中也更好地感受到这种模式的优势:依赖提升(
hoist
)多个互相关联的包大多都存在很多相同的依赖,在
monorepo
模式下,这些相同的依赖会被提升到根目录下下载并存储。node.js
解析依赖的方式是逐级向上查找node_modules
目录,所以通过这种方式,能够极大减少依赖重复安装,并保证了依赖版本的一致性。安装依赖后的目录如下:代码复用更方便
相互关联的包之间通常都会有一些通用的方法,在
multi-repo
的模式下,这些通用函数往往存在于多个仓库中,当某个函数更改时,还需要手动同步所有仓库,不仅繁琐而且容错率低。monorepo
项目通过把通用函数抽离到类似于root/packages/shared/
这样的目录下(可以通过yarn/npm link
该目录或者webpack alias
甚至直接通过相对路径引用的形式进行代码复用)。现在,所有通用函数都被抽离到独立的包中管理,只需要维护这一份代码即可。原子提交特性
对于原子提交,笔者的理解是:在
monorepo
中,多个项目之间相互关联,每次代码提交覆盖到所有的项目更改,较大限度地保证了各项目之间版本的兼容性。......
任何事物都有相对性,
monorepo
也有这样一些缺点:monorepo
对这些开发人员来说造成了很大的冗余(多余的包,多余的依赖)。monorepo
实践让我们先从最简单的方式入手,创建一个
monorepo
项目,随后逐步添加新的工具。PS:本文不会深入到各种细节,如
lerna
配置详解、TS
配置如何复用之类的。第一版
你只需要
yarn/npm
就能实现。首先让我们新建一个文件夹作为我们的monorepo
项目根目录,根目录下的package.json
的private
字段记得设置为true
,因为我们并不想发布我们的monorepo
整个项目:$ mkdir test-monorepo $ cd test-monorepo $ yarn init
接着我们创建
packages
目录,并添加package-1
和package-2
两个包,并用yarn
初始化它们,现在我们的项目文件结构是这样的:我们l两个项目都使用了
lodash
,package-1
和package-2
的package.json
依赖如下:为了让依赖提升,现在让我们回到项目根目录(
test-monorepo
),添加依赖:现在的目录结构:
假设
package-1
依赖package-2
,我们使用yarn link
把package-2
给link
到根目录下:现在我们的目录结构如下:
test-monorepo/ node_modules/ lodash/ package-2/ # link package.json packages/ package-1/ package.json package-2/ package.json
好了,我们可以在
package-1
中通过require('package-2')
获得package-2
模块了。开发完成,提交代码,然后我们分别进入package-1
和package-2
目录下发布包:第一版小结:仅仅通过
yarn
的基本命令及我们勤劳的双手(手动狗头),我们就创建了一个monorepo
项目,体会到了monorepo
带来的优势。不过嘛,这也太繁琐了,我们奔走于各个目录安装依赖,体现不出我们的X格啊,于是该yarn workspaces
登场了。第二版
那么
yarn workspaces
能给我们带来什么好处呢?node_modules
,同时,工作区的所有包都会被link
到根目录下的node_modules
。而你只需要在根目录yarn or yarn install
即可;yarn.lock
文件,在根目录下,避免了依赖的版本冲突。回到第一版结尾的目录结构:
test-monorepo/ node_modules/ lodash/ package-2/ # link package.json packages/ package-1/ package.json package-2/ package.json
现在,删除根目录下的
node_modules
,修改根目录下的package.json
,添加workspaces
字段,并清除所有依赖:然后,只需要在根目录执行:
现在,你将观察到项目的目录结构如下:
同时,通过以下命令可以轻松做到给各个包添加依赖并自动下载到根目录下的
node_modules
,而无需手动修改包的package.json
再yarn install
:瞬间清爽了!
第二版小结:我们通过
yarn workspaces
进行依赖包管理,摆脱了原始的依赖管理方式,通过几个命令即可在根目录下管理所有的依赖。我们已经前进了一大步,但这还不够,还记得我们发包的过程吗,进入每个包目录,运行yarn publish
。我们的发包过程还是相对繁琐。接下来,我们将引入lerna
帮助我们进一步提高效率。第三版
lerna
提供了创建和管理monorepo
项目的便捷工作流程,其中包括了yarn workspaces
能做的依赖管理,但相比之下,用yarn workspaces
管理依赖更好,毕竟yarn
是专业的包管理器(更详细的原因先占个坑,有空再补)。所以有关依赖包的管理还是交给yarn workspaces
来做,而我们将使用lerna
来进行发包管理,当然lerna
还有很多其他很有用的功能,但在本文不做讨论。还是前面的项目,现在我们需要使用
lerna
进行管理,首先我们需要初始化lerna
相关配置,只需要在项目根目录下运行:lerna
的初始化包括:git
仓库;packages
目录,用于存放所有的包;lerna.json
配置文件;package.json
文件。我们是在已有的项目中进行初始化,所以仅会创建我们项目没有的部分。接下来我们需要重新运行
yarn install
下载lerna
依赖包,然后就可以愉快的玩耍了。首先我们先修改一下
lerna.json
:接着我们需要做的是将我们的
test-monorepo
项目托管到github
上,将本地文件提交到远程仓库后,在根目录运行:这个命令会对比上次发包的
commit
,并识别出有变更的包,然后在命令行工具里询问每个更新包的版本,这些变更后的版本号也会同步到packages
下所有包的package.json
依赖中,确保依赖了正确版本的包。之后这些版本变更会被提交到远程仓库,同时变更的包会被打上tag
并发布到npm
服务器上。第三版小结:通过
lerna
我们很轻松的完成了monorepo
的包之间版本依赖自动更新,近乎一键发包的功能。建议大家都动手尝试一下,会更容易理解。总结
本文简单讨论了
monorepo
模式在前端的应用,提供了一个简易的渐进式的实践教学(yarn
——>yarn workspaces
——>yarn workspaces
+lerna
),希望能对大家有所帮助。BTW,笔者更推荐在开源项目中使用
monorepo
模式,因为通常开源项目都会有自己的生态,通过monorepo
将生态整合到一个仓库中管理,能更好地发挥monorepo
的优势。References
The text was updated successfully, but these errors were encountered: