实现Monorepo项目管理随着团队建设和相关业务的不断增长,越来越多的NPM包需要多人维护。如果每个项目之间有关系,需要在多个编辑器之间切换,并且通过npmlink调试,开发效率受限,那么有没有办法解决目前的痛点呢?答案是Monorepo!字节跳动内部百科词条中对Monorepo的定义如下:Monorepo是一种软件开发策略,用于将来自多个项目的代码存储在单个存储库中。目前,Lerna作为JavaScript项目的多包管理器,已经比较成熟,也得到了现代企业的验证。因此,我们会逐步搭建一个基于Lerna[1]的Monorepo管理环境,希望能帮助大家在公司的各种业务中实现,实现降本增效。根据笔者的经验,Monorepo将显着提升开发者的幸福感,让我们开始吧!本文主要内容结构如下,小伙伴们可以按需食用:1.为什么选择LernaMonorepo可以定义为一个策略,那么一定是一个可以解决问题的策略,基于Monorepo多包Lerna实施的管理方法。可以解决的问题(优点)如下:扁平化:同一个仓库(项目)下多个包的统一管理和维护集中化:根目录下的node_modules/文件夹下维护所有包的三方依赖简化:根据文件变化统一执行命令,按需发送包,自动升级版本回写仓库,打标签高效:相互依赖的项目可以直接链接,避免开发者在多个仓库之间切换当然,在使用了Lerna之后长期以来,生产环境中也暴露出一些问题,例如:Invalidbuild:allpackageswillbebuildedbeforeapackagerelease[2]吊装(hoist)后比较明显问题都列在这里,不是说Lerna应该被摒弃,但我们应该清楚技术方案的优缺点,并结合项目的实际情况做一些权衡,以上缺点只是在施工中没有那么优雅,但并不将Lerna影响为可以在Monorepo中实施的解决方案。2、以Monorepo形式初始化一个项目我们将从0到1搭建一个纯Lerna架构的Monorepo项目,完善团队协作规范ESlint校验、Prettier自动格式化、gitcommit消息规范。2.1初始化项目结构首先你要全局安装Lerna:yarnglobaladdlerna//ornpminstalllerna-g然后新建一个项目目录,使用Lerna初始化一个基本结构mkdirdyboy-lerna-projectcddyboy-lerna-project/lernainit--independent之后得到一个文件目录结构如下:.├──lerna.json//Lerna配置文件├──package.json//当前项目的描述文件└──packages///存放的文件夹allpackagesLernainitializationproject这时候加了一个--independent参数,表示使用独立模式。在Lerna中,有两种模式:Fixed模式:所有包的版本号一致,每次更新全量发布。独立模式:每个包版本号都是独立的,互不影响,每次更新一般按需发布。我们都会选择独立的模式,避免多次打包下的频繁发包,尤其是一些业务变动频繁的项目,发包的压力是很恐怖的#128561;。2.2Lerna+YarnWorkspacesLerna默认会使用NPM作为包管理器,但更推荐使用yarn作为Lerna的默认包管理器。Yarn在1.0版本就已经支持了workspaces功能。关于它的优势和与Lerna的关系,可以参考当时的这篇文章:《Workspaces[3]》与YarnWorkspaces的结合,让Lerna解决方案补短板,如虎添翼。首先是修改lerna.json的配置,改成如下:{"version":"independent","npmClient":"yarn","useWorkspaces":true}并在包中指定(新增)工作空间.json文件(Workspace)字段:+"workspaces":["packages/*"],表示packages/目录下的所有项都被认为是Lerna+Yarn管理的。之后,无论我们在哪个文件夹执行yarn,都会把packages/目录下所有项目的依赖安装到根目录下的node_modules/下。2.3ESlint+Prettier+CommitRules以上规则需要根据项目进行配置,在任何项目中都是比较统一的。因为相关配置过程在之前的文章中已经详细介绍,这里不再赘述。相关配置规则的初始化和详细过程请参考《手摸手学会搭建一个 TS+Rollup 的初始开发环境》中的5-7步。以上配置完成后,我们的项目就大致初始化好了!2.4NPM团队账号因为发包需要一个账号,Monorepo同时管理着几个或者几十个包,都需要维护和发包。如果用个人账户向公司自建Registry发送合约,万一学生离开,仓库就会变成“幽灵仓库”。当然,我们可以请公司内部的Registry维护人员直接改相应的包,但总归是一件麻烦的事情。为此,可以为团队申请一个公众号,通过npmtokencreate创建一个权限token,放在项目根目录下的.npmrc文件中。之后无论哪个开发者维护,都会默认使用团队账号发送包更新。最终初始化的项目文件结构如下:3.在发布版本之前提到过,Lerna可以统一管理所有的包,所以我们可以直接在根目录下的package.json文件中指定快捷命令来实现按需发送包的功能注意:Lerna发送包时,默认会忽略package.json中设置为"private":true的私有包。3.1项目打包编译在发布新的打包版本之前,一般需要对产品进行打包编译。Monorepo下的多个包在发布之前,也必须先打包。(1).借助Lerna提供的run命令,LearnRun可以让所有在package.json->scripts中定义该命令的项目在发送包之前执行该命令。例如执行:lernaexecbuild会遍历每一个package,查找其package.json中是否定义了build命令->scripts,有则执行,否则跳过(在所有包含的包中运行npmrunbuild构建命令)。这种方式会有一个问题:在发送每个包之前,会先构建所有的包,这样会增加包发布的时间。有没有更优雅的方式?(2).NPMScripts生命周期定义在package.json文件中的scripts字段中,包含默认的pre和post两个生命周期。通过执行npmrunbuild,会自动先执行npmrunprebuild,然后是npmrunbuild,最后是npmrunpostbuild。除了package.json->scripts中的自定义命令外,还有一些npm自带的脚本,比如npmpublish。npmpublish命令的生命周期包括:prepublishOnlyprepareprepublishpublishpostpublishprepare不会在npmpublish--dry-run时执行。注意:npm6.x和7.x版本的生命周期不同。以上是6.x版本。考虑到6.x和7.x版本的差异,建议将发送包前的动作放到prepublishOnly命令中。详情请参考:《scripts - NPM 6.x 官方文档[4]》&《scripts - NPM 7.x 官方文档[5]》(3)。On-demandBuild有了上面的NPMPublish生命周期的基础,所以我们可以在需要发布包的时候构建项目(packages/目录下的项目)。在其package.json->scripts中定义如下字段:"scripts":{"build":"rollup-c","prepublishOnly":"yarnbuild"}这样,在执行npmpublish时,会执行prepublishOnly先yarnbuild,编译打包项目,再打包。这样就可以优雅流畅的按需包裹派送了!3.2项目包发送在包发送阶段,我们在根目录的package.json文件中添加内容:"scripts":{"release":"lernapublish","re??lease:beta":"lernapublish--canary--pre-dist-tag=beta--preidbeta--yes"},yarnrelease用于发布yarnrelase:beta正式版用于发布测试版开发联调时测试使用约定大于配置:根目录下的package.json->name字段默认为root,可以理解为“工作根目录”。如果它有作用域(作用域,例如:@dyboy/utils),可以重命名为:@dyboy/root让其他开发者知道这是一个作用域的Monorepo项目,虽然name字段没有作用。综上所述,基于Lerna构建Monorepo项目的心智成本并不高,但需要我们对其中的流程、生命周期、NPMScripts等知识有一定的了解和掌握,构建者需要是能够在流程和管理中找到共同的需求和约束,为团队降本提效的落地方案。本文简要介绍一个基于Monorepo的解决方案的构建过程。在前端工程中,构建者还需要思考是否有优化空间,考虑细节?比如写一个通俗易懂的README.md文档,思考新人是否更容易上手,尝试解决过程中的问题,积极探索新的Monorepo技术方案,比如Rush,PNPM...TODO:Monorepo能否自动发布正式版包?感兴趣的朋友可以关注后续分享!参考文献[1]Lerna官网:https://lerna.js.org/[2]Phantom依赖-应用级Monorepo优化方案:https://github.com/worldzhao/blog/issues/9[3]Yarn工作区:https://classic.yarnpkg.com/lang/en/docs/workspaces/[4]scripts-NPM6.x官方文档:https://docs。npmjs.com/cli/v6/using-npm/scripts[5]scripts-NPM7.x官方文档:https://docs.npmjs.com/cli/v7/using-npm/scripts
