从 0 开始手把手带你搭建一套规范的 Vue3.x 项目工程环境
[TOC]
Vue3 跟 Vite 正式版发布有很长一段时间了,生态圈也渐渐丰富起来,作者已在多个项目中使用,总结一下:就是快!也不用担心稳定性问题,开发体验真不是一般好!还没尝试的同学可以从本文开始学习,从 0 开始手把手带你搭建一套规范的 Vite + Vue3 + TypeScript 前端工程化项目环境。
本文篇幅较长,从以下几个方面展开:
- 架构搭建
- 代码规范
- 提交规范
- 单元测试
- 自动部署
技术栈
- 编程语言: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
- 单元测试:vue-test-utils + jest + vue-jest + ts-jest
- 自动部署:GitHub Actions
架构搭建
请确保你的电脑上成功安装 Node.js,本项目使用 Vite 构建工具,需要 Node.js 版本 >= 12.0.0。
查看 Node.js 版本:
node -v
建议将 Node.js 升级到最新的稳定版本:
# 使用 nvm 安装最新稳定版 Node.js
nvm install stable
使用 Vite 快速初始化项目雏形
使用 NPM:
npm init @vitejs/app
使用 Yarn:
yarn create @vitejs/app
然后按照终端提示完成以下操作:
-
输入项目名称
例如:本项目名称 vite-vue3-starter
2.选择模板
本项目需要使用 Vue3 + TypeScript,所以我们选择 vue-ts,会自动安装 Vue3 和 TypeScript。
你还可以通过附加的命令行选项直接指定项目名和模板,本项目要构建 Vite + Vue3 + TypeScript 项目,则运行:
# npm 6.x
npm init @vitejs/app vite-vue3-starter --template vue-ts
# npm 7+(需要额外的双横线)
npm init @vitejs/app vite-vue3-starter -- --template vue-ts
# yarn
yarn create @vitejs/app vite-vue3-starter --template vue-ts
3.安装依赖
npm install
4.启动项目
npm run dev
Vite + Vue3 + TypeScript 简单的项目骨架搭建完毕,下面我们来为这个项目集成 Vue Router、Vuex、Element Plus、Axios、Stylus/Sass/Less。
修改 Vite 配置文件
Vite 配置文件 vite.config.ts 位于根目录下,项目启动时会自动读取。
本项目先做一些简单配置,例如:设置 @ 指向 src 目录、 服务启动端口、打包路径、代理等。
关于 Vite 更多配置项及用法,请查看 Vite 官网 vitejs.dev/config/ 。
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
-
安装支持 Vue3 的路由工具 vue-router@4
npm i vue-router@4 -
创建
src/router/index.ts文件在
src下创建router目录,然后在router目录里新建index.ts文件:└── src/ ├── router/ ├── index.ts // 路由配置文件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。 -
在
main.ts文件中挂载路由配置
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index'
createApp(App).use(router).mount('#app')
集成状态管理工具 Vuex
-
安装支持 Vue3 的状态管理工具 vuex@next
npm i vuex@next -
创建
src/store/index.ts文件└── src/ ├── store/ ├── index.ts // store 配置文件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 } } }) -
在
main.ts文件中挂载 Vuex 配置import { createApp } from 'vue' import App from './App.vue' import store from './store/index' createApp(App).use(store).mount('#app')
集成 UI 框架 Element Plus
-
安装支持 Vue3 的 UI 框架 Element Plus
npm i element-plus -
在
main.ts文件中挂载 Element Plusimport { 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')
集成 HTTP 工具 Axios
-
安装 Axios(Axios 跟 Vue 版本没有直接关系,安装最新即可)
npm i axios -
配置 Axios
为了使项目的目录结构合理且规范,我们在
src下创建utils目录来存储我们常用的工具函数。Axios 作为 HTTP 工具,我们在
utils目录下创建axios.ts作为 Axios 配置文件:└── src/ ├── utils/ ├── axios.ts // Axios 配置文件import Axios from 'axios' import { ElMessage } from 'element-plus' const baseURL = 'https://api.github.com' const axios = Axios.create({ baseURL, timeout: 20000 // 请求超时 20s }) // 前置拦截器(发起请求之前的拦截) 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 -
使用 Axios
在需要使用 Axios 文件里,引入 Axios 配置文件,参考如下:
<template></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 预编译器 Stylus,直接安装为开发依赖即可。Vite 内部已帮我们集成了相关的 loader,不需要额外配置。同理,你也可以使用 Sass 或 Less 等。
-
安装
npm i stylus -D # or npm i sass -D npm i less -D -
使用
<style lang="stylus"> ... </style>
至此,一个基于 TypeScript + Vite + Vue3 + Vue Router + Vuex + Element Plus + Axios + Stylus/Sass/Less 的前端项目开发环境搭建完毕,项目 Demo 托管在 GitHub 仓库,需要的同学可以去下载下来,参考学习。
下面我们来打磨这个项目,增加代码规范约束、提交规范约束、单元测试、自动部署等,让其更完善、更健壮。
代码规范
随着前端应用逐渐变得大型化和复杂化,在同一个项目中有多个人员参与时,每个人的前端能力程度不等,他们往往会用不同的编码风格和习惯在项目中写代码,长此下去,势必会让项目的健壮性越来越差。解决这些问题,理论上讲,口头约定和代码审查都可以,但是这种方式无法实时反馈,而且沟通成本过高,不够灵活,更关键的是无法把控。不以规矩,不能成方圆,我们不得不在项目使用一些工具来约束代码规范。
本文讲解如何使用 EditorConfig + Prettier + ESLint 组合来实现代码规范化。
这样做带来好处:
- 解决团队之间代码不规范导致的可读性差和可维护性差的问题。
- 解决团队成员不同编辑器导致的编码规范不统一问题。
- 提前发现代码风格问题,给出对应规范提示,及时修复。
- 减少代码审查过程中反反复复的修改过程,节约时间。
- 自动格式化,统一编码风格,从此和脏乱差的代码说再见。
集成 EditorConfig 配置
EditorConfig 有助于为不同 IDE 编辑器上处理同一项目的多个开发人员维护一致的编码风格。
在项目根目录下增加 .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
注意:
- VSCode 使用 EditorConfig 需要去插件市场下载插件 EditorConfig for VS Code 。
- JetBrains 系列(WebStorm、IntelliJ IDEA 等)则不用额外安装插件,可直接使用 EditorConfig 配置。
集成 Prettier 配置
Prettier 是一款强大的代码格式化工具,支持 JavaScript、TypeScript、CSS、SCSS、Less、JSX、Angular、Vue、GraphQL、JSON、Markdown 等语言,基本上前端能用到的文件格式它都可以搞定,是当下最流行的代码格式化工具。
官网:prettier.io/
-
安装 Prettier
npm i prettier -D -
创建 Prettier 配置文件
Prettier 支持多种格式的配置文件,比如
.json、.yml、.yaml、.js等。在本项目根目录下创建
.prettierrc文件。 -
配置
.prettierrc在本项目中,我们进行如下简单配置,关于更多配置项信息,请前往官网查看 Prettier-Options 。
{ "useTabs": false, "tabWidth": 2, "printWidth": 100, "singleQuote": true, "trailingComma": "none", "bracketSpacing": true, "semi": false } -
Prettier 安装且配置好之后,就能使用命令来格式化代码
# 格式化所有文件(. 表示所有文件) npx prettier --write .
注意:
- VSCode 编辑器使用 Prettier 配置需要下载插件 Prettier - Code formatter 。
- JetBrains 系列编辑器(WebStorm、IntelliJ IDEA 等)则不用额外安装插件,可直接使用 Prettier 配置。
Prettier 配置好以后,在使用 VSCode 或 WebStorm 等编辑器的格式化功能时,编辑器就会按照 Prettier 配置文件的规则来进行格式化,避免了因为大家编辑器配置不一样而导致格式化后的代码风格不统一的问题。
集成 ESLint 配置
ESLint 是一款用于查找并报告代码中问题的工具,并且支持部分问题自动修复。其核心是通过对代码解析得到的 AST(Abstract Syntax Tree 抽象语法树)进行模式匹配,来分析代码达到检查代码质量和风格问题的能力。
正如前面我们提到的因团队成员之间编程能力和编码习惯不同所造成的代码质量问题,我们使用 ESLint 来解决,一边写代码一边查找问题,如果发现错误,就给出规则提示,并且自动修复,长期下去,可以促使团队成员往同一种编码风格靠拢。
-
安装 ESLint
可以全局或者本地安装,作者推荐本地安装(只在当前项目中安装)。
npm i eslint -D -
配置 ESLint
ESLint 安装成功后,执行
npx eslint --init,然后按照终端操作提示完成一系列设置来创建配置文件。
-
How would you like to use ESLint? (你想如何使用 ESLint?)
我们这里选择 To check syntax, find problems, and enforce code style(检查语法、发现问题并强制执行代码风格)
-
What type of modules does your project use?(你的项目使用哪种类型的模块?)
我们这里选择 JavaScript modules (import/export)
-
Which framework does your project use? (你的项目使用哪种框架?)
我们这里选择 Vue.js
-
Does your project use TypeScript?(你的项目是否使用 TypeScript?)
我们这里选择 Yes
-
Where does your code run?(你的代码在哪里运行?)
我们这里选择 Browser 和 Node(按空格键进行选择,选完按回车键确定)
-
How would you like to define a style for your project?(你想怎样为你的项目定义风格?)
我们这里选择 Use a popular style guide(使用一种流行的风格指南)
-
Which style guide do you want to follow?(你想遵循哪一种风格指南?)
ESLint 为我们列出了三种社区流行的 JavaScript 风格指南,分别是 Airbnb、Standard、Google。
这三份风格指南都是由众多大佬根据多年开发经验编写,足够优秀,全球很多大小公司都在使用。我们选用 GitHub 上 star 最多的 Airbnb,免去繁琐的配置 ESLint 规则时间,然后让团队成员去学习 Airbnb JavaScript 风格指南即可。
此时,我们在 ESLint 配置了 Airbnb JavaScript 规则,在编码时,所有不符合 Airbnb 风格的代码,编辑器都会给出提示,并且可以自动修复。
这里作者不建议大家去自由配置 ESLint 规则,相信我,这三份 JavaScript 代码风格指南值得我们反复学习,掌握后,编程能力能上一大台阶。
-
What format do you want your config file to be in?(你希望你的配置文件是什么格式?)
我们这里选择 JavaScript
-
Would you like to install them now with npm?(你想现在就用 NPM 安装它们吗?)
根据上面的选择,ESLint 会自动去查找缺失的依赖,我们这里选择 Yes,使用 NPM 下载安装这些依赖包。
注意:如果自动安装依赖失败,那么需要手动安装
npm i @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-config-airbnb-base eslint-plugin-import eslint-plugin-vue -D
-
ESLint 配置文件
.eslintrc.js在上一步操作完成后,会在项目根目录下自动生成
.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: {} }根据项目实际情况,如果我们有额外的 ESLint 规则,也在此文件中追加。
注意:
-
VSCode 使用 ESLint 配置文件需要去插件市场下载插件 ESLint 。
-
JetBrains 系列(WebStorm、IntelliJ IDEA 等)则不用额外安装插件。
配置好以后,我们在 VSCode 或 WebStorm 等编辑器中开启 ESLin,写代码时,ESLint 就会按照我们配置的规则来进行实时代码检查,发现问题会给出对应错误提示和修复方案。
如图:
-
VSCode
-
WebStorm
虽然,现在编辑器已经给出错误提示和修复方案,但需要我们一个一个去点击修复,还是挺麻烦的。很简单,我们只需设置编辑器保存文件时自动执行 eslint --fix 命令进行代码风格修复。
-
VSCode 在
settings.json设置文件中,增加以下代码:"editor.codeActionsOnSave": { "source.fixAll.eslint": true } -
WebStorm 打开设置窗口,按如下操作,最后点击
Apply->OK。
解决 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 配置规则。
-
安装插件
npm i eslint-plugin-prettier eslint-config-prettier -D -
在
.eslintrc.js添加 prettier 插件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 —— Git Hook 工具,可以设置在 git 各个阶段(
pre-commit、commit-msg、pre-push等)触发我们的命令。 lint-staged —— 在 git 暂存的文件上运行 linters。
配置 husky
-
自动配置(推荐)
使用
husky-init命令快速在项目初始化一个 husky 配置。npx husky-init && npm install这行命令做了四件事:
1:安装 husky 到开发依赖
2:在项目根目录下创建
.husky目录3:在
.husky目录创建pre-commithook,并初始化pre-commit命令为npm test4:修改
package.json的scripts,增加"prepare": "husky install" -
手动配置(不推荐,懒是程序员第一生产力)
-
安装 husky
npm i husky -D -
创建 Git hooks
npx husky install该命令做了两件事:
在项目根目录下创建
.husky目录在
.husky目录创建pre-commithook,并初始化pre-commit命令为npm test3:在
.husky目录创建pre-commithook,并初始化pre-commit命令为npm test -
手动修改
package.json的scripts,增加"prepare": "husky install"
特别注意:本项目使用 husky 6.x 版本,6.x 版本配置方式跟之前的版本有较大差异。目前网上大部分有关 husky 的教程都是 6 以前的版本 ,跟本文教程不太一样,当发现配置方法不一致时,一切以 husky 官网为准。
到这里,husky 配置完毕,现在我们来使用它:
husky 包含很多 hook(钩子),常用有:pre-commit、commit-msg、pre-push。这里,我们使用 pre-commit 来触发 ESLint 命令。
修改 .husky/pre-commit hook 文件的触发命令:
eslint --fix ./src --ext .vue,.js,.ts
上面这个 pre-commit hook 文件的作用是:当我们执行 git commit -m "xxx" 时,会先对 src 目录下所有的 .vue、.js、.ts 文件执行 eslint --fix 命令,如果 ESLint 通过,成功 commit,否则终止 commit。
但是又存在一个问题:有时候我们明明只改动了一两个文件,却要对所有的文件执行 eslint --fix。假如这是一个历史项目,我们在中途配置了 ESLint 规则,那么在提交代码时,也会对其他未修改的“历史”文件都进行检查,可能会造成大量文件出现 ESLint 错误,显然不是我们想要的结果。
我们要做到只用 ESLint 修复自己此次写的代码,而不去影响其他的代码。所以我们还需借助一个神奇的工具 lint-staged 。