前言
有幸读到一文,醍醐灌顶:if 我是前端团队 Leader,怎么制定前端协作规范?。有所启发,想写一篇文章如何一步步走完一个项目完善的过程。
以前公司项目也有类似实践经历,本文以搭建一个 UI 框架为引子,从新梳理记录这一过程实践心得。
- 至少要准备两个项目 一个项目用于管理UI框架,一个项目用于UI使用文档
知识准备
Monorepo
Monorepo 是管理项目代码的一个方式,指在一个项目仓库 (repo) 中管理多个模块/包 (package),不同于常见的每个模块建一个 repo。它的主要使用场景如下。
- 我想看一个 pacakge 的代码、了解某段逻辑,不需要找它的 repo,直接就在当前 repo,直接在当前 repo 修改,统一测试、统一发版
- 当某个需求要修改多个 pacakge 时,不需要分别到各自的 repo 进行修改、测试、发版或者
npm link
。
方式一:yarn workspace
这里参考的是 element3 原理:自动在当前node_modules 下生成子项目的引用
方式二: lerna管理package
目前社区中最主流的方案,也是yarn官方推荐的方案,是集成yarn workspace和lerna。使用yarn workspace来管理依赖,使用lerna来管理npm包的版本发布。
接下来项目目录结构处理按方式一
处理
[一] 项目组织规范
├─base-ui
│ └─packages #
│ │ └─base-ui # UI组件框架
│ │ └─md-loader # 作为.md文件加载的库
│ │ └─website # UI组件使用文档网站
│ └─scripts #
│ │ └─verifyCommit # 校验提交信息是否符合规范
│ └─package.json # 校验提交信息是否符合规范
│ └─README.md # 项目说明及贡献规范
复制代码
1.1 初始化根项目
# 初始化根项目
vue create base-ui # 选择vue3 和 yarn进行包管理
cd base-ui
mkdir packages # 用于存放子项目
复制代码
之后配置private与workspaces,删除name
// base-ui package.json
{
"private": true,
"workspaces": [
"packages/*"
]
}
复制代码
1.2 初始化子项目一(website)
cd packages
mkdir website
npm init -y
复制代码
由于项目选用vue作为主框架,因此
- 将src 与 public 目录移动到该项目下
- 根据需求修改scripts
- 执行脚本检验服务是否正常
1.3 初始化子项目二(base-ui)
cd packages
mkdir base-ui
cd base-ui
npm init -y
复制代码
1.4 初始化子项目二(md-loader)
cd packages
mkdir md-loader
cd md-loader
npm init -y
复制代码
接下来在根目下执行yarn
命令就可以将所有子项目依赖安装上,开发过程中可以相互引用。
[二] 梳理子项目目录结构及依赖
这里参考的是 element3
2.1 子项目一(webs-site)
2.1.1 依赖管理
引入base-ui
md-loader
依赖,以及vue 项目一些基础依赖
// package.json
{
"dependencies":{
"base-ui":"0.0.1",
"md-loader": "1.0.0"
}
}
复制代码
2.1.2 目录规范安排
// 目录结构大致跟vue项目类似,根据自己需求安排
├─webs-site
│ └─public #
│ └─src #
│ │ └─docs # 各个UI组件使用.md文档说明,通过md-loder 引入(需要在vue.config.js处理)
│ │ └─其它
复制代码
2.2 子项目二(base-ui)
// 目录安排
├─base-ui
│ └─scripts #
│ └─packages # theme-chalk
│ │ └─Alert # UI 组件
│ │ │ └─__test__ # UI 组件测试用例
│ │ │ │ └─Alert.spec.js
│ │ └─theme-chalk # 组件样式文件 scss
│ └─src #
│ └─tests # 工具类测试用例
、
复制代码
梳理需要实现功能功能
- rollup 打包
- gulp 构建处理scss样式
2.2.1 使用rollup实现组件打包
2.2.1.1 安装rollup依赖
{
"@rollup/plugin-babel": "^5.2.2",
"@rollup/plugin-commonjs": "^17.0.0",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^11.0.0",
"@rollup/plugin-replace": "^2.3.4",
"rollup": "^2.56.3",
"rollup-plugin-peer-deps-external": "^2.2.4",
"rollup-plugin-scss": "^2.6.1",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.29.0",
"rollup-plugin-vue": "^6.0.0",
}
复制代码
安装完成,配置rollup.config.js
,这里省略可以参考element3配置
2.2.1.2 babel相关插件
如需支持babel转换,需要安装一下依赖,还需要配置.babelrc
{
"@babel/core": "^7.12.10",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.1",
"@babel/plugin-proposal-optional-chaining": "^7.12.7",
"@babel/preset-env": "^7.12.10",
"@babel/preset-typescript": "^7.12.7",
"@vue/babel-plugin-jsx": "^1.0.0-rc.4",
}
复制代码
2.2.1.3 打包验证
配置scripts命令,执行npm run build:lib
打包验证dist
输出文件是否正常
"scripts": {
"build:lib": "rollup -c"
}
复制代码
2.2.2 使用 gulp 编译scss
2.2.2.1 安装gulp相关依赖
{
"cp-cli": "^2.0.0",
"gulp": "^4.0.2",
"gulp-autoprefixer": "^7.0.1",
"gulp-cssmin": "^0.2.0",
"gulp-sass": "^4.1.0",
}
复制代码
2.2.2.2 配置gulpfile
- 用gulpfile.js 编译转换css
- 执行
build:theme
命令验证lib目录是否生成css 文件
"scripts": {
"build:theme": "gulp build --gulpfile packages/theme-chalk/gulpfile.js && cp-cli packages/theme-chalk/lib lib/theme-chalk"
}
复制代码
3.1 子项目 md-loader
这里不做展开直接使用 element3 源码
[三] 搭建测试框架
3.1 单元测试(jest)
这里选择jest
作为单元测试框架
3.1.1 安装jest依赖
{
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/vue": "^5.8.2",
"@vue/babel-plugin-jsx": "^1.0.0-rc.4",
"babel-jest": "26.3.0",
"typescript": "^4.1.2",
"@types/jest": "^27.0.1",
"@vue/test-utils": "^2.0.0-rc.13",
"@vue/cli-plugin-unit-jest": "^4.5.13",
"ts-jest": "26.4.4",
"vue-jest": "^5.0.0-alpha.10",
"jest": "26.0.0",
"jest-environment-jsdom-sixteen": "^2.0.0"
}
复制代码
3.1.2 jest配置
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jest-environment-jsdom-sixteen',
setupFilesAfterEnv: ['./scripts/setupJestEnv.js'],
roots: ['<rootDir>/src', '<rootDir>/packages', '<rootDir>/tests'],
transform: {
'^.+\\.vue$': 'vue-jest',
'^.+\\js$': 'babel-jest'
},
moduleFileExtensions: ['vue', 'js', 'json', 'jsx', 'ts', 'tsx', 'node'],
testMatch: [
'**/tests/**/?(*.)+(test).[jt]s?(x)',
'**/tests/**/*spec.[jt]s?(x)',
'**/__tests__/**/*.spec.js'
],
moduleNameMapper: {
'^element-ui(.*)$': '<rootDir>$1',
'^main(.*)$': '<rootDir>/src$1',
'^lodash-es$': 'lodash'
},
transformIgnorePatterns: ['<rootDir>/node_modules/(?!lodash-es)']
}
复制代码
3.1.3 验证jest测试用例
配置scripts命令,在__test__
目录下写几个测试用例验证是否正常
"scripts": {
"test": "jest --runInBand"
},
复制代码
3.2 UI自动化测试(cypress)
这里我们选择自动化测试框架是cypress 具体用法可以参考 github.com/cypress-io/…
{
"cypress": "^8.3.1",
"@cypress/vite-dev-server": "^2.0.8",
"@vitejs/plugin-vue": "^1.2.2",
"vite": "^2.2.3",
"local-cypress": "^1.2.2"
}
复制代码
配置
{
"scripts":{
"cy": "cypress open-ct"
}
}
复制代码
具体业务中如何编写测试用例可以参考 cypress-example-recipes
[四] 规范工作流
4.1 开发规范
4.1.1 编码规范
使用ESLint+Prettier来统一前端代码风格,相关配置如下:.prettierrc
和 .eslintrc.js
安装相关依赖
{
"@typescript-eslint/eslint-plugin": "^4.11.0",
"@typescript-eslint/parser": "^4.11.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/eslint-config-prettier": "^6.0.0",
"@vue/eslint-config-typescript": "^7.0.0",
"babel-eslint": "^10.1.0",
"eslint": "^7.15.0",
"eslint-plugin-json": "^2.1.2",
"eslint-plugin-prettier": "^3.2.0",
"eslint-plugin-vue": "^7.2.0",
"prettier": "^2.2.1",
}
复制代码
4.1.2 提交信息规范
组织好的提交信息, 可以提高项目的整体质量. 至少具有下面这些优点:
- 格式统一的提交信息有助于自动化生成CHANGELOG
- 版本库不只是存放代码的仓库, 它记录项目的开发日志, 它应该要清晰表达这次提交的做了什么. 这些记录应该可以帮助后来者快速地学习和回顾代码, 也应该方便其他协作者review你的代码
- 规范化提交信息可以促进提交者提交有意义的、粒度合适的'提交' . 提交者要想好要怎么描述这个提交,这样被动促进了他们去把控提交的粒度
yorkie 功能与husky类似,通过触发commit-msg
和pre-commit
两个钩子,检验提交信息是否规范和检验代码是否符合编码规范。
{
"lint-staged": "^10.5.3",
"yorkie": "^2.0.0"
}
复制代码
4.2 发布流程规范
4.2.1 编写release脚本
这个脚本主要完成以下几个事情:
- 运行组件内单元测试(
npm run test
) - 打包组件(
npm run build
) - 更新版本号
- git add & git commit & git push
- git publish
- git tag & git push
配置脚本执行命令
{
"release": "node scripts/release.js"
}
复制代码
4.2.2 验证是否发布成功
通常来说我们使用的镜像链接有以下这些:
-
国内常用镜像(淘宝镜像): registry.npm.taobao.org
-
npm官方镜像: registry.npmjs.org
-
yarn 官方镜像:registry.yarnpkg.com/
-
个人github镜像:npm.pkg.github.com/OWNER
但是有些只提供下载,不提供发布作品。这里我们选择npm这个渠道
npm login --scope=@OWNER --registry=https://registry.npmjs.org
# OWNER 为个人用户名
# username 请填入你的npm用户名(不一定是github用户名,是你要发布包管理服务的账号)
# password 请填入上面生成的
# email 请填入邮箱地址
# 当然你也可以发布到 github,具体细节不做展开
# npm login --scope=@OWNER --registry=https://npm.pkg.github.com
复制代码
4.2.2.1 发布前准备
发布之前得先注册一个npm 官网账号,也可以命令行注册
# 注意仓库地址是否官网 : --registry https://registry.npmjs.org
# (注意这里跟GitHub账号不是同一个,输入usename 、password 、email)
# 需要邮箱验证后才可以使用
npm addUser 或 npm login
复制代码
4.2.2.2 登录 npm 账号
如果直接执行npm run lease,会出现如下问题:
// 未登录
UnhandledPromiseRejectionWarning: Error: Command failed with exit code 1: yarn publish --new-version 0.0.2 --registry https://registry.npmjs.org --access public
error No token found and can't prompt for login when running with --non-interactive
复制代码
Couldn't publish package: "https://registry.npmjs.org/base-ui: You do not
have permission to publish \"base-ui\". Are you logged in as the correct user?"
复制代码
注意:淘宝镜像
只是提供下载,如果你要 npm login
、npm publish
登陆发布自己的作品, 你必须要切换到【官方货源】。先登录再发布:
npm login --registry https://registry.npmjs.org
4.2.2.3 发布包
执行release命令,并制定发布版本号
npm run release -- --v 0.0.2
复制代码
4.2.2.4 验证成果
等待脚本执行完
- 1、查看 npm 官网 是否有你的包,由于目前npm规定必须带有私有域名的包,上面需要把包名统一改为
@geek-tim/base-ui
- 2、查看GitHub上是否生成对应release 版本
4.3 生成 changelog
发布版本之后,记得更新changelog
{
"scripts": {
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s"
},
"devDependencies": {
"conventional-changelog-cli": "^2.1.1",
}
}
复制代码
[五] 组件开发流程
一切准备工作就绪,那我们就来开始组件开发吧。
- 前提当然要有一个组件开发测试环境
- 1、base-ui 子项目
- 新增组件具体实现
- 新增样式
- 新增测试用例
- 2、website子项目
- 新增组件使用文档
结语
本文主要参考 element3 源码大致实现一个简单UI框架搭建。涉及的知识点比较多,如Monorepo,yarn workspace,learn 。对一些框架或概念不熟悉的请自行查阅。