天天看点

lerna 项目中集成 babel lint-staged husky eslintlerna 项目中集成 babel lint-staged husky eslint

lerna 项目中集成 babel lint-staged husky eslint

Monorepo 是针对单仓库、多 package 的流行解决方案, lerna 是它的一种实现。

说明

重要 package 版本
  • “lint-staged”: “^10.5.3”,
  • “eslint”: “^7.17.0”,
  • “husky”: “^4.3.6”,
  • “lerna”: “^3.22.1”
  • “@babel/core”: “^7.12.10”
因为项目采用 lerna 这种 monorepo 解决方案,实际的工作区在 根目录下的子级目录,这样就会对我们使用这些工具造成不可避免的影响(安装在顶级目录还是子级目录?怎么协同?多个子项目之间相互影响?),要解决这些影响,需要我们做如下的配置。

eslint 在 lerna 项目中的配置

在 lerna 这种 monorepo 项目中,由于会有多个子项目存在,不同子项目对 eslint 的使用可能大同小异,

.eslintrc.js

这个 eslint 配置文件会在每个使用 eslint 的子项目中单独创建,项目顶层不创建 .eslintrc.js.
如何获得 eslint 错误提示?
    1. 开发时我们主要是通过代码编辑器的反馈来获得 eslint 的提示;
    1. 打包 build 时,我们是通过打包工具(Webpack, Rollup)的 eslint 插件 (eslint-loader, @rollup/plugin-eslint)来链接 eslint ,且在命令行中得到 eslint 的反馈。
    1. commit 时,会通过钩子直接触发 eslint 检查,并反馈在命令行中
针对后两种情况, 只要命令是在子项目根目录运行的 eslint 就会以子项目根目录的配置文件为准,因此不用过多担心子项目之间的 eslint 互相影响。
针对 第一种情况,我们需要确保代码编辑器能够正确的获得 eslint 配置文件的作用范围,经测试 WebStorm 不需要经过配置即可识别当前子项目的 eslint 配置文件. VSCode 的话需要我们给VSCode 的 WorkSpace 做如下设置
// /.vscode/settings.json
{
    // 那个子项目用到了 eslint 都需要手动添加进来,否则 vscode eslint 提示会失效(通配符不可用)
  "eslint.workingDirectories": [
    "packages/rollup-ui", 
    "packages/mjz-ui",
  ],
  "eslint.packageManager": "yarn",
}
           

babel 在 lerna 项目中的集成

查看官方文档 ,我们可以了解到Babel将工作目录视为其逻辑“根”,如果您想在特定的子程序包中运行Babel工具而不将Babel应用于整个仓库,则会引起问题,所以 在 monorepo 的项目下我们设置 babel 需要有一定的规范
  1. 在项目顶层目录下创建 一个 babel.config.json 并给其设置 babelrcRoots 选项,这个选项用来设置那些子 package 会被 babel 视为"根“(不被 babel 视为根的子 程序包中的 babelrc 配置文件不会生效)
// /babel.config.js
module.exports = function(api) {
  api.cache(true);
  return { 
    babelrcRoots: [
        ".",
        "packages/*" // 将子程序包都作为工作目录
     ] 
  };
};
           
  1. 在子 package 中设置 .babelrc.js 作为配置文件而不是 babel.config.js
以上配置好后即可正确的使用 babel 了

lint-staged husky 在 lerna 项目中集成

  • husky: 用来做 git 的钩子,通常我们在 git commit 之前触发 pre-commit 钩子,然后通过在 husky 中配置这个钩子触发后的执行命令
  • lint-staged: 用来帮助我们快速的做代码校验,例如我们用 eslint 做代码校验,只能校验全量的代码(js 或者 ts),而 lint-staged 可以查询 git 的工作区,筛选出与上一个 commit 相比较有改动的代码,结合 eslint 后我们就可以仅校验这部分改动的,从而减少时间。理论上讲 lint-staged 只是帮我们筛选出有过改动的文件,然后通过它的 配置文件, 再次出发其他脚本进行对应类型文件的校验
