Bjkb/Blog

使用 lerna 管理项目中的业务组件

Opened this issue · 0 comments

Bjkb commented

方案的起源

单仓库管理多个包

在进行业务开发的时候,往往能抽离出很多相似的模块跟组件。自己负责的项目一般也不止一个,如果只是将其手动复制到不同的项目当中,组件跟模块往后的更新与迭代将会很困难。

大多数人都会这么干,那就是将公共的组件、模块、工具方法、发布到 npm私有仓库上进行统一的管理跟迭代...

遇到的问题

  • 由于大家都是高自我要求的前端开发者,踊跃的抽离项目中的公共模块,发布到 npm 仓库当中,所以就造成了 npm 公共组件仓库体积的急剧增加。
  • 不仅如此,由于公共方法也被放到这个仓库当中,造成了一个很尴尬的情况,自己只是修改了公共方法当中的导出操作,不得不发布了整个仓库。

提出的解决方案(多仓库管理多个包)

最开始的解决方案,将公共操作方法,公共组件,公共模块弄成了三个仓库单独发布
自从这样实行以来,上面的问题得到了初步的解决,本以为满心欢喜,结果却遇到了更棘手的情况。

遇到的问题

  • 在使用公共操作方法时,细心的小明发现了公共操作方法当中的某个缺陷,提出了改进的意见,于是提了一个 pull request ,负责管理公共仓库的小华,review 以后觉得相当的不错,于是打上了 tag 更新了版本。
  • 某日,小明在使用公共模块时,发现自己改进的操作方法,在这个模块当中没有更改,于是在查看源码之后,发现公共模块依赖的公共操作方法的版本没有升级...

简单的总结

  • 使用单独的仓库进行管理多个包,更改一个,必须发布整个仓库,文件目录过多,阅读体验差。
  • 使用多仓库管理多个包,难以梳理其中的依赖情况,不便于管理,维护多个仓库浪费时间。

那能不能一个仓库管理多个包,但是这些包相对于这个仓库,又是独立的呢?

答案是可以的,在调研之后,发现业界早已存在解决方案,那就是 monorepo 管理方案,本文基于monorepo 方案的开源框架 lerna 进行简单的讲解。


lerna 的简单使用

Lerna 是一个管理多个 npm 模块的工具,是 Babel 自己用来维护自己的 Monorepo 并开源出的一个项目。优化维护多包的工作流,解决多个包互相依赖,且发布需要手动维护多个包的问题。

简单的安装

npm i -g lerna

git init lernaExample

cd lernaExample

lerna init

然后根据提示,项目当中生成的 package.jsonlerna.json 如下

// package.json
{
  "name": "root",
  "private": true, // 私有的,不会被发布,是管理整个项目,与要发布到npm的解耦
  "devDependencies": {
    "lerna": "^3.15.0"
  }
}
 
// lerna.json
{
  "packages": [
    "packages/*"
  ],
  "version": "0.0.0" // 仓库的版本
}

创建 npm

这里有个注意点是,lerna 中的仓库名 @lernaExample 必须一致

lerna create @lernaExample/utils

lerna create @lernaExample/components

lerna create @lernaExample/decorator

文件目录

image

增加依赖

lerna add react // 为所有 package 增加 react 模块 (像这种公共的包可以使用 lerna bootstrap --hoist 提升到项目的工程目录)

lerna add classnames--scope @lernaExample/components // 为 @lernaExample/components 增加 classnames 模块 

lerna add @lernaExample/utils--scope @lernaExample/components // 增加内部模块之间的依赖

// 想删除某个依赖项
lerna clean

更多的命令可以查看 lerna

image

发布

lerna publish 
// 按照提示发布就行
// 如果发布报错,可能是没有相对于仓库的权限

lerna 完整示例

