现代Monorepo工程技术选型,说说我的想法工具:Bazel(byGoogle)[2]Lage(byMicrosoft)[3]Lerna[4]Nx(byNrwl)[5]Rush(byMicrosoft)[6]Turborepo(byVercel)[7]相应地,在本文中也对各种工具一一进行了介绍。而且,相信每一个看过这篇文章的同学都会留下这样一个疑问:那么多的MonorepoTools我该如何选择类型呢?在这里,我给出的答案是PNPM+Turborepo+Changesets。那么,为什么是这3个?下面,我将围绕这三项技术来回答选择这个的原因以及如何去做。PNPMPNPM的动机(Motivation[8]),官方文档中介绍:“Savingdiskspaceandboostinginstallationspeed”,节省磁盘空间,提高安装速度。除了这个动机中描述的明显优势外,PNPM还内置了对Monorepo[9]的支持,并解决了许多被批评的问题。其中比较经典的是Phantomdependencies。因为,默认情况下,yarn和npm安装的依赖都会升级。因此,有时候你可能会遇到Monorepo项目中某个package中的package.json并没有安装这个依赖,但是在实际代码中却用到了这个依赖...虽然PNPM可以解决这个问题,但是默认情况下安装的依赖PNPM也将升级。如果需要PNPM禁止依赖提升,我们可以在Monorepo项目工作空间下的.npmrc文件中配置【10】,比如只提升lodash:hoist-pattern[]=*lodash*当然还有一些其他的问题,有兴趣的同学可以看看ELab团队写的这篇文章《Monorepo 的这些坑,我们帮你踩过了!》[11]。那么,在简单说明了为什么要使用PNPM之后,我们来看看如何使用它?工作区配置使用PNPM的Monorepo非常简单。你只需要在Monorepo项目的工作空间下新建一个pnpm-workspace.yaml文件,配置:packages:-'packages/**'接下来就是记住公共依赖和多包任务执行相关命令。由于我们的技术选型中有Turborepo,它会负责多包任务的执行。所以这里只需要记住常用依赖相关的命令即可。常用的依赖相关命令pnpmi在PNPM中,依赖的安装可以用pnpmi来完成。在Monorepo场景下,pnpmi会默认安装所有依赖(包括packages/*)。另外,pnpmi还需要用到3个选项(Option):--filter,将依赖安装到指定的包中,如果不声明要安装的依赖包,则会安装package.json中的所有依赖默认--prod,P,安装依赖到dependencies--dev,D,安装依赖到devDependenciespnpmremove在PNPM中,删除package.json中的一个依赖,可以使用pnpmremove来完成。它的选项(Option)与pnpmi类似。其中,不同的是,当我们要删除工作空间中packages中所有package的package.json中的一个依赖时,需要使用-r,比如移除所有packages中的lodash:pnpmremovelodash-r当然,可能有些同学还有其他需求。有兴趣的同学可以去文档了解更多,这里就不展开了。经常维护开源项目的Changesets同学都知道,每次发布一个包,都需要修改package.json的version字段,并且需要同步更新为本次发布修改的CHANGELOG.md。这样一来,一个问题就会凸显出来。每次发布时手动更新版本和CHANGELOG.md有点麻烦。而且用过Lerna的同学应该都知道,Lerna内置了这个支持。但是,下面提到的PNPM和Turborepo都不支持这个功能,因此两者的官方文档都推荐了支持此功能的工具,例如Changesets[12],Beachball[13],Auto[14]等。所以,我们这里要介绍的是Changesets。下面我们就来看看如何在之前搭建的PNPMMonorepo项目中使用Changesets。首先需要在Monorepo项目的工作空间执行以下两条命令:pnpmi-DW@changesets/clipnpmchangesetinit前者是安装Cliangsets的CLI,后者是初始化.changeset文件夹和对应文件:.changeset|--config.json|__README.md在这里,我们先看一下config.json[15]文件:{"$schema":"https://unpkg.com/@changesets/config@1.6.4/schema.json","changelog":"@changesets/cli/changelog","commit":false,"linked":[],"access":"restricted","baseBranch":"master","updateInternalDependencies":"patch","ignore":[]}除了不需要修改的$schema,config.json文件中列出了7个字段。各个字段的作用是:changelog设置CHANGELOG.md的生成方式,可以设置false不生成,也可以设置为定义生成行为的文件地址或者依赖名,比如`changelog-git`[16]由Changsets提供。其中,定义生成行为的文件固定代码模板为:asyncfunctiongetReleaseLine(){}asyncfunctiongetDependencyReleaseLine(){}exportdefault{getReleaseLine,getDependencyReleaseLine}commit设置执行changeset时是否使用Git提交变更addorchangesetpublishoperationslinked设置一个共享版本的包,而不是一个独立版本的包,比如组件库中一个主题和一个独立组件的关系,即修改Version时,共享包需要一起同步更新。access设置执行npmpublish--access选项,通常我们是public包,所以设置public即可(注意会被package.json中的access字段覆盖)baseBranch设置默认的Git分支,比如默认GitHub的branch现在应该是mainupdateInternalDependencies设置依赖包版本更新机制,是一个枚举(major|minor|patch),比如设置为minor时,只有当依赖包有新的minor版本或者才会package.json的dependencies或devDependencies相应更新对应的依赖版本ignore设置不需要发布的包,这些会被Changesets忽略初始化.changeset文件夹后,可以正常使用changeset相关的命令,主要是这三个commands:pnpmchageset用来生成这个修改的CHANGELOG.md中要添加的描述pnpmchangesetversion用来生成修改的版本packagepnpmchangesetpublish用于发布包另外,如果是在业务场景中,我们通常需要将包发送到公司私有的NPMRegistry,可以通过多种方式进行配置。不过需要注意的是,Changesets只支持在每个package中声明publicConfig.registry或者配置process.env.npm_config_registry,对应的代码会是这样的://https://github.com/changesets/changesets/blob/main/packages/cli/src/commands/publish/npm-utils.tsfunctiongetCorrectRegistry(packageJson?:PackageJSON):string{constregistry=packageJson?.publishConfig?.registry??process.env.npm_config_registry;返回!注册表||注册表===“https://registry.yarnpkg.com”?"https://registry.npmjs.org":registry;}可以看到,如果上面两种情况都获取不到registry,Changesets都是根据publicRegistry查找或者发布包的。Turborepo说到Turborepo,大家可能有点陌生。不过,我想大家都知道Vercel[17](毕竟RichHarris[18]、SebastianMarkb?ge等人都加入了),Turbrepo是Vercel旗下的一个开源项目。Turborepo用于为JavaScript/TypeScript的Monorepo提供极快的构建系统。简单理解一下,使用Turborepo执行Monorepo项目的构建(或其他)任务会非常快!关于Turborepo的其他优点,其官方文档[19]介绍的很详细,有兴趣的同学可以自行学习~所以可以理解,快是选择Turborepo负责多包任务执行的原因单体仓库项目。在Turborepo中执行多包任务是通过turborun