搭建 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

node

# 1. vite

1、使用 Vite 快速初始化项目雏形

yarn create @vitejs/app

node

2、选择项目使用的模板

node

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.vuevuex.vueaxios.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-prettiereslint-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

我们在项目中已集成 ESLintPrettier,在编码时,这些工具可以对我们写的代码进行实时校验,在一定程度上能有效规范我们写的代码,但团队可能会有些人觉得这些条条框框的限制很麻烦,选择视“提示”而不见,依旧按自己的一套风格来写代码,或者干脆禁用掉这些工具,开发完成就直接把代码提交到了仓库,日积月累,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.jsonscripts,增加 "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、安装 commitizencz-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。

node

# 自定义配置提交说明

从上面的截图可以看到,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,那么可以把询问 bodyfooter 的步骤跳过(在 .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 推荐的整体流程:

  1. Make changes 改动代码
  2. Commit those changes 使用 git commit 提交改动信息
  3. Make sure Travis turns green 确保提交信息都符合规则
  4. Bump version in package.json 更改 package.json 中的版本
  5. conventionalChangelog 使用该工具生成 changelog
  6. Commit package.json and CHANGELOG.md files 提交 package.json 和 changelog 文档
  7. Tag 给分支打标签
  8. 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,
}

# 参考文章

从 0 开始手把手带你搭建一套规范的 Vue3.x 项目工程环境 (opens new window)

给你的git commit加点料 (opens new window)