在上文中,只是简单的了解 lerna 的基本使用,在项目当中使用的话还存在下面的几个问题。

  • 包中的文件没有经过编译,如果是直接书写的 es6 文件,则很有可能浏览器中不能兼容
  • 没有对文件进行基本的 eslint 格式校验
  • 没有对发布进行约束,比如 commit 校验,changlog 文件生成等

环境搭建

babel 环境

需要下载的 babel

  "dependencies": {
    "@babel/runtime": "^7.6.2",
    "@lernaExample/utils": "^0.0.0",
    "classnames": "^2.2.5",
    "react": "^16.8.0"
  },
  "devDependencies": {
    "@babel/cli": "^7.6.0",
    "@babel/core": "^7.6.0",
    "@babel/plugin-proposal-class-properties": "^7.5.5",
    "@babel/plugin-proposal-decorators": "^7.6.0",
    "@babel/plugin-transform-regenerator": "^7.4.5",
    "@babel/plugin-transform-runtime": "^7.6.0",
    "@babel/preset-env": "^7.6.0",
    "@babel/preset-react": "^7.0.0",
    "babel-plugin-import": "^1.12.2"
  },

.babelrc 文件配置

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "modules": false // 模块使用 es modules ,不使用 commonJS 规范
      }
    ],
    "@babel/preset-react"
  ],
  "plugins": [
    [
      "@babel/plugin-proposal-decorators",
      {
        "legacy": true
      }
    ],
    ["@babel/plugin-transform-regenerator"],
    ["@babel/plugin-proposal-class-properties"],
    [
      "@babel/plugin-transform-runtime",
      {
        "corejs": false, // 默认值,可以不写
        "helpers": true, // 默认,可以不写
        "regenerator": true, // 通过 preset-env 已经使用了全局的 regeneratorRuntime, 不再需要 transform-runtime 提供的 不污染全局的 regeneratorRuntime
        "useESModules": true // 使用 es modules helpers, 减少 commonJS 语法代码
      }
    ],
    [
      "import",
      {
        "libraryName": "antd"
      }
    ] // 通过手动引入antd样式
  ]
}

image

package.json 文件中增加编译命令

"main": "lib/index.js", // 文件入口
"scripts": {
    "compile": "babel src --out-dir lib",
     ...
 },

使用方式  npm run compile

eslint 环境

我这边直接使用的 vscode 扩展的 eslint 工具,如果团队内部对 eslint 有自己的定义,可以单独下载 eslint 包,对其文件进行配置。


commit 以及 changelog 环境

自动校验commit规范

使用 husky 包,相关依赖项

"devDependencies": {
    "@commitlint/cli": "^7.0.0",
    "@commitlint/config-conventional": "^7.0.1",
    "husky": "^0.14.3",
 }

创建 commitlint.config.js 文件

module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [
      2,
      'always',
      [
        'feat',
        'fix',
        'bug',
        'docs',
        'style',
        'refactor',
        'test',
        'chore',
        'version',
        'build',
        'ci',
        'perf',
        'revert',
      ]
    ],
    'scope-case': [2, 'always', ['lower-case', 'snake-case', 'upper-case', 'kebab-case']],
    'subject-case': [0]
  }
};

自动生成changelog记录

npm i cz-lerna-changelog 下载 lerna 专用的 changelog

image


使用

  • 更改了文件之后,执行npm run compile
  • 写上 commit 内容,如果内容不符合,会自动打回
  • 如果不用发布版本,则直接执行 lerna version,打上tag 生成 changlog 记录
  • 需要发布版本
    • 只发布一个,可以先执行 lerna updated,然后执行 lerna publish
    • 全部发布, 执行 lerna publish
  • 如果不想生成版本记录,以及不发布版本,则直接执行 git push

总结

  • 以往的仓库管理方式的不足之处
  • 使用 lerna 之后,package 之间存在互相的依赖项,lerna 会自动对其管理,不需要手动更新 package.json 中依赖项的版本号。
  • 一个基本完整的 lerna 使用介绍

参考文章:lerna+yarn workspace+monorepo项目的最佳实践(掘金)