// lint-staged.config.js 针对不同类型的改动文件执行对应的脚本进行校验
module.exports = {
  'src/**/*.{less,css,md,html}': ['prettier --write'],
  'src/**/*.{js,jsx}': ['prettier --write', 'yarn lint:js'],
  'src/**/*.{ts,tsx}': ['prettier --write', 'yarn lint:ts'],
};
           
在 lerna 项目中,由于所有子项目公用一个 repo 源代码仓库,因此它的 husky 钩子只能建立在最顶层目录;
每次 commit 都很有可能是多个子项目都有改动,这个时候使用 lint-staged 时,就不但要区分文件类型,还要区分改动文件所在的子项目(因为不同的子项目可能会有不同的校验处理),lerna 项目中实现这个能力有如下三种方法
  1. 将 lint-staged 安装到最顶层,这样在每次 pre-commit 时都借助 husky 触发 lint-staged 执行,然后在lint-staged 中通过匹配路径来决定哪些子项目的 校验执行
.
├── lerna.json
├── package.json
├── .huskyrc.js
├── lint-staged.config.js
├── packages
│   ├── mjz-ui
│   │   ├── package.json
│   │   ├── rollup.config.js
│   │   ├── src
│   │   └── tsconfig.json
│   └── rollup-ui
│       ├── package.json
│       ├── rollup.config.js
│       ├── src
│       ├── tsconfig.json
└── yarn.lock
           
// lint-staged.config.js 根据路径匹配子项目
module.exports = {
    'packages/mjz-ui/src/**/*.{js,jsx}': 'cd packages/mjz-ui && yarn lint:js',
    'packages/rollup-ui/src/**/*.{ts,tsx}': 'cd packages/rollup-ui && yarn lint:ts' ,
};
           
  1. 将 lint-staged 安装到每一个子项目,然后在 husky 触发钩子后执行所有 子项目的 lint-staged
// /package.json
{
  "scripts": {
    "precommit:prj-1": "cd prj-1 && npm run lint-staged",
    "precommit:prj-2": "cd prj-2 && npm run lint-staged",
    "precommit": "npm-run-all precommit:*" // husky 触发这个命令,然后这个命令触发所有子项目的 lint-staged
  },
}
           
  1. 以上两个方案可以用作非 lerna 的 monorepo 项目中,针对 lerna 项目我们可以使用 lerna 命令来实现对“哪个子项目有修改”的判断;同样 lint-staged 需要安装到任意一个需要做校验的子项目中
.
├── lerna.json
├── package.json
├── .huskyrc.js
├── lint-staged.config.js
├── packages
│   ├── mjz-ui
│   │   ├── package.json
│   │   ├── lint-staged.config.js // 这里只要关系当前子项目的改动即可
│   │   ├── rollup.config.js
│   │   ├── src
│   │   └── tsconfig.json
│   └── rollup-ui
│       ├── package.json
│       ├── lint-staged.config.js
│       ├── rollup.config.js
│       ├── src
│       ├── tsconfig.json
└── yarn.lock
           
// /.huskyrc.js
module.exports = {
  hooks: {
    'commit-msg': 'commitlint -E HUSKY_GIT_PARAMS',
    'pre-commit': 'lerna run --concurrency 1 --stream lint-staged --since HEAD --exclude-dependents' // 通过 lerna 命令查到哪个子项目有改动,并除服这个子项目对应的 lint-staged
  }
}
           
综上,针对 lerna 项目,我会选择第三种方案实现, 因为每一个子项目的 lint-staged 应该只关心当前子项目的(即lint-staged 配置文件放在子项目下),顶层目录不应该包含“关于怎么校验某个子项目代码”的逻辑,因此排除方案一;使用第三种方案同时还能兼顾“仅对有改动的文件做校验”,而第二种方案不能做到这一点。

继续阅读