搭建 vue3 规范的项目环境
# 搭建 vue3 规范的项目环境
# 技术栈
编程语言:TypeScript 4.x + JavaScript
构建工具:Vite 2.x
前端框架:Vue 3.x
路由工具:Vue Router 4.x
状态管理:Vuex 4.x
UI 框架:Element Plus
CSS 预编译:Stylus / Sass / Less
HTTP 工具:Axios
Git Hook 工具:husky + lint-staged
代码规范:EditorConfig + Prettier + ESLint + Airbnb JavaScript Style Guide
提交规范:Commitizen + Commitlint
# 一、架构搭建
建议 nodeJs 版本 >= 12
# 1. vite
1、使用 Vite 快速初始化项目雏形
yarn create @vitejs/app
2、选择项目使用的模板
3、进入项目目录,安装项目依赖
4、启动 npm run dev
如图,表示 Vite + Vue3 + TypeScript 简单的项目骨架搭建完毕,下面我们来为这个项目集成 Vue Router、Vuex、Element Plus、Axios、Stylus/Sass/Less
# 2. 配置 vite
Vite 配置文件 vite.config.ts
位于根目录下,项目启动时会自动读取。
本项目先做一些简单配置,例如:设置 @
指向 src
目录、 服务启动端口、打包路径、代理等。
关于 Vite 更多配置项及用法,请查看 Vite 官网 vitejs.dev/config/ (opens new window) 。
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// 如果编辑器提示 path 模块找不到,则可以安装一下 @types/node -> npm i @types/node -D
import { resolve } from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, 'src') // 设置 `@` 指向 `src` 目录
}
},
base: './', // 设置打包路径
server: {
port: 4000, // 设置服务启动端口号
open: true, // 设置服务启动时是否自动打开浏览器
cors: true // 允许跨域
// 设置代理,根据我们项目实际情况配置
// proxy: {
// '/api': {
// target: 'http://xxx.xxx.xxx.xxx:8000',
// changeOrigin: true,
// secure: false,
// rewrite: (path) => path.replace('/api/', '/')
// }
// }
}
})
# 规范目录结构
├── publish/
└── src/
├── assets/ // 静态资源目录
├── common/ // 通用类库目录
├── components/ // 公共组件目录
├── router/ // 路由配置目录
├── store/ // 状态管理目录
├── style/ // 通用 CSS 目录
├── utils/ // 工具函数目录
├── views/ // 页面组件目录
├── App.vue
├── main.ts
├── shims-vue.d.ts
├── tests/ // 单元测试目录
├── index.html
├── tsconfig.json // TypeScript 配置文件
├── vite.config.ts // Vite 配置文件
└── package.json
# 集成 vue-router@4
1、安装 vue-router
yarn add vue-router@4
2、创建 src/router/index.ts
文件
└── src/
├── router/
├── index.ts // 路由配置文件
3、配置路由
import {
createRouter,
createWebHashHistory,
RouteRecordRaw
} from 'vue-router'
import Home from '@/views/home.vue'
import Vuex from '@/views/vuex.vue'
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/vuex',
name: 'Vuex',
component: Vuex
},
{
path: '/axios',
name: 'Axios',
component: () => import('@/views/axios.vue') // 懒加载组件
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
根据本项目路由配置的实际情况,你需要在 src
下创建 views
目录,用来存储页面组件。
我们在 views 目录下创建 home.vue
、vuex.vue
、axios.vue
。
4、在 src/main.ts
中挂载路由
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index'
createApp(App).use(router).mount('#app')
# 集成 vuex
1、安装 vuex@next
yarn add vuex@next
2、创建 src/store/index.ts
文件
└── src/
├── store/
├── index.ts // store 配置文件
3、配置 vuex
import { createStore } from 'vuex'
const defaultState = {
count: 0
}
// Create a new store instance.
export default createStore({
state() {
return defaultState
},
mutations: {
increment(state: typeof defaultState) {
state.count++
}
},
actions: {
increment(context) {
context.commit('increment')
}
},
getters: {
double(state: typeof defaultState) {
return 2 * state.count
}
}
})
4、在 src/main.ts
中挂载 vuex
import { createApp } from 'vue'
import App from './App.vue'
import store from './store/index'
createApp(App).use(store).mount('#app')
# 集成 Element-plus
1、安装
yarn add element-plus
2、在 src/main.ts
中挂载 element-plus
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/lib/theme-chalk/index.css'
createApp(App).use(ElementPlus).mount('#app')
# 集成 axios
1、安装
yarn add axios
2、创建 src/utils/axios.ts
文件
└── src/
├── utils/
├── axios.ts // axios 配置文件
3、配置 axios
import Axios from 'axios'
import { ElMessage } from 'element-plus'
const baseURL = 'http://api.github.com'
const axios = Axios.create({
baseURL,
timeout: 2000
})
// 前置拦截器(发起请求之前的拦截)
axios.interceptors.request.use(
(response) => {
/**
* 根据你的项目实际情况来对 config 做处理
* 这里对 config 不做任何处理,直接返回
*/
return response
},
(error) => {
return Promise.reject(error)
}
)
// 后置拦截器(获取到响应时的拦截)
axios.interceptors.response.use(
(response) => {
/**
* 根据你的项目实际情况来对 response 和 error 做处理
* 这里对 response 和 error 不做任何处理,直接返回
*/
return response
},
(error) => {
if (error.response && error.response.data) {
const code = error.response.status
const msg = error.response.data.message
ElMessage.error(`Code: ${code}, Message: ${msg}`)
console.error(`[Axios Error]`, error.response)
} else {
ElMessage.error(`${error}`)
}
return Promise.reject(error)
}
)
export default axios
4、使用
<template>
<!-- src/views/axios.vue -->
<div>axios</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import axios from '../utils/axios'
export default defineComponent({
setup() {
axios
.get('/users/XPoet')
.then((res) => {
console.log('res: ', res)
})
.catch((err) => {
console.log('err: ', err)
})
}
})
</script>
# 集成 CSS 预编译器 Stylus/Sass/Less
本项目使用 CSS 预编译器 scss,直接安装为开发依赖即可。Vite 内部已帮我们集成了相关的 loader,不需要额外配置。
1、安装
yarn add sass
2、使用
<style lang="scss" scope>
...
</style>
至此,一个基于 TypeScript + Vite + Vue3 + Vue Router + Vuex + Element Plus + Axios + Stylus/Sass/Less 的前端项目开发环境搭建完毕
# 二、代码规范
本文讲解如何使用 EditorConfig + Prettier + ESLint 组合来实现代码规范化。
这样做带来好处:
- 解决团队之间代码不规范导致的可读性差和可维护性差的问题。
- 解决团队成员不同编辑器导致的编码规范不统一问题。
- 提前发现代码风格问题,给出对应规范提示,及时修复。
- 减少代码审查过程中反反复复的修改过程,节约时间。
- 自动格式化,统一编码风格,从此和脏乱差的代码说再见。
# 集成 EditorConfig 配置
EditorConfig 有助于为不同 IDE 编辑器上处理同一项目的多个开发人员维护一致的编码风格。
1、在项目根目录下增加 .editorconfig
文件:
# Editor configuration, see http://editorconfig.org
# 表示是最顶层的 EditorConfig 配置文件
root = true
[*] # 表示所有文件适用
charset = utf-8 # 设置文件字符集为 utf-8
indent_style = space # 缩进风格(tab | space)
indent_size = 2 # 缩进大小
end_of_line = lf # 控制换行类型(lf | cr | crlf)
trim_trailing_whitespace = true # 去除行首的任意空白字符
insert_final_newline = true # 始终在文件末尾插入一个新行
[*.md] # 表示仅 md 文件适用以下规则
max_line_length = off
trim_trailing_whitespace = false
2、VSCode 使用 EditorConfig
需要去插件市场下载插件 EditorConfig for VS Code
# 集成 Prettier
Prettier 是一款强大的代码格式化工具,支持 JavaScript、TypeScript、CSS、SCSS、Less、JSX、Angular、Vue、GraphQL、JSON、Markdown 等语言,基本上前端能用到的文件格式它都可以搞定,是当下最流行的代码格式化工具。
1、安装 Prettier
yarn add prettier
2、在项目根目录下创建 .prettierrc
文件
{
"useTabs": false,
"tabWidth": 2,
"printWidth": 100,
"singleQuote": true,
"trailingComma": "none",
"bracketSpacing": true,
"semi": false
}
更多 Prettier 配置 (opens new window)
3、VSCode 编辑器使用 Prettier 配置需要下载插件 Prettier - Code formatter
4、Prettier 安装且配置好之后,就能使用命令来格式化代码
# 格式化所有文件(. 表示所有文件)
npx prettier --write .
# 集成 Eslint
1、安装 ESLint
yarn add eslint
2、创建配置文件
# 1. 执行
npx eslint --init
# 2. 根据提示选择相关 eslint 配置
3、如果最后选择保存 eslint
配置为 javascript
,则会在根目录下生成 .eslintrc.js
module.exports = {
env: {
browser: true,
es2021: true,
node: true
},
extends: ['plugin:vue/essential', 'airbnb-base'],
parserOptions: {
ecmaVersion: 12,
parser: '@typescript-eslint/parser',
sourceType: 'module'
},
plugins: ['vue', '@typescript-eslint'],
rules: {}
}
4、VSCode 使用 ESLint 配置文件需要去插件市场下载插件 ESLint
5、设置 vscode 保存时格式化,在 VSCode 中的 setting.json
中 配置
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
# 解决 Prettier 和 ESLint 的冲突
通常大家会在项目中根据实际情况添加一些额外的 ESLint 和 Prettier 配置规则,难免会存在规则冲突情况。
本项目中的 ESLint 配置中使用了 Airbnb JavaScript 风格指南校验,其规则之一是代码结束后面要加分号,而我们在 Prettier 配置文件中加了代码结束后面不加分号的配置项,这样就有冲突了,会出现用 Prettier 格式化后的代码,ESLint 检测到格式有问题的,从而抛出错误提示。
解决两者冲突问题,需要用到 eslint-plugin-prettier 和 eslint-config-prettier。
eslint-plugin-prettier
将 Prettier 的规则设置到 ESLint 的规则中。eslint-config-prettier
关闭 ESLint 中与 Prettier 中会发生冲突的规则。
最后形成优先级:Prettier 配置规则
> ESLint 配置规则
。
1、安装插件
yarn add eslint-plugin-prettier eslint-config-prettier
2、配置 .eslint.js
module.exports = {
...
extends: [
'plugin:vue/essential',
'airbnb-base',
'plugin:prettier/recommended' // 添加 prettier 插件
],
...
}
这样,我们在执行 eslint --fix
命令时,ESLint 就会按照 Prettier 的配置规则来格式化代码,轻松解决二者冲突问题。
# 集成 husky 和 lint-staged
我们在项目中已集成 ESLint 和 Prettier,在编码时,这些工具可以对我们写的代码进行实时校验,在一定程度上能有效规范我们写的代码,但团队可能会有些人觉得这些条条框框的限制很麻烦,选择视“提示”而不见,依旧按自己的一套风格来写代码,或者干脆禁用掉这些工具,开发完成就直接把代码提交到了仓库,日积月累,ESLint 也就形同虚设。
所以,我们还需要做一些限制,让没通过 ESLint 检测和修复的代码禁止提交,从而保证仓库代码都是符合规范的。
为了解决这个问题,我们需要用到 Git Hook,在本地执行 git commit
的时候,就对所提交的代码进行 ESLint 检测和修复(即执行 eslint --fix
),如果这些代码没通过 ESLint 规则校验,则禁止提交。
实现这一功能,我们借助 husky
+ lint-staged
。
husky —— Git Hook 工具,可以设置在 git 各个阶段(pre-commit、commit-msg、pre-push 等)触发我们的命令。 lint-staged —— 在 git 暂存的文件上运行 linters。
# 配置 husky
1、使用 husky-init
命令快速在项目初始化一个 husky 配置
npx husky-init && npm install
此命令会做4件事
- 安装
husky
到开发依赖 - 在项目根目录下创建
.husky
目录 - 在
.husky
目录创建pre-commit hook
,并初始化pre-commit
命令为npm test
- 修改
package.json
的scripts
,增加"prepare": "husky install"
2、修改 .husky/pre-commit
hook 文件的触发命令:
# 借助 lint-staged 实现 eslint 只检测此次修改的文件
npx lint-staged
# 配置 lint-staged
lint-staged 这个工具一般结合 husky 来使用,它可以让 husky 的 hook 触发的命令只作用于 git add那些文件(即 git 暂存区的文件),而不会影响到其他文件。
1、安装
yarn add lint-staged
2、在 package.json
里增加 lint-staged
配置项
{
// ...
"lint-staged": {
"*.{vue,js,ts}": "eslint --fix"
},
// ...
}
这行命令表示:只对 git 暂存区的 .vue
、.js
、.ts
文件执行 eslint --fix
。
# 三、提交规范
# 设置提交规范
规范 commit message 的好处
- 首行就是简洁实用的关键信息,方便在 git history 中快速浏览。
- 具有更加详细的 body 和 footer,可以清晰的看出某次提交的目的和影响。
- 可以通过 type 过滤出想要查找的信息,也可以通过关键字快速查找相关提交。
- 可以直接从 commit 生成 change log。
1、安装 commitizen
和 cz-conventional-changelog
yarn add commitizen cz-conventional-changelog
2、配置
// package.json 中添加
{
// ...
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
// ...
}
3、使用 Commitizen
以前我们提交代码都是 git commit -m "xxx"
,现在改为 git cz
,然后按照终端操作提示,逐步填入信息,就能自动生成规范的 commit message。
# 自定义配置提交说明
从上面的截图可以看到,git cz
终端操作提示都是英文的,如果想改成中文的或者自定义这些配置选项,我们使用 cz-customizable 适配器。
1、安装 cz-customizable
yarn add cz-customizable
2、配置
// package.json 中修改 config.commitizen
{
// ...
"config": {
"commitizen": {
// "path": "./node_modules/cz-conventional-changelog" 改为下面的
"path": "./node_modules/cz-customizable"
}
}
// ...
}
3、使用 cz-customizable
在项目根目录下创建 .cz-config.js
文件,然后按照官方提供的示例来配置。本次使用如下:
See More
module.exports = {
// type 类型(定义之后,可通过上下键选择)
types: [
{ value: 'perf', name: '修改优化 ' },
{ value: 'feat', name: '添加功能 ' },
{ value: 'fix', name: '修复BUG ' },
].map((item) => {
return {
value:item.value,
name: item.name
}
}),
// scope 类型(定义之后,可通过上下键选择)
scopes: [
'组件功能相关',
'全局方法相关',
'样式格式相关',
'项目依赖相关',
'其他',
].map(value => {
return {
value:value.padEnd(6),
name: value
}
}
),
// 交互提示信息
messages: {
type: '确保本次提交遵循规范!\n选择你要提交的类型:',
subject: '填写简短精炼的变更描述:\n',
body: '填写更加详细的变更描述(可选)。使用 "|" 换行:\n',
breaking: '列举非兼容性重大的变更(可选):\n',
footer: '列举出所有变更的 ISSUES CLOSED(可选)。\n',
confirmCommit: '确认提交?'
},
// 设置只有 type 选择了 feat 或 fix,才询问 breaking message
allowBreakingChanges: ['feat', 'fix'],
// 跳过要询问的步骤
skipQuestions: ['body', 'footer'],
subjectLimit: 100, // subject 限制长度
breaklineChar: '|' // 换行符,支持 body 和 footer
}
建议大家结合项目实际情况来自定义配置提交规则,例如很多时候我们不需要写长描述,公司内部的代码仓库也不需要管理 issue,那么可以把询问 body 和 footer 的步骤跳过(在 .cz-config.js
中修改成 skipQuestions: ['body', 'footer'])
# 集成 commitlint 验证提交规范
为了统一提交信息格式。在提交代码这个环节,我们增加一个限制:只让符合 Angular 规范的 commit message 通过,我们借助 @commitlint/config-conventional 和 @commitlint/cli 来实现
1、安装
yarn add @commitlint/config-conventional @commitlint/cli
2、在项目根目录下创建 commitlint.config.js
文件,并填入以下内容:
module.exports = { extends: ['@commitlint/config-conventional'] }
3、使用 husky 的 commit-msg hook 触发验证提交信息的命令
我们使用 husky 命令在 .husky 目录下创建 commit-msg 文件,并在此执行 commit message 的验证命令。
npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"
以上就配置好了 git 提交信息的配置 ,使用 git cz
提交
# 四、生成 changelog
changelog 是改动日志,包含我们发布的版本信息,以及每次版本更新的东西。
conventional-changelog-cli 是一款 changelog 生成工具,通过提取 commit 信息(默认提取类型为 feat、fix 和包含 breaking change 的提交信息)为我们自动化生成changelog文档。
1、安装
yarn add conventional-changelog-cli
2、配置
在 package.json
中添加一个新指令
{
// ...
"scripts": {
// ...
"changelog":"conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
},
// ...
}
3、使用
# 生成 changelog 日志
yarn changelog
conventional-changelog-cli
生成 changelog 是根据到目前为止的所有提交信息同最新一次tag作比较得到的所有 feature、bug、breaking change 信息进行文档的生成,所以要在每次版本更新并生成 changelog 文档后对分支打 tag,再 push 到远程分支。
这里摘取 conventional-changelog-cli
推荐的整体流程:
- Make changes 改动代码
- Commit those changes 使用 git commit 提交改动信息
- Make sure Travis turns green 确保提交信息都符合规则
- Bump version in package.json 更改 package.json 中的版本
- conventionalChangelog 使用该工具生成 changelog
- Commit package.json and CHANGELOG.md files 提交 package.json 和 changelog 文档
- Tag 给分支打标签
- Push 推送到远程分支
# 优化配置
为了方便使用,我们可以在直接将提交流程写在一个脚本中,提交时执行这个脚本即可
项目根目录下新建 ci/ci.sh
#!/bin/sh
set -e
yarn config set version-git-message "build(version): new version v%s"
yarn version --patch --no-git-tag-version
git add .
git cz
yarn changelog
git add CHANGELOG.md
git commit --amend --no-edit
git pull
git push
exit 0
# 五、说明
# cz-customizable 配置模板
See More
module.exports = {
// type 类型(定义之后,可通过上下键选择)
types: [
{ value: 'feat', name: 'feat: 新增功能' },
{ value: 'fix', name: 'fix: 修复 bug' },
{ value: 'docs', name: 'docs: 文档变更' },
{ value: 'style', name: 'style: 代码格式(不影响功能,例如空格、分号等格式修正)' },
{ value: 'refactor', name: 'refactor: 代码重构(不包括 bug 修复、功能新增)' },
{ value: 'perf', name: 'perf: 性能优化' },
{ value: 'test', name: 'test: 添加、修改测试用例' },
{ value: 'build', name: 'build: 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等)' },
{ value: 'ci', name: 'ci: 修改 CI 配置、脚本' },
{ value: 'chore', name: 'chore: 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)' },
{ value: 'revert', name: 'revert: 回滚 commit' }
],
// scope 类型(定义之后,可通过上下键选择)
scopes: [
['components', '组件相关'],
['hooks', 'hook 相关'],
['utils', 'utils 相关'],
['element-ui', '对 element-ui 的调整'],
['styles', '样式相关'],
['deps', '项目依赖'],
['auth', '对 auth 修改'],
['other', '其他修改'],
// 如果选择 custom,后面会让你再输入一个自定义的 scope。也可以不设置此项,把后面的 allowCustomScopes 设置为 true
['custom', '以上都不是?我要自定义']
].map(([value, description]) => {
return {
value,
name: `${value.padEnd(30)} (${description})`
}
}),
// 是否允许自定义填写 scope,在 scope 选择的时候,会有 empty 和 custom 可以选择。
// allowCustomScopes: true,
// allowTicketNumber: false,
// isTicketNumberRequired: false,
// ticketNumberPrefix: 'TICKET-',
// ticketNumberRegExp: '\\d{1,5}',
// 针对每一个 type 去定义对应的 scopes,例如 fix
/*
scopeOverrides: {
fix: [
{ name: 'merge' },
{ name: 'style' },
{ name: 'e2eTest' },
{ name: 'unitTest' }
]
},
*/
// 交互提示信息
messages: {
type: '确保本次提交遵循 Angular 规范!\n选择你要提交的类型:',
scope: '\n选择一个 scope(可选):',
// 选择 scope: custom 时会出下面的提示
customScope: '请输入自定义的 scope:',
subject: '填写简短精炼的变更描述:\n',
body:
'填写更加详细的变更描述(可选)。使用 "|" 换行:\n',
breaking: '列举非兼容性重大的变更(可选):\n',
footer: '列举出所有变更的 ISSUES CLOSED(可选)。 例如: #31, #34:\n',
confirmCommit: '确认提交?'
},
// 设置只有 type 选择了 feat 或 fix,才询问 breaking message
allowBreakingChanges: ['feat', 'fix'],
// 跳过要询问的步骤
// skipQuestions: ['body', 'footer'],
// subject 限制长度
subjectLimit: 100,
breaklineChar: '|', // 支持 body 和 footer
// footerPrefix : 'ISSUES CLOSED:'
// askForBreakingChangeFirst : true,
}