美文网首页
基于 lerna 和 yarn workspace 的 mono

基于 lerna 和 yarn workspace 的 mono

作者: 前端小白的摸爬滚打 | 来源:发表于2021-08-09 19:52 被阅读0次

    基于 lerna 和 yarn workspace 的 monorepo 工作流

    由于 yarn 和 lerna 在功能上有较多的重叠,我们采用 yarn 官方推荐的做法,用 yarn 来处理依赖问题,用 lerna 来处理发布问题。能用 yarn 做的就用 yarn 做

    可以避免管理很多仓库

    使用 monorepo 的原因

    我正在开发的项目 A,依赖了已经线上发布的项目 B,但是随着项目 A 的不断开发,又需要不时修改项目 B 的代码(这些修改暂时不必发布线上),如何能够在修改项目 B 代码后及时将改动后在项目 A 中同步? 在项目 A 发布上线后,如何以一种优雅的方式解决项目 A,B 版本升级后的版本同步问题?

    为了解决

    1. 多个库之间共享依赖

    2. 库与库之间相互依赖的问题

    3. 测试、构建、lint、commit、发布等脚本共享

    4. 手动改版本号的问题

    monorepo 最主要的好处是统一的工作流和Code Sharing。比如我想看一个 pacakge 的代码、了解某段逻辑,不需要找它的 repo,直接就在当前 repo;当某个需求要修改多个 pacakge 时,不需要分别到各自的 repo 进行修改、测试、发版或者 npm link,直接在当前 repo 修改,统一测试、统一发版。

    16c6ba2a8a6ce740.png

    monorepo 方案的优势

    1. 避免重复安装包,因此减少了磁盘空间的占用,并降低了构建时间;

    2. 内部代码可以彼此相互引用;

    • 代码重用将变得非常容易

    • 依赖管理将变得非常简单:同理,由于项目之间的引用路径内化在同一个仓库之中,我们很容易追踪当某个项目的代码修改后,会影响到其他哪些项目。通过使用一些工具,我们将很容易地做到版本依赖管理和版本号自动升级;lerna

    monorepo 方案的劣势

    • 项目粒度的权限管理变得非常复杂:这意味着 A 部门的 a 项目若是不想被 B 部门的开发者看到就很难了。

    • 新员工的学习成本变高:不同于一个项目一个代码仓库这种模式下,组织新人只要熟悉特定代码仓库下的代码逻辑,在 monorepo 策略下,新人可能不得不花更多精力来理清各个代码仓库之间的相互逻辑,当然这个成本可以通过新人文档的方式来解决,但维护文档的新鲜又需要消耗额外的人力;

    当多个子项目放在一个代码仓库,并且子项目之间又相互依赖时,我们面临的棘手问题有两个:

    1. 如果我们需要在多个子目录执行相同的命令,我们需要手动进入各个目录,并执行命令; yarn worksapces

    2. 当一个子项目更新后,我们只能手动追踪依赖该项目的其他子项目,并升级其版本。 lerna

    依赖管理

    monorepo: 各个库之间存在依赖,如 A 依赖于 B,因此我们通常需要将 B link 到 A 的 node_module 里,一旦仓库很多的话,手动的管理这些 link 操作负担很大,因此需要自动化的 link 操作,按照拓扑排序将各个依赖进行 link

    解决方式:

    通过使用 workspace,yarn install 会自动的帮忙解决安装和 link 问题(https://github.com/lerna/lerna/issues/1308

    $ yarn install # 等价于 lerna bootstrap --npm-client yarn --use-workspaces
    

    在某一个 workspace 下执行 yarn install 和在 root 下执行 yarn install 的效果是一样的

    依赖清理

    在依赖乱掉或者工程混乱的情况下,清理依赖

    • 普通项目:直接删掉 node_modules 和编译之后的产物即可

    • monorepo: 不仅需要删除 root 的 node_modules 的编译产物还需要删除各个 package 里的 node_modules 以及编译产物

    解决方式:使用 lerna clean 来删除所有的 node_modules,使用 yarn workspaces run clean 来执行所有 package 的清理工作

    $ lerna clean # 清理所有的node_modules
    $ yarn workspaces run clean # 执行所有package的clean操作
    

    安装/删除依赖

    monorepo: 一般分为三种场景

    1. 给某个 package 安装依赖:yarn workspace packageB add packageA 将 packageA 作为 packageB 的依赖进行安装

    2. 给所有的 package 安装依赖: 使用 yarn workspaces add lodash 给所有的 package 安装依赖

    3. 给 root 安装依赖:一般的公用的开发工具都是安装在 root 里,如 typescript,我们使用 yarn add -W -D typescript 来给 root 安装依赖

    对应的三种场景删除依赖如下

    yarn workspace packageB remove packageA
    yarn workspaces remove lodash
    yarn remove -W -D typescript
    

    项目构建

    root 目录下的 package.json 中编写脚本

    "build": "yarn workspaces run build",
    

    yarn workspaces run xxx

    执行每一个 package 下的 xxx 命令

    该脚本会执行每一个子目录下的 build 脚本

    会按照 root 目录下的 package.json 中

    "workspaces": [
        "packages/*"
      ],
    

    的顺序执行各个子项目的 build 脚本(通配符时就按照在项目中的次序)

    版本升级及发包

    发布 npm 包

    发布完 git 后我们还需要将更新的版本发布到 npm 上,以便外部用户使用

    我们发现手动的执行这些操作是很麻烦的且及其容易出错,幸运的是 lerna 可以帮助我们解决这些问题

    lerna 提供了 publish 和 version 来支持版本的升级和发布

    publish 的功能可以即包含 version 的工作,也可以单纯的只做发布操作。

    lerna version

    lerna version 的作用是进行 version bump,支持手动和自动两种模式

    只发布某个 package

    不支持,lerna 官方不支持仅发布某个 package,见 https://github.com/lerna/lerna/issues/1691,如果需要,只能自己手动的进入package进行发布,这样lerna自带的各种功能就需要手动完成且可能和lerna的功能相互冲突

    由于 lerna 会自动的监测 git 提交记录里是否包含指定 package 的文件修改记录,来确定版本更新,这要求设置好合理的 ignore 规则(否则会造成频繁的,无意义的某个版本更新),好处是其可以自动的帮助 package 之间更新版本

    例如如果 ui-form 依赖了 ui-button,如果 ui-button 发生了版本变动,会自动的将 ui-form 的对 ui-button 版本依赖更新为 ui-button 的最新版本。 如果 ui-form 发生了版本变动,对 ui-button 并不会造成影响。

    lerna publish

    package的源码需要打包后进行发布,需要在lerna publish之前,手动打包已经改动的package,有时候会在publish之后才想起来还没打包,针对这个问题,把build流程和publish发布封装一个npm scripts,打包后自动publish

    npm publish之前需要进行build且需要在打包后的目录下运行npm publish

    lerna publish from-package

    在注册表中不存在版本的最新提交中发布包 ( from-package)。

    除了要发布的包列表是通过检查每个 package.json 包并确定注册表中是否不存在任何包版本来确定的。注册表中不存在的任何版本都将被发布。当以前 lerna publish 未能将所有包发布到注册表时,这很有用。

    发布 package 中 pkg.json 上的 version 在 registry(高于 latest version)不存在的包

    lerna publish 永远不会发布 package.json 中 private 设置为 true 的包

    在 lerna 中,如果 workspaces 之前存在依赖的话,在这次发包中,例如 A 这个包依赖了 B,B 在这次发包中版本升级了,那么这里 A 里面依赖的 B 也要更新到对应的版本。

    lerna publish 一般有以下几种形式

    1. 发布自上次发布来有更新的包(这里的上次发布也是基于上次执行lerna publish 而言)

    2. 发布在最近 commit 中修改了 package.json 中的 version (且该 version 在 registry 中没有发布过)的包(即 lerna publish from-package)

    发布

    创建项目

    初始化项目 & 初始化 lerna

    yarn init
    
    npx lerna init
    

    lerna 配置使用 yarn workspaces, 使用 independent 模式

    lerna.json

    {
      "packages": ["packages/*"],
      "version": "independent",
      "npmClient": "yarn",
      "useWorkspaces": true
    }
    

    npmClient: 我们显示声明了我们的包客户端(npmClient)为 yarn,并且让 Lerna 追踪我们 workspaces 设置的目录,这样我们就依旧保留了之前 workspaces 的所有特性(子项目引用和通用包提升)。

    version: 除此之外一个有趣的改动在于我们将 version 属性指定为一个关键字 independent,这将告诉 lerna 应该将每个子项目的版本号看作是相互独立的。当某个子项目代码更新后,运行 lerna publish 时,Lerna 将监听到代码变化的子项目并以交互式 CLI 方式让开发者决定需要升级的版本号,关联的子项目版本号不会自动升级,反之,当我们填入固定的版本号时,则任一子项目的代码变动,都会导致所有子项目的版本号基于当前指定的版本号升级。

    独立模式,每个 package 都可以有自己的版本号。版本号维护在各自 package.json 的 version 中。每次发布前都会提示已经更改的包,以及建议的版本号或者自定义版本号。

    lerna publish:发布代码有变动的 package,因此首先您需要在使用 Lerna 前使用 git commit 命令提交代码,好让 Lerna 有一个 baseline;

    lerna publish 参数

    --npm-tag [tagname] — 使用给定的 npm dist-tag (默认为 latest)发布到 npm。

    配置 package.json 使用 yarn workspacess

    // package.json
    {
      "name": "monorepo-template",
      "private": true, // root禁止发布
      "workspaces": [ // 配置package目录
         "packages/*"
      ]
    }
    

    创建 package

    注意 package.json 模块的配置

    // package.json
    {
     "name": "ui",
     "version": "1.0.0",
     "main": "index.js",
     "publishConfig": {
       "access": "publish" // 如果该模块需要发布,对于scope模块,需要设置为publish,否则需要权限验证
      }
    }
    
    1. 创建一个 lib 模块并初始化
    cd packages && mkdir lib && cd lib && yarn init -y
    
    1. 创建一个 ui 模块并初始化 --- 需要发布

    使用 lerna create 快速创建 package --- 推荐

    npx lerna create ui -y
    
    1. 创建一个 app 模块
    npx lerna create app -y
    

    将 lib 作为 app 的依赖

    yarn workspace app add lib@1.0.0 # 这里必须加上版本号,否则报错
    

    一般 root 只包含一些开发工具依赖如 webpack,babel,typescript 等

    yarn add -W -D typescript
    

    添加 conventional-commit 支持

    lerna 的 version_bump 和 changelog 生成都依赖于 conventional-commit,因此需要保证 commit-msg 符合规范。

    添加@commitlint/cli 和@commitlint/config-conventional 以及 husky

    yarn add -W -D @commitlint/config-conventional @commitlint/cli lint-staged husky
    

    commitlint.config.js

    module.exports = {
      extends: ["@commitlint/config-conventional"]
    };
    

    package.json 中配置 commit-msg 的 hooks

    "husky": {
        "hooks": {
          "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
        }
      },
    

    添加 git-cz,支持 commit-msg 提示

    $ yarn add -W -D commitizen cz-conventional-changelog
    

    配置 commitizen 并添加 commit 为 npm script

    // package.json
    "scripts": {
       "commit": "git-cz"
     },
     "config": {
        "commitizen": {
          "path": "cz-conventional-changelog"
        }
      }
    

    这样后续 commit,就可以使用 yarn commit 进行 commit,其会自动做出如下提示

    发版

    package.json 中新增发版脚本

     "release": "lerna version --no-git-tag-version",
        "publish": "lerna publish from-package",
        "publish:next": "lerna publish from-package --dist-tag next"
    

    lerna 常用命令

    本项目使用 lerna 来管理多个插件

    lerna 常用命令

    $ npx lerna init # 初始化
    $ npx lerna create @templatejs/parser # 创建一个package
    $ npx lerna add yargs --scope=@templatejs/parser # 给package安装依赖
    $ npx lerna list # 列出所有的包
    $ npx lerna bootstrap # 安装全部依赖
    $ npx lerna link # 建立全部软连接
    $ npx lerna changed # 列出下次发版lerna publish 要更新的包
    $ npx lerna publish # 会打tag,上传git,上传npm
    

    发布步骤

    $ yarn release
    # git commit
    $ yarn publish:next # 测试包
    $ yarn publish # 正式包
    

    <del>也可使用下面的命令发布</del>

    不打 tag 发布

    $ yarn test
    $ yarn build
    $ npx lerna version --no-git-tag-version # 仅修改version
    $ npx lerna publish from-package --dist-tag next # 发布测试包,需要选择对应的 alpha 版本号
    $ npx lerna publish from-package # 发布正式包
    

    打 tag 发布

    $ yarn test
    $ yarn build
    $ npx lerna publish --dist-tag next # 发布测试包,需要选择对应的 alpha 版本号
    $ npx lerna publish # 发布正式包
    

    如何从 multirepo 迁移至使用 monorepo 策略?

    至此,我们学会了如何采用 monorepo 策略组织项目代码的最佳实践,或许您已经开始跃跃欲试想要尝试前文提到的种种技巧。从 0 搭建一个 monorepo 项目,当然没问题!可是如果要基于已有的项目,将其转化为一个使用 monorepo 策略的项目呢?

    或许您注意到了,Lerna 为我们提供了 lerna import 命令,用来将我们已有的包导入到 monorepo 仓库,并且还会保留该仓库的所有 commit 信息。然而实际上,该命令仅支持导入本地项目,并且不支持导入项目的分支和标签 。

    推荐阅读

    为什么使用 monorepo

    注意事项

    1. 运行lerna publish命令时需要提交代码,也就是执行 commit & push

    2. package.json 中的

    // 发包的地址
    "publishConfig": {
        "registry": "xxx.xxx.com"
      },
    
    1. push 执行lerna publish通过命令行交互的形式选择每一个 package 的版本,然后为你自动修改每个包的 package.json,对于依赖的 package 也会自动更新到对应的版本,并且提交到 git

    2. npx lerna publish from-package 会检查每一个包的 package.json(即使该包没有改动),按 package.json 中的版本进行发包,只有在当前版本不存在于注册表中的时候才可以发布成功

    lerna version 会做的事情

    1.识别从上次打标记发布以来发生变更的 package

    2.版本提示

    3.修改 package 的元数据反映新的版本,在根目录和每个 package 中适当运行lifecycle scripts

    4.在 git 上提交改变并对该次提交打标记(git commit & git tag)

    5.提交到远程仓库(git push)

    lerna version --no-git-tag-version 不会执行4 、5

    lerna publish / lerna version 是否需要build?

    需要!但是不需要切换到打包后的目录,lerna自动帮我们在打包后的目录运行publish

    会更新所有包 还是只更新修改过的包?

    是的会更新所有包的版本,不论你是否修改

    publish结束之后需要我们自己commit & push来将版本号提交到远端

    lerna version会将版本修改commit&push

    直接调用lerna publish会先进行lerna version的操作

    相关文章

      网友评论

          本文标题:基于 lerna 和 yarn workspace 的 mono

          本文链接:https://www.haomeiwen.com/subject/njqtbltx.html