Monorepo 是针对单仓库、多 package 的流行解决方案, lerna 是它的一种实现。
重要 package 版本
因为项目采用 lerna 这种 monorepo 解决方案,实际的工作区在 根目录下的子级目录,这样就会对我们使用这些工具造成不可避免的影响(安装在顶级目录还是子级目录?怎么协同?多个子项目之间相互影响?),要解决这些影响,需要我们做如下的配置。
在 lerna 这种 monorepo 项目中,由于会有多个子项目存在,不同子项目对 eslint 的使用可能大同小异,
.eslintrc.js
这个 eslint 配置文件会在每个使用 eslint 的子项目中单独创建,项目顶层不创建 .eslintrc.js.
如何获得 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将工作目录视为其逻辑“根”,如果您想在特定的子程序包中运行Babel工具而不将Babel应用于整个仓库,则会引起问题,所以 在 monorepo 的项目下我们设置 babel 需要有一定的规范
- 在项目顶层目录下创建 一个 babel.config.json 并给其设置 babelrcRoots 选项,这个选项用来设置那些子 package 会被 babel 视为"根“(不被 babel 视为根的子 程序包中的 babelrc 配置文件不会生效)
// /babel.config.js
module.exports = function(api) {
api.cache(true);
return {
babelrcRoots: [
".",
"packages/*" // 将子程序包都作为工作目录
]
};
};
- 在子 package 中设置 .babelrc.js 作为配置文件而不是 babel.config.js
以上配置好后即可正确的使用 babel 了
// 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 项目中实现这个能力有如下三种方法
.
├── 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' ,
};
// /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
},
}
.
├── 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 配置文件放在子项目下),顶层目录不应该包含“关于怎么校验某个子项目代码”的逻辑,因此排除方案一;使用第三种方案同时还能兼顾“仅对有改动的文件做校验”,而第二种方案不能做到这一点。