2021.4,项目参考:https://gitee.com/zaiMoe/my-monorepo
本文从0到1搭建一个项目,包含ts、vue、jest、linter(eslint、stylelint、prettier、ls-lint、husky)、docs(vuepress)、build(rollup)、dev(vite)
以及由于做了勇士而踩过的一些坑(一些新的库安装使用过程的问题)
-
多仓库Multirepo:每个项目都放在不同的仓库管理,然后通过npm包的方式引入,或者通过git-submodule引入使用。
-
单个仓库Monorepo:各个项目都放在一个仓库管理,每个项目称为package,不同的package可以单独发包。直接的好处是可以避免重复安装包,减少了磁盘空间的占用,并降低了构建时间,并且内部代码可以彼此相互引用。
两者的优缺点如下: (图片来源: https://juejin.cn/post/6844903911095025678)
当各个package之间有关联,依赖关系时,明显采用monorepo的方式管理更加合适。结合我们实际项目需求(utils、components、low-code-components、docs等等各个库之间的依赖关系,其他的业务项目的使用情况)以及......,决定采用monorepo来搭建我们的组建仓库。
两个都是monorepo
项目的管理工具和解决方案,他们会在在相互引用的包之间创建符号链接进行管理,以及依赖、发布和版本控制的管理。
lerna
是一个babel
自己维护自己的monorepo项目所开源出来的一个工具。它优化了使用git
和npm
管理多包存储库的工作流,所以我们一般用lerna
进行版本控制和npm包的发布管理。
lerna bootstrap
command: lerna add depend
lerna add vue
command: lerna add depend --scope=pkg
# 给所有的pkgB安装wepack
lerna add webpack --scope=pkgB
# 将本地的pakA安装到pkgB
lerna add pkgA --scope=pkgB
command:lerna run command
# 所有package执行build命令
lerna run build
# 按照拓扑排序规则执行命令,即依赖的顺序
lerna run --stream --sort build
# 单独运行一个模块
lerna --scope pkgA run build
command: lerna create pkgName [loc]
# 创建一个名为pkgC的项目,默认安装到lerna.json中packages字段下的第一个目录位置
lerna create pkgC
# 将docs安装到sites目录下
lerna create docs sites
# 检查自上次发布以来哪些软件包已经更新
lerna changed
# 生成changelog,并提交到远程,同时创建对应的tag
lerna version --conventional-commits
# 生成changelog,并提交到远程,同时创建对应的tag
发布
lerna publish
# 清除项目中所有 node_modules
lerna clean
# 查看lerna和系统的信息
lerna info
yarn workspace
能更好的管理monorepo
多个项目中的依赖,可以为所有的packages安装统一的目录,也可以为单独的package安装单独的依赖,所有packages共享一个yarn.lock
文件。同时可以通过yarn
一键安装or更新多有的依赖。同时,yarn
还会分析依赖关系,将所有相同的依赖安装到根目录的node_modules
下,避免重复安装,避免磁盘空间的浪费。
所以我们使用yarn workspace
来管理依赖。
说明:
- 根目录称为workspace-root
- 各个(子)项目之间,没有node_modules,但如果依赖的版本不一致的话,例如project-1依赖了不同版本的project-2,则会单独安装
- 工作区名称指packjson.json中的name
command: yarn add xxx -D -W
不同于lerna,这个是安装在root的package.json里
(1)安装外部依赖
command:yarn workspace <workspace_name> <command>
# 往 pkg1 添加 rollup 开发依赖
yarn workspace pkg1 add rollup -E
# 从 pkg1 移除 rollup 依赖
yarn workspace pkg1 remove rollup
(2)安装内部依赖
command:yarn workspace <workspace_name> <command> <workspace_name>
# 将pkg2添加到pkg1的package中,若不加版本号则是从远程添加
yarn workspace pkg1 add pkg2@version -E
# 将pkg2从pkg1的package中移除
yarn workspace pkg1 remove pkg2@version
# 显示当前各 workspace 之间的依赖关系树。
yarn workspaces info [--json]
# 执行各个项目中的命令
yarn workspaces run command
-
开启
yarn workspace
(yarn1.x版本默认是关闭的)yarn config set workspaces-experimental true
-
在项目中安装lerna:
# 当前发布的4.x有坑,且文档不全,还是先用3.x的版本吧,勇士请随意上 yarn add [email protected] # npx lerna init // 初始化lerna项目
-
此时的目录结构
│ lerna.json │ package.json │ yarn.lock └─packages
-
修改
lerna.json
{ "packages": [ "packages/*" ], "version": "independent", // 默认是1.0.0 "npmClient": "yarn", // 采用yarn来管理依赖,而不是npm "useWorkspaces": true, // 使用yarn workspace }
version
-lerna的两种模式:- Fixed(固定模式):默认。所有package 共用一个版本号,任何
package
的major change
均会导致所有包都会进行major version
的更新。 - Independent(独立模式):每个包都有自己独立的版本号。lerna会配合git,检查文件变动,只发布有改动的package。
- Fixed(固定模式):默认。所有package 共用一个版本号,任何
-
修改
package.json
{ "name": "my-monorepo", // 项目名称 "version": "1.0.0", "license": "MIT", "private": true, // 防止误发布 "workspaces": [ // 添加工作区间 "packages/*" ], "devDependencies": { "lerna": "^3.22.1" } }
-
创建两个项目 创建项目可以手动在
packages
目录下创建,也可以通过lerna
,这里我们用lerna
来创建:npx lerna create @my-monorepo/utils npx lerna create @my-monorepo/components
同样,给
package.json
添加private: true
防止误发布创建完并且调整后的目录结构:
|-- lerna.json |-- package.json |-- packages | |-- components | | |-- README.md | | |-- package.json | | `-- src | | |-- __test__ | | | `-- index.spec.ts | | `-- index.ts | `-- utils | |-- README.md | |-- package.json | `-- src | |-- __test__ | | `-- index.spec.ts | `-- index.ts `-- yarn.lock
基于
lerna
和yarn workspace
项目的初始化完毕
yarn add typescript -W
npx tsc --init // 生成tsconfig.json文件
这个时候,各个package
都是在共享这份配置文件,假如我们要单独打包utils
工具库的代码:
// package.json
"scripts": {
"build": "lerna run --stream --sort build"
}
// utils/package.json
"scripts": {
"build": "tsc --outDir ./lib"
}
执行yarn build
结果却是打包了所有的package
这个时候可以给每个package
增加tsconfig.json
作为单独的目录,继承外层的配置,这样tsc
就会以这层作为根路径打包了
// utils/tsconfig.json
{
"extends": "../../tsconfig.json",
"include": [
"src"
]
}
再次打包:
eslint结合prettier进行统一的风格代码格式,网上介绍很多,不做说明
yarn add -D -W prettier
// eslint 套餐...
yarn add -D -W
eslint
eslint-config-prettier
eslint-plugin-prettier
@typescript-eslint/eslint-plugin
@typescript-eslint/parser
说明:
eslint-config-prettier
: 截止目前大部分都是介绍这个的,8.x版本开始已经不用这个了,而是换成了下面了这个,如果实在搞不定,可以安装低版本的按照其他文章配置即可eslint-plugin-prettier
:3.3.x版本使用这个,但是还需要依赖上面的一个文件,见官方推荐配置- 后面2个按照其他文档进行配置即可,无坑
-
(略)添加配置文档
eslintrc
、eslintignore
、prettierrc
、prettierignore
、editorconfig
、prettierrc + editorconfig
保障在不同ide上的显示效果的一致性 -
添加命令
// package.json
"lint": "lerna run lint",
"lint:fix": "lerna run lint:fix",
"prettier": "prettier --write **/*.{js,jsx,tsx,ts,less,vue,md,json}",
// 其他packages按需添加
"lint:ts": "tsc --noEmit",
"lint:es": "eslint ./src --ext .vue,.js,.jsx,.ts,.tsx",
"lint": "yarn lint:ts && yarn lint:es",
"lint:fix": "yarn lint:es --fix"
执行yarn lint
报错,然后执行yarn lint:fix
修复,在执行yarn prettier
统一格式所有的文件
注意:eslint为了提高编译速度,是不会检查ts的类型错误的,所以采用tsc取检查,见上面的
lint:ts
tip:如果是webstrom党,也可以像vscode一样保存自动修复,参考
yarn add -D -W
stylelint
stylelint-config-prettier
stylelint-config-rational-order
stylelint-order
stylelint-config-standard
stylelint-declaration-block-no-ignored-properties
说明:
style-config-rational-order+stylelint-order
:统一css属性的书写顺序,见 浏览器渲染原理stylelint-config-prettier
:结合prettier
的规则格式化css
stylelint-declaration-block-no-ignored-properties
:防止冲突导致的属性无效,见官方文档- 其他略
- (略)添加配置文档:
.stylelintrc.json
、.stylelintignore
- 添加命令
// 其他package下的package.json按需修改
"lint:style": "stylelint ./src/**/*.{vue,less}",
"lint": "yarn lint:ts && yarn lint:es && yarn lint:style",
"lint:fix": "yarn lint:es --fix && yarn lint:style --fix",
添加一个样式文件测试下,pass
tip: webstrom中也可以像eslint一样保存修复,参考
yarn add -D -W @ls-lint/ls-lint
-
添加配置文件
.ls-lint.yml
,详细配置见官方网 -
添加命令
// 根目录package.json
"lint:ls": "ls-lint",
"lint": "yarn lint:ls && lerna run lint"
执行命令yarn lint:ls
测试下
todo
有很多文章讲的很好,这里就不详细说明了,没坑,找一篇合适的照着配置就行,这里推荐:
commit规范+commitlint+CHANGELOG自动生成一条龙服务
配置的时候可以跳过生成CHANGELOG.md
和校验的部分,在后面章节会讲
yarn add -D -W
commitizen
cz-customizable
- 添加配置文件:
.cz-config.js
- 添加命令
// 根路径package.json
// script:
"commit": "git cz -a",
// 自定义适配器
"config": {
"commitizen": {
"path": "node_modules/cz-customizable"
}
}
然后运行yarn commit
进行代码提交
当然,如果不想用yarn commit | git cz
代替原来原来的git commit
,那么可以通过git-hook修改:
"prepare-commit-msg": "exec < /dev/tty && git cz --hook || true" // 配个下面的husky可以修改钩子
上面介绍了那么多校验规则,但在团队中,不可能实时记得在提交代码的时候,将全部校验都过一遍,那么我们可以有其他方式来自动校验,如:
- CI-CD:在提交到远程仓库的个人分支的时候,跑一边校验命令,错误的时候发送通知给对应的提交人(这个可以另起一篇说明,跳过)
- 利用
git-hook
,在不同的动作执行不同的命名,比如上面的提到的,利用prepare-commit-msg
设置git cz
默认替换git commit
,下面介绍下这种方案(当然,每次提交代码的时候都要等较长时间的校验hhh)
yarn add -D -W
husky
@commitlint/cli
commitlint-config-cz
说明: 看官方介绍吧...
注意,当前的husky为5.x的版本,与绝大多数文章的配置方法都不同,如果不想折腾请安装@4.x的版本
- 添加配置文档:
.commitlintrc
- 设置钩子
// package.json添加并执行
"prepare": "yarn husky install"
接着执行yarn husky install
会在根目录增加一个.husky
文件,在这里文件里面添加钩子。
执行命令yarn husky add .husky/commit-msg 'npx commitlint --edit "$1"'
,会在.husky
下增加commit-msg
文件,这个就是对应的钩子
执行命令yarn husky add .husky/pre-commit 'yarn lint',会在
.husky下增加
pre-commit文件,在commit
之前便会执行yarn lint
校验所有的代码
更多的钩子请参考 官方文档 ,几乎没什么坑
上面步骤中,pre-commit
会在commit的时候执行yarn lint
校验所有文件,导致提交时间过长,所以可以考虑下是否可以只校验改动的部分呢?答案是有的,采用lint-staged
这个库就能实现
yarn add -D -W lint-staged
// package.json
// script
"lint:staged": "lint-staged"
// 增加
"lint-staged": {
"**/*.{js,ts,less,vue,json,md}": ["prettier --write"],
"**/*.{js,ts,vue}": ["eslint --fix"],
"**/*.{less,vue}": ["stylelint --fix"]
}
接着打开.husky/pre-commit
,修改为:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn lint:ls
yarn lint:staged
接着提交代码便会看到校验了:
https://juejin.cn/post/6844904062987550733
- 当前项目没有
git status
为空 - 新增
.gitattributes
文件 - 加入配置,提交
lerna
本身自动了发布包的功能和相关的命令,常用带有
查看可以发布的包
标识自上一个版本以来发生了变化的包,然后提供可以升级的bump
版本给用户选择,然后将相关的变动(package.json版本号更新)提交,然后打上tag
推送到远程仓库,并且会自动生成CHANGELOG.md
记录各个版本的变动
这一过程可以加上--conventional-commits
来自动化,跳过选择阶段。
同时,为了符合我们之前commitlint
的校验,需要设置下:
// package.json
"script": {
"version": "lerna version --conventional-commits"
}
// lerna.json
"command": {
"version": {
"ignoreChanges": ["*.md", "**/*.test.ts", "**/*.spec.ts"],
"message": "chore(release): publish"
}
}
执行npm run version
,然后远程仓库便会出现一条chore(release): publish
的记录。
这一步才是真正的发布,from-git
会识别lerna version
标记的包,并将它们发布到npm
上面。
lerna publish
默认会会执行lerna version
,我们可以在lerna.json
中对发布的进行一些配置
"command": {
"publish": {
"allowBranch": ["master", "main"], // 限制 lerna version 只能在主分支执行
"conventionalCommits": true,
"exact": true, // 关联包锁定
"ignoreChanges": ["ignored-file", "*.md"],
"message": "chore(release): publish %s",
"registry": "https://registry.npmjs.org" // 发布的地址,如私服地址
}
}
然后执行lerna publish from-git
,则会成功发布,如果发现发布错误,执行npm unpublish
即可撤回
lerna version --conventional-commits -y preminor --preid bi // 将 1.0.0 -> 1.1.0-bi.0
lerna version --conventional-commits -y --preid bi // 将 1.1.0-bi.0 -> 1.1.0-bi.1
- 先以第一个发布一个新版本,
- 然后在去掉 preminor,之后发布就会只修改后面的版本号(fix/feat相同)
理想的文档是既能写文档,同时也能用于开发,查了一下当前有的文档库
- marked + highlight.js:将markdown转成页面,需要自己搭建页面项目,自定义样式等,灵活度较高,但需要折腾
- docsify:无需构建,在页面将md转成html,不需要每次打包生成html,能实时更新,本身支持解析vue,同样需要折腾
- gatsby:基于react开发的,如@antv/gatsby-theme-antv主题等
- Gridsome:基于vue开发,gatsby的替代品,但目前还在开发阶段,可能有坑
- VuePress:将md转成md,天然支持vue,中文文档
- vitePress: 还不能用
- Storybook:ui 组件开发管理的工具,并且每个组件都有一个独立开发调试环境
由于是基于vue
的开发,比较成熟的只有vuepress
、storybook
了。
- demo和示例代码写一套,最好能直接引入
.vue
文件,即有demo又有示例代码 - 能使用
packages
下的组件
对于vuepress
和storybook
都支持,当时简单搜索了一下,在vuepress
中发现了插件
Demo Container
满足了需求,加上为了降低上手成本,选择了vuepress
。
在使用Demo Container
的时候,发现不支持直接引用vue文件作为示例以及代码展示,作者已经不维护了,于是fork了一份自己搞。
vuepress作为本地开发环境比较慢,不能忍受,所以直接上了vite搭建了一个本地开发环境,简单易上手!
rollup从入门到打包一个按需加载的组件库 你的Tree-Shaking并没什么卵用 VUE UI组件库按需引入的探索 聊聊 Webpack4 的 Tree Shaking Webpack 4 Tree Shaking 终极优化指南 Webpack5 新特性业务落地实战
@types/jest
,eslint-plugin-jest
,jest
,ts-jest
'jest/globals': true
jest https://www.yuque.com/sunluyong/node/gq5qaa https://fe.rualc.com/note/jest.html#zi-liao 参考 参考2 参考3
preset: 'ts-jest',
root: ['>rootDir>/packages']
testEnvironment: 'jsdom',
moduleFileExtensions: [ts,tsx,js,json]
// coverage
collectCoverage: true
coveragePathIgnorePatterns: [node_module, es, lib, dist]
- All in one:项目级 monorepo 策略最佳实践
- 基于 Lerna 管理 packages 的 Monorepo 项目最佳实践
- 官方:Workspaces
- Yarn Workspace 使用指南
- lerna指令总览
- 浏览器渲染原理 和 alloyteam-css属性声明顺序
- 规范Git提交说明
- commit规范+commitlint+CHANGELOG自动生成一条龙服务
- 项目组件库用的vue是2.5.7,而vuepress用的是2.6.12,会导致vue-template-compiler的版本对应不上,解决方式: 将2.5.7的相关依赖装全局,这样文档包的依赖就会自己安装在目录下,不会影响外部