常用构建流程
项目描述
本文主要用于记录 Vue 项目常用构建流程
Vite + Vue3 + Vue-Router + Pinia + ElementPlus + Axios
创建项目
npm init vue
npm init vue@latest开发环境
node:16.17.1npm:8.15.0vite:3.2.1eslint:8.26.0prettier:2.7.1vue:3.2.41vue-router:4.1.6pinia:2.0.23
代码规范
ESLint
代码格式检测工具【中文配置文档】 默认配置
// .eslintrc.js
// ESLint 配置文件遵循 commonJS 的导出规则,所导出的对象就是 ESLint 的配置对象
module.exports = {
// 表示当前目录即为根目录,ESLint 规则将被限制到该目录下
root: true,
env: {
node: true,
commonjs: true
},
// env 表示启用 ESLint 检测的环境
env: {
// 在 node 环境下启动 ESLint 检测
node: true
},
// ESLint 中基础配置需要继承的配置
extends: ["plugin:vue/vue3-essential", "@vue/standard"],
// 解析器
parserOptions: {
parser: "babel-eslint"
},
// 需要修改的启用规则及其各自的错误级别
/**
* 错误级别分为三种:
* "off" 或 0 - 关闭规则
* "warn" 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出)
* "error" 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)
*/
rules: {
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-unused-vars": [
"warn",
{ vars: "all", args: "after-used", ignoreRestSiblings: false }
]
}
}Prettier
// .prettierrc
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"useTabs": false,
"printWidth": 80,
"arrowParens": "avoid",
"proseWrap": "preserve",
"bracketSpacing": true,
"endOfLine": "auto",
"eslintIntegration": false,
"jsxBracketSameLine": false,
"jsxSingleQuote": false,
"stylelintIntegration": false,
"trailingComma": "none",
"tslintIntegration": false
}VSCode
常用配置
{
/**
* 编辑器配置
*/
// 编辑器字体大小
"editor.fontSize": 17,
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"workbench.iconTheme": "vscode-icons",
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"security.workspace.trust.untrustedFiles": "open",
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"emmet.preferences": {},
"html.format.contentUnformatted": "",
"liveServer.settings.donotShowInfoMsg": true,
"editor.inlineSuggest.enabled": true,
"github.copilot.enable": {
"*": true,
"yaml": false,
"plaintext": false,
"markdown": false
},
"vsicons.dontShowNewVersionMessage": true,
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"git.enableSmartCommit": true,
"terminal.integrated.fontSize": 15,
"terminal.integrated.fontWeightBold": "bold",
"editor.maxTokenizationLineLength": 100000,
"editor.semanticTokenColorCustomizations": {},
"prettier.tabWidth": 2, // 缩进空格数
"prettier.useTabs": false, // 缩进不使用tab,使用空格
"prettier.semi": false, // 句尾添加分号
"prettier.singleQuote": true, // 使用单引号代替双引号
"prettier.proseWrap": "preserve", // 默认值。因为使用了一些折行敏感型的渲染器(如GitHub comment)而按照markdown文本样式进行折行
"prettier.arrowParens": "avoid", // (x) => {} 箭头函数参数只有一个时是否要有小括号。avoid:省略括号
"prettier.bracketSpacing": true, // 在对象,数组括号与文字之间加空格 "{ foo: bar }"
// "prettier.disableLanguages": ["vue"], // 不格式化vue文件,vue文件的格式化单独设置
"prettier.endOfLine": "auto", // 结尾是 \n \r \n\r auto
// "prettier.eslintIntegration": false, //不让prettier使用eslint的代码格式进行校验
"prettier.htmlWhitespaceSensitivity": "ignore", //包裹文字时候结束标签的结尾尖括号掉到了下一行
"prettier.ignorePath": ".prettierignore", // 不使用prettier格式化的文件填写在项目的.prettierignore文件中
// "prettier.jsxBracketSameLine": false, // 在jsx中把'>' 是否单独放一行
"prettier.bracketSameLine": false, // 在jsx中把'>' 是否单独放一行
"prettier.jsxSingleQuote": false, // 在jsx中使用单引号代替双引号
// "prettier.parser": "babylon", // 格式化的解析器,默认是babylon
"prettier.requireConfig": false, // Require a 'prettierconfig' to format prettier
// "prettier.stylelintIntegration": false, //不让prettier使用stylelint的代码格式进行校验
"prettier.trailingComma": "none",
// 优化CPU占用率高的问题
"search.followSymlinks": false,
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/tmp": true,
"**/node_modules": true,
"**/bower_components": true,
"**/dist": true
},
"files.watcherExclude": {
"**/.git/objects/**": true,
"**/.git/subtree-cache/**": true,
"**/node_modules/**": true,
"**/tmp/**": true,
"**/bower_components/**": true,
"**/dist/**": true
},
"editor.fontLigatures": false
}提交规范
Commitizen
当使用 Commitizen 提交时,系统会提示在提交时填写所有必需的提交字段
- 全局安装
Commitizen
npm install -g commitizen@4.2.4- 安装并配置
cz-customizable插件
npm i cz-customizable@6.3.0 --save-dev- 添加以下配置到
package.json中
"config": {
"commitizen": {
"path": "node_modules/cz-customizable"
}
}- 项目根目录下创建
.cz-config.js自定义提示文件
module.exports = {
// 可选类型
types: [
{ value: 'feat', name: 'feat: 新功能' },
{ value: 'fix', name: 'fix: 修复' },
{ value: 'docs', name: 'docs: 文档变更' },
{ value: 'style', name: 'style: 代码格式(不影响代码运行的变动)' },
{
value: 'refactor',
name: 'refactor: 重构(既不是增加feature,也不是修复bug)'
},
{ value: 'perf', name: 'perf: 性能优化' },
{ value: 'test', name: 'test: 增加测试' },
{ value: 'chore', name: 'chore: 构建过程或辅助工具的变动' },
{ value: 'revert', name: 'revert: 回退' },
{ value: 'build', name: 'build: 打包' }
],
// 消息步骤
messages: {
type: '请选择提交类型:',
customScope: '请输入修改范围(可选):',
subject: '请简要描述提交(必填):',
body: '请输入详细描述(可选):',
footer: '请输入要关闭的issue(可选):',
confirmCommit: '确认使用以上信息提交?(y/n/e/h)'
},
// 跳过问题
skipQuestions: ['body', 'footer'],
// subject文字长度默认是72
subjectLimit: 72
}- 用
git cz代替git commit,即可看到提示内容
Git Hooks
git在执行某个事件之前或之后进行一些其他额外的操作
Git Hook | 调用时机 | 说明 |
|---|---|---|
| pre-applypatch | git am执行前 | |
| applypatch-msg | git am执行前 | |
| post-applypatch | git am执行后 | 不影响git am的结果 |
| pre-commit | git commit执行前,不接受任何参数,并且在获取提交日志消息并进行提交之前被调用,脚本git commit以非零状态退出会导致命令在创建提交之前中止会在提交前被调用,并且可以按需指定是否要拒绝本次提交 | 可以用git commit --no-verify绕过 |
| commit-msg | git commit执行前,可用于将消息规范化为某种项目标准格式,还可用于在检查消息文件后拒绝提交可以用来规范化标准格式,并且可以按需指定是否要拒绝本次提交 | 可以用git commit --no-verify绕过 |
| post-commit | git commit执行后 | 不影响git commit的结果 |
| pre-merge-commit | git merge执行前 | 可以用git merge --no-verify绕过。 |
| prepare-commit-msg | git commit执行后,编辑器打开之前 | |
| pre-rebase | git rebase执行前 | |
| post-checkout | git checkout或git switch执行后 | 如果不使用--no-checkout参数,则在git clone之后也会执行。 |
| post-merge | git commit执行后 | 在执行git pull时也会被调用 |
| pre-push | git push执行前 | |
| pre-receive | git-receive-pack执行前 | |
| update | ||
| post-receive | git-receive-pack执行后 | 不影响git-receive-pack的结果 |
| post-update | 当 git-receive-pack对 git push 作出反应并更新仓库中的引用时 | |
| push-to-checkout | 当``git-receive-pack对git push做出反应并更新仓库中的引用时,以及当推送试图更新当前被签出的分支且receive.denyCurrentBranch配置被设置为updateInstead`时 | |
| pre-auto-gc | git gc --auto执行前 | |
| post-rewrite | 执行git commit --amend或git rebase时 | |
| sendemail-validate | git send-email执行前 | |
| fsmonitor-watchman | 配置core.fsmonitor被设置为.git/hooks/fsmonitor-watchman或.git/hooks/fsmonitor-watchmanv2时 | |
| p4-pre-submit | git-p4 submit执行前 | 可以用git-p4 submit --no-verify绕过 |
| p4-prepare-changelist | git-p4 submit执行后,编辑器启动前 | 可以用git-p4 submit --no-verify绕过 |
| p4-changelist | git-p4 submit执行并编辑完changelist message后 | 可以用git-p4 submit --no-verify绕过 |
| p4-post-changelist | git-p4 submit执行后 | |
| post-index-change | 索引被写入到read-cache.c do_write_locked_index后 |
commitlint
用于检查提交信息(
npm需要在 7.x 以上版本!)
- 安装依赖
npm install --save-dev @commitlint/config-conventional@12.1.4 @commitlint/cli@12.1.4- 创建
commitlint.config.js文件
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js- 打开
commitlint.config.js, 增加配置项( config-conventional 默认配置点击可查看 )
注意:确保保存为
UTF-8的编码格式,否则可能会出现错误
module.exports = {
// 继承的规则
extends: ['@commitlint/config-conventional'],
// 定义规则类型
rules: {
// type 类型定义,表示 git 提交的 type 必须在以下类型范围内
'type-enum': [
2,
'always',
[
'feat', // 新功能 feature
'fix', // 修复 bug
'docs', // 文档注释
'style', // 代码格式(不影响代码运行的变动)
'refactor', // 重构(既不增加新功能,也不是修复bug)
'perf', // 性能优化
'test', // 增加测试
'chore', // 构建过程或辅助工具的变动
'revert', // 回退
'build' // 打包
]
],
// subject 大小写不做校验
'subject-case': [0]
}
}husky
git hooks工具(npm需要在 7.x 以上版本!)
- 安装依赖
npm install husky@7.0.1 --save-dev- 启动
hooks, 生成.husky文件夹
npx husky install
- 在
package.json中生成prepare指令
npm set-script prepare "husky install"
- 执行
prepare指令
npm run prepare执行成功,提示

添加
commitlint的hook到husky中,并指令在commit-msg的hooks下执行npx --no-install commitlint --edit "$1"指令
npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'- 此时的
.husky的文件结构
至此, 不符合规范的 commit 将不再可提交
pre-commit
提交时检测代码规范
- 执行
npx husky add .husky/pre-commit "npx eslint --ext .js,.vue src"添加commit时的hook(npx eslint --ext .js,.vue src会在执行到该hook时运行)
npx husky add .husky/pre-commit "npx eslint --ext .js,.vue src"该操作会生成对应文件
pre-commit
关闭
VSCode的自动保存操作修改一处代码,使其不符合
ESLint校验规则执行 提交操作 会发现,抛出一系列的错误,代码无法提交
想要提交代码,必须处理完成所有的错误信息
post-commit
提交后自动推送,该操作会生成对应文件
post-commit
npx husky add .husky/post-commit "git push"lint-staged
只检查本次修改更新的代码,并在出现错误的时候,自动修复并且推送
- 安装依赖
npm install --save-dev lint-staged- 修改
package.json配置
"lint-staged": {
"src/**/*.{js,vue}": [
"eslint --fix"
]
}- 修改
.husky/pre-commit文件
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged- 如上配置,每次会在本地
commit之前,校验提交的内容是否符合本地配置的eslint规则(见文档 ESLint ),校验会出现两种结果- 如果符合规则,则提交成功
- 如果不符合规则,会自动执行
eslint --fix尝试自动修复- 如果修复成功,则会把修复好的代码提交
- 如果修复失败,则会提示错误,必须修复好错误之后才能允许提交代码
图标处理
SVG图标
- 安装依赖
npm i vite-plugin-svg-icons -D- 在
vite.config.js中注册
import path from 'path'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
export default defineConfig({
plugins: [
vue(),
createSvgIconsPlugin({
// 指定需要缓存的图标文件夹
iconDirs: [path.resolve(process.cwd(), 'src/assets/svg')],
// 指定symbolId格式
symbolId: 'icon-[name]'
})
]
})- 如果此处
process报错没有定义,则需要在.eslintrc.cjs中加入
module.exports = {
// 加入下面内容
env: {
node: true,
commonjs: true
}
}- 如果之后运行出现
fast-glob错误,则需要安装一下
npm install fast-glob- 在
main.js中注册图标
import 'virtual:svg-icons-register'- 自定义组件
src/libs/svg-icon/index.vue
<template>
<svg aria-hidden="true">
<use :class="fillClass" :xlink:href="symbolId" :fill="color" />
</svg>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
// 图标名称(剔除 icon- 之后的)
name: {
type: String,
required: true
},
// 图标颜色
color: {
type: String
},
// 自定义图标类名
fillClass: {
type: String
}
})
// 真实显示的 svg 图标名(拼接 #icon-)
const symbolId = computed(() => `#icon-${props.name}`)
</script>- 使用自定义组件
<template>
<svg-icon name="logo"></svg-icon>
</template>
<script setup>
import SvgIcon from '@/libs/svg-icon/index.vue'
</script>字体图标
自定义字体图标以 【iconfont】 为例
选择并将图标添加到自己的项目下,下载至本地,得到如下文件

将图标文件(
iconfont.ttf、iconfont.woff、iconfont.woff2)复制到字体文件夹src/assets/fonts将
iconfont.css文件中的内容复制到项目的 css 文件中,并修改其中字体文件引入路径使用方法
<template>
<!-- name 为图标的 css 类名 -->
<i class="iconfont icon-[name]"></i>
</template>物料封装
将自定义组件放到统一的物料目录,统一导入注册
这里以之前提到的图标处理中
svg-icon组件为例,目录结构如下

libs/index.js文件中导入并注册组件
import svgIcon from './svg-icon/index.vue'
export default {
install(app) {
app.component('svg-icon', svgIcon)
}
}src/main.js中导入并使用物料库
import mLibs from './libs'
app.use(mLibs)- 组件中可直接使用,不需再次导入
<template>
<svg-icon name="logo"></svg-icon>
</template>
<script setup></script>依赖安装
ElementPlus
安装
npm install element-plus --save组件自动导入
- 安装依赖
npm install -D unplugin-vue-components unplugin-auto-import- 添加 Vite 配置
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
module.exports = {
// ...
plugins: [
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
]
}图标自动导入
- 安装依赖
npm i -D unplugin-icons unplugin-auto-import- 修改 vite 配置
检查是否有此依赖
@iconify-json,没有的话也需要安装一下
import path from 'path'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import Icons from 'unplugin-icons/vite'
import IconsResolver from 'unplugin-icons/resolver'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
const pathSrc = path.resolve(__dirname, 'src')
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [
ElementPlusResolver(),
// 自动导入图标组件
IconsResolver({
prefix: 'Icon'
})
],
dts: path.resolve(pathSrc, 'auto-imports.d.ts')
}),
Components({
resolvers: [
ElementPlusResolver(),
// 自动注册图标组件
IconsResolver({
enabledCollections: ['ep']
})
],
dts: path.resolve(pathSrc, 'components.d.ts')
}),
Icons({
autoInstall: true
})
],
})- 使用图标
<i-ep-search />
<el-icon><i-ep-search /></el-icon>Scss
【中文文档】 安装依赖后启动项即可
npm add -D sassAxios
- 安装依赖
npm install axios- 简易拦截器封装
import axios from 'axios'
const service = axios.create({
baseURL: import.meta.env.VITE_VUE_APP_BASE_API,
timeout: 5000
})
// 请求拦截器
service.interceptors.request.use(config => {
// 添加接口校验码
config.headers.icode = 'DE31B80D68BC2A25'
return config
})
// 响应拦截器
service.interceptors.response.use(
response => {
const { success, message, data } = response.data
// 要根据success的成功与否决定下面的操作
if (success) {
return data
} else {
// 业务错误,提示错误消息
ElMessage.error({
type: 'error',
message: message,
grouping: true
})
return Promise.reject(new Error(message))
}
},
error => {
// TODO: 将来处理 token 超时问题
// 提示错误信息
ElMessage.error({
type: 'error',
message: error.message,
grouping: true
})
return Promise.reject(error)
}
)
export default servicePinia
使用方式
- 安装依赖
npm install pinia- 引入依赖
// src/main.js
import { createPinia } from 'pinia'
const pinia = createPinia()
// const app = createApp(App)
app.use(pinia)- 定义
Store
// src/store/count.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => {
return {
count: 1
}
},
getters: {
doubleCount: state => {
return state.count * 2
}
},
actions: {
increment() {
this.count++
}
}
})- 代码测试
<template>
<div>
<button @click="handleAdd">添加列表元素</button>
<button @click="handleEdit">修改 store 值</button>
<button @click="handleReset">重置为初始值</button>
<div>{{ count }}</div>
<div>{{ doubleCount }}</div>
<ul>
<li v-for="item of list" :key="item">{{ item }}</li>
</ul>
</div>
</template>
<script setup>
import { useCounterStore } from '../store/counter.js'
import { storeToRefs } from 'pinia'
const counter = useCounterStore()
const { count, doubleCount, list } = storeToRefs(counter)
const { increment } = counter
console.log(counter)
console.log(count)
console.log(doubleCount)
console.log(increment)
setTimeout(() => {
increment()
}, 2000)
// 添加列表元素
function handleAdd() {
counter.$patch(state => {
state.list.push('猕猴桃')
})
}
// 修改 store 值
function handleEdit() {
counter.$patch({
count: 12
})
}
// 重置 store 初始值
function handleReset() {
counter.$reset()
}
</script>
<style scoped></style>持久化存储
默认存储在 localStroge 中
npm i pinia-plugin-persistedstate- 将插件添加到
Pinia实例
// src/main.js
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)- 声明
store时,将persist选项设置为true
import { defineStore } from 'pinia'
export const useStore = defineStore('main', {
state: () => {},
persist: true // 持久化存储
})MD5
加密字符串 【插件地址】
- 安装依赖
npm install md5- 使用
import md5 from 'md5'
console.log(md5('test'))国际化
- 安装依赖
npm install vue-i18n@9- 创建
src/i18n/index.js文件
import { createI18n } from 'vue-i18n'
// 数据源
const messages = {
en: {
msg: {
test: 'Hello, World!'
}
},
zh: {
msg: {
test: '你好,世界'
}
}
}
// 语言变量
const locale = 'en'
// 初始化实例
const i18n = createI18n({
// 使用 Composition API 模式,则需要将其设置为false
legacy: false,
// 全局注入 $t 函数
globalInjection: true,
locale,
messages
})
export default i18n- 在
main.js中导入
// i18n (PS:导入放到 APP.vue 导入之前,因为会在 app.vue 中使用国际化内容)
import i18n from '@/i18n'
app.use(i18n)- 使用
- 组件模板中使用
<h1>{{ $t('msg.test') }}</h1>- 组件js中使用
import { useI18n } from 'vue-i18n'
const i18n = useI18n()
const message = i18n.t('xxx.xxx.xxx')- 其他 js 中使用
import i18n from '@/i18n'
const message = i18n.global.t('xxx.xxx.xxx')