Vue3前传:创建工程时必须要做的事

10 minute read

前言

Vue3 跟 Vite 正式版发布有很长一段时间了,可我到现在Vue2都还没整明白,学不动了呀,Angular和React也都快忘完了。但该学还是要学,今天再开个专栏,记录下学习Vue3的过程。

言归正传,这篇主要讲怎么搭建一套规范的前端工程,这篇内容不限于Vue3,同样也适用Vue2等其他前端工程。篇幅较长,先列一下目录:

项目地址

github:https://github.com/RicardoLSW/vue-basic

技术栈

环境信息

  • Node版本:v16.0.0

    关于Node版本管理我使用的是nvm,我直接安装最新稳定版Node,安装最新稳定版Node命令:

    $ nvm install stable
    
  • pnpm版本: 6.0.2

    pnpm是一款很火的包管理工具,比yarn/npm更快、更省空间,且目录结构清晰。

项目搭建

确认电脑Node版本,这里我使用Vite搭建项目而不是Vue CLI,需要Node版本 >= 12.0.0

查看Node版本:

$ node -v

使用Vite快速初始化项目

  1. 初始化项目

    $ pnpm init @vitejs/app vue3-demo -- --template vue
    
  2. 进入目录

    $ cd vue3-demo
    
  3. 安装依赖

    在根目录新增.npmrc配置文件,为项目配置pnpm镜像源,可以配置为国内镜像用来加速,或者是不能访问外网的公司内部镜像仓库。

    registry=https://registry.npmjs.org/
    
    $ pnpm i
    
  4. 启动项目

    $ pnpm run dev
    

image-20210424213553260

如上图所示,一个简单的Vue3项目框架就搭建完毕,下面需要接着集成Vue Router、Vuex等其他工具。

修改Vite配置文件

Vite配置文件vite.config.js位于项目根目录下面,类似于Vue CLI的vue.config.js,在启动项目的时候自动加载。

这里只做一些简单的配置,如端口、路径、代理等。详细配置可以看Vite的官网:https://cn.vitejs.dev/config/

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  base: './', // 设置打包路径
  server: {
    port: 3000, // 设置服务启动端口号
    open: true, // 设置服务启动时是否自动打开浏览器
    cors: true // 允许跨域
    // 设置代理,根据我们项目实际情况配置
    // proxy: {
    //   '/api': {
    //     target: 'http://xxx:8000',
    //     changeOrigin: true,
    //     secure: false,
    //     rewrite: (path) => path.replace('/api/', '/')
    //   }
    // }
  }
})

目录结构

├── public/
└── src/
    ├── api/                       // api地址目录
    ├── assets/                    // 静态资源目录
    ├── icons/                     // svg图标目录
    ├── config/                    // 配置文件目录
    ├── core/                      // 核心配置目录
    ├── components/                // 公共组件目录
    ├── router/                    // 路由配置目录
    ├── store/                     // 状态管理目录
    ├── utils/                     // 工具函数目录
    ├── views/                     // 页面组件目录
    ├── App.vue
    ├── main.js
├── tests/                         // 单元测试目录
├── index.html
├── vite.config.ts                 // Vite 配置文件
└── package.json

新增环境配置文件

在根目录下分别新建.env.env.development.env.preview三个文件。

  • .env生产环境配置文件

    NODE_ENV=production
    VUE_APP_PREVIEW=false
    VUE_APP_API_BASE_URL=/api
    
  • .env.development开发环境配置文件

    NODE_ENV=development
    VUE_APP_PREVIEW=true
    VUE_APP_API_BASE_URL=/api
    
  • .env.preview测试环境配置文件

    NODE_ENV=production
    VUE_APP_PREVIEW=true
    VUE_APP_API_BASE_URL=/api
    

集成Vue Router

  1. 安装Vue Router 4

    pnpm i vue-router@4
    
  2. 创建src/confit/router.config.js文件

     └── src/
         ├── config/
             ├── router.config.js  // 路由表配置文件
    
    /**
     * 动态路由表
     */
    export const asyncRouterMap = []
       
    /**
     * 基础路由表
     */
    export const constantRouterMap = [
        {
          path: '/',
          redirect: '/home'
        },
        {
            path: '/home',
            name: 'Home',
            component: () => import('../views/Home.vue')
        },
        {
            path: '/test',
            name: 'Test',
            component: () => import('../views/Test.vue')
        }
    ]
    

    动态路由表:当需要后端生成菜单路由时(例如角色权限管理,每个角色看到的菜单路由都不一样),根据后端接口返回的菜单路由,把数据动态添加到动态路由表里,然后再把动态路由表合并到基础路由表里,具体我后面再讲。

  3. 创建src/router/index.js文件

     └── src/
         ├── router/
             ├── index.js  // 路由配置文件
    
    import { createRouter, createWebHashHistory } from "vue-router";
    import { constantRouterMap } from "../config/router.config";
       
    export default createRouter({
        history: createWebHashHistory(),
        routes: constantRouterMap
    })
    
  4. src/views/目录下创建Home.vueTest.vue,这里我就不详细写了,再更改下src/App.vue

    <template>
      <router-view />
    </template>
       
    <script setup></script>
       
    <style></style>
    
  5. src/main.js引入router

    import { createApp } from 'vue'
    import App from './App.vue'
    import router from './router/index'
       
    createApp(App).use(router).mount('#app')
    

集成Vuex

  1. 安装Vuex 4

    $ pnpm i vuex@next
    
  2. src/store/目录下创建结构

    └── src/
        ├── store/
        		├── modules/   	// 模块目录
            ├── index.js  	// store 配置文件
            ├── getters.js  // getter配置文件
    
  3. getters.js

    const getters = {}
    export default getters
    
  4. index.js

    import { createStore } from 'vuex'
    import getters from './getters'
       
    export default createStore({
        modules: {},
        state: {},
        mutations: {},
        actions: {},
        getters
    })
    
  5. src/main.js引入Vuex

    import { createApp } from 'vue'
    import App from './App.vue'
    import router from './router/index'
    import store from './store/index'
       
    createApp(App).use(router).use(store).mount('#app')
    

集成HTTP工具Axios

  1. 安装Axios

    $ pnpm i axios
    
  2. 创建src/utils/request.jsaxios配置文件

    └── src/
        ├── utils/
        		├── request.js   	// axios配置文件
    
    import axios from 'axios'
       
    // 创建 axios 实例
    const request = axios.create({
        baseURL: process.env.VUE_APP_API_BASE_URL, // api base_url
        timeout: 60000 // 请求超时时间
    })
       
    /**
     * axios 全局错误状态码处理
     * @param error
     * @returns {Promise<never>}
     */
    const errorHandler = (error) => {
        if (error.response) {
            // const { data } = error.response
            switch (error.response.status) {
                case 403:
                    // 拒绝访问
                    break
                case 500:
                    // 服务器异常
                    break
                case 404:
                    // 很抱歉,资源未找到!
                    break
                case 504:
                    // 网络超时
                    break
                case 401:
                    // 未授权,请重新登录
                    break
                default:
                    break
            }
        }
        return Promise.reject(error)
    }
       
    /**
     * axios 配置全局请求参数
     */
    request.interceptors.request.use((config) => {
        return config
    }, errorHandler)
       
    /**
     * axios 配置全局响应参数
     */
    request.interceptors.response.use((response) => {
        if (response.config.responseType === 'blob') return response
        return response.data
    }, errorHandler)
       
    export default request
    
  3. 封装网络请求src/api/methods.js

    └── src/
        ├── api/
        		├── methods.js   	// 封装网络请求
    
    import request from '../utils/request'
       
    // post
    export function postAction(url, parameter) {
      return request({
        url,
        method: 'post',
        data: parameter
      })
    }
       
    // post method= {post | put}
    export function httpAction(url, parameter, method) {
      return request({
        url,
        method,
        data: parameter
      })
    }
       
    // put
    export function putAction(url, parameter) {
      return request({
        url,
        method: 'put',
        data: parameter
      })
    }
       
    // get
    export function getAction(url, parameter) {
      return request({
        url,
        method: 'get',
        params: parameter
      })
    }
       
    // deleteAction
    export function deleteAction(url, parameter) {
      return request({
        url,
        method: 'delete',
        params: parameter
      })
    }
       
    // patchAction
    export function patchAction(url, parameter) {
      return request({
        url,
        method: 'patch',
        data: parameter
      })
    }
       
    // exportAction
    export function exportAction(url, parameter) {
      return request({
        url,
        method: 'get',
        params: parameter,
        responseType: 'blob'
      }).then((res) => {
        const { data } = res
        const blob = new Blob([data], { type: 'application/vnd.ms-excel;charset=utf-8' })
        const downloadElement = document.createElement('a')
        const href = window.URL.createObjectURL(blob) // 创建下载的链接
        downloadElement.href = href
        downloadElement.download = decodeURI(
          res.headers['content-disposition'].split(';')[1].split('=')[1]
        ) // 下载后文件名
        document.body.appendChild(downloadElement)
        downloadElement.click() // 点击下载
        document.body.removeChild(downloadElement) // 下载完成移除元素
        window.URL.revokeObjectURL(href) // 释放掉blob对象
      })
    }
    

集成CSS预编译器

Vite 内部已帮我们集成了相关的 loader,不需要额外配置。直接安装到开发依赖就可以用了。

  1. 安装

    # less
    $ pnpm i less -D
    # sass
    $ pnpm i sass -D
    
  2. 使用

    <style lang="less">
      ...
    </style>
    or
    <style lang="scss">
      ...
    </style>
    

代码规范

随着前端应用逐渐变得大型化和复杂化,在同一个项目中有多个人员参与时,每个人的前端能力程度不等,他们往往会用不同的编码风格和习惯在项目中写代码,长此下去,势必会让项目的健壮性越来越差。解决这些问题,理论上讲,口头约定和代码审查都可以,但是这种方式无法实时反馈,而且沟通成本过高,不够灵活,更关键的是无法把控。不以规矩,不能成方圆,我们不得不在项目使用一些工具来约束代码规范

这里讲解如何使用 EditorConfig + ESLint + Prettier 组合来实现代码规范化。

这样做带来好处:

  • 解决团队之间代码不规范导致的可读性差和可维护性差的问题。
  • 解决团队成员不同编辑器导致的编码规范不统一问题。
  • 提前发现代码风格问题,给出对应规范提示,及时修复。
  • 减少代码审查过程中反反复复的修改过程,节约时间。
  • 自动格式化,统一编码风格,从此和脏乱差的代码说再见。

集成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
  • WebStorm则不需要安装插件,可直接使用EditorConfig配置

集成Eslint

ESLint 是一款用于查找并报告代码中问题的工具,并且支持部分问题自动修复。其核心是通过对代码解析得到的 AST(Abstract Syntax Tree 抽象语法树)进行模式匹配,来分析代码达到检查代码质量和风格问题的能力。

正如前面我们提到的因团队成员之间编程能力和编码习惯不同所造成的代码质量问题,我们使用 ESLint 来解决,一边写代码一边查找问题,如果发现错误,就给出规则提示,并且自动修复,长期下去,可以促使团队成员往同一种编码风格靠拢。

  1. 安装Eslint

    pnpm i eslint -D
    
  2. 配置eslint,在终端输入pnpx eslint --init,然后按照终端提示完成一系列设置来创建配置文件。

    pnpx eslint --init
    
    • How would you like to use ESLint?

      image-20210424232428780

      选择To check syntax, find problems, and enforce code style

    • What type of modules does your project use?

      image-20210424232556626

      选择JavaScript modules (import/export)

    • Which framework does your project use?

      image-20210424232651740

      选择Vue.js

    • Does your project use TypeScript?

      image-20210424232737023

      选择No

    • Where does your code run?

      image-20210424232805378

      选择Browse和Node(按空格选择,回车确认)

    • How would you like to define a style for your project?

      image-20210424232858162

      选择Use a popular style guide

    • Which style guide do you want to follow?

      image-20210424232942630

      选择Airbnb: https://github.com/airbnb/javascript

      ESLint 为我们列出了三种社区流行的 JavaScript 风格指南,分别是 Airbnb、Standard、Google。

      这三份风格指南都是由众多大佬根据多年开发经验编写,足够优秀,全球很多大小公司都在使用。这里选用 GitHub 上 star 最多的 Airbnb,免去繁琐的配置 ESLint 规则时间,然后让团队成员去学习 Airbnb JavaScript 风格指南即可。

      此时,我们在 ESLint 配置了 Airbnb JavaScript 规则,在编码时,所有不符合 Airbnb 风格的代码,编辑器都会给出提示,并且可以自动修复。

    • What format do you want your config file to be in?

      image-20210424233129299

      选择JavaScript

    • Would you like to install them now with npm?

      image-20210424233219307

      根据上面的选择,ESLint 会自动去查找缺失的依赖,我们这里选择 No,我们使用 pnpm 手动下载安装这些依赖包。

      $ pnpm i eslint-plugin-vue eslint-config-airbnb-base eslint@^7.2.0 eslint-plugin-import@^2.22.1 -D
      
  3. Eslint配置文件.eslintrc.js

    在上一步操作完成后,会在根目录下自动生成.eslintrc.js配置文件:

    module.exports = {
      env: {
        browser: true,
        es2021: true,
        node: true,
      },
      extends: [
        "plugin:vue/essential",
        "airbnb-base"
      ],
      parserOptions: {
        ecmaVersion: 12,
        sourceType: "module",
      },
      plugins: ["vue"],
      rules: {
        "import/no-unresolved": "off",
        "import/extensions": "off",
        "import/no-absolute-path": "off",
        "import/no-extraneous-dependencies": "off",
        "vue/no-multiple-template-root": "off",
        "no-param-reassign": [
          "error",
          {
            props: true,
            ignorePropertyModificationsFor: ["state", "config"],
          },
        ],
      },
    };
    

    如果需要额外的规则,可以在这里添加。

    注意:VSCode需要安装ESLint插件,WebStorm则不需额外安装插件,可以直接使用。

集成Prettier

Prettier 是一款强大的代码格式化工具,支持 JavaScript、TypeScript、CSS、SCSS、Less、JSX、Angular、Vue、GraphQL、JSON、Markdown 等语言,基本上前端能用到的文件格式它都可以搞定,是当下最流行的代码格式化工具。

  1. 安装Prettier

    $ pnpm i prettier -D
    
  2. 在根目录创建prettier配置文件.prettierrc

    {
      "useTabs": false,
      "tabWidth": 2,
      "printWidth": 100,
      "singleQuote": true,
      "trailingComma": "none",
      "bracketSpacing": true,
      "semi": false
    }
    
  3. 安装好后,可使用一下命令格式化代码

    $ pnpx prettier --write .
    

注意:VSCode和WebStorm都需要安装Prettier插件。

解决ESLint + Prettier冲突

通常大家会在项目中根据实际情况添加一些额外的 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. 安装插件

    $ pnpm i eslint-plugin-prettier eslint-config-prettier -D
    
  2. .eslintrc.js添加prettier插件

    module.exports = {
      env: {
        browser: true,
        es2021: true,
        node: true,
      },
      extends: [
        "plugin:vue/essential",
        "airbnb-base",
        "plugin:prettier/recommended", // 添加 prettier 插件
      ],
      parserOptions: {
        ecmaVersion: 12,
        sourceType: "module",
      },
      plugins: ["vue"],
      rules: {
        "import/no-unresolved": "off",
        "import/extensions": "off",
        "import/no-absolute-path": "off",
        "import/no-extraneous-dependencies": "off",
        "vue/no-multiple-template-root": "off",
        "no-param-reassign": [
          "error",
          {
            props: true,
            ignorePropertyModificationsFor: ["state", "config"],
          },
        ],
      },
    };
    

这样,我们在执行 eslint --fix 命令时,ESLint 就会按照 Prettier 的配置规则来格式化代码,轻松解决二者冲突问题。

集成Git Hook工具:husky 和 lint-staged

我们在项目中已集成 ESLint 和 Prettier,在编码时,这些工具可以对我们写的代码进行实时校验,在一定程度上能有效规范我们写的代码,但团队可能会有些人觉得这些条条框框的限制很麻烦,选择视“提示”而不见,依旧按自己的一套风格来写代码,或者干脆禁用掉这些工具,开发完成就直接把代码提交到了仓库,日积月累,ESLint 也就形同虚设。

所以,我们还需要做一些限制,让没通过 ESLint 检测和修复的代码禁止提交,从而保证仓库代码都是符合规范的。

为了解决这个问题,我们需要用到 Git Hook,在本地执行 git commit 的时候,就对所提交的代码进行 ESLint 检测和修复(即执行 eslint --fix),如果这些代码没通过 ESLint 规则校验,则禁止提交。

实现这一功能,我们借助 husky + lint-staged

配置husky

使用husky-init命令快速初始化一个husky配置

$ pnpx husky-init && pnpm i

注意:配置husky之前要建立git仓库,使用git init创建本地仓库。

该命令做了一下四件事:

  1. 安装husky到开发依赖:

    image-20210425084142819

  2. 在根目录下创建.husky目录

    image-20210425084416827

  3. .husky 目录创建 pre-commit hook,并初始化 pre-commit 命令为 npm test

    image-20210425084517705

  4. 修改 package.jsonscripts,增加 "prepare": "husky install"

    image-20210425084545824

使用husky

修改 .husky/pre-commit hook 文件的触发命令:

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

eslint --fix ./src --ext .vue,.js,.jsx

上面这个 pre-commit hook 文件的作用是:当我们执行 git commit -m "xxx" 时,会先对 src 目录下所有的 .vue.js.ts 文件执行 eslint --fix 命令,如果 ESLint 通过,成功 commit,否则终止 commit

但是又存在一个问题:有时候我们明明只改动了一两个文件,却要对所有的文件执行 eslint --fix。假如这是一个历史项目,我们在中途配置了 ESLint 规则,那么在提交代码时,也会对其他未修改的“历史”文件都进行检查,可能会造成大量文件出现 ESLint 错误,显然不是我们想要的结果。

我们要做到只用 ESLint 修复自己此次写的代码,而不去影响其他的代码。所以我们还需借助一个神奇的工具 lint-staged

配置lint-staged

lint-staged 这个工具一般结合 husky 来使用,它可以让 husky 的 hook 触发的命令只作用于 git add那些文件(即 git 暂存区的文件),而不会影响到其他文件。

  1. 安装lint-staged

    $ pnpm i lint-staged -D
    
  2. package.json里增加配置项

    "lint-staged": {
      "*.{vue,js,jsx}": [
        "eslint --fix",
        "git add"
      ]
    },
    
  3. 修改 .husky/pre-commit hook 的触发命令为:npx lint-staged

    #!/bin/sh
    . "$(dirname "$0")/_/husky.sh"
       
    npx lint-staged
    

至此,husky 和 lint-staged 配置完成。

当我们修改了代码后,在commit的时候就会触发pre-commit这个hook,在pre-commit里又会触发lint-staged里的eslint去检查语法错误。如下图:

image-20210425085738205

当eslint检查不通过是就会拒绝提交,如下图:

image-20210425090215833

修改后再次提交,提交成功:

image-20210425090407500

提交规范

前面我们已经统一代码规范,并且在提交代码时进行强约束来保证仓库代码质量。多人协作的项目中,在提交代码这个环节,也存在一种情况:不能保证每个人对提交信息的准确描述,因此会出现提交信息紊乱、风格不一致的情况。

如果 git commit 的描述信息精准,在后期维护和 Bug 处理时会变得有据可查,项目开发周期内还可以根据规范的提交信息快速生成开发日志,从而方便我们追踪项目和把控进度。

这里,我们使用社区最流行、最知名、最受认可的 Angular 团队提交规范。

<Header>

<Body>

<Footer>

Header规范

Header 部分包括三个字段 type(必需)、scope(可选)和 subject(必需)。

<type>(<scope>): <subject>

type说明

描述
feat 新增一个功能
fix 修复一个bug
docs 文档变更
style 代码格式(不影响功能,例如空格、分号等格式修正)
refactor 代码重构
perf 改善性能
test 测试
build 变更项目构建或外部依赖(例如scopes: webpack、gulp、npm等)
ci 更改持续集成软件的配置文件和package中的script命令,例如scopes: Travis、Circle等
chore 变更构建流程或辅助工具
revert 代码回退

scope说明

scope 用于指定本次 commit 影响的范围。scope 依据项目而定,例如在业务项目中可以依据菜单或者功能模块划分,如果是组件库开发,则可以依据组件划分。(scope 可省略)

subject说明

subject 是本次 commit 的简洁描述,长度约定在 50 个字符以内,通常遵循以下几个规范:

  • 用动词开头,第一人称现在时表述,例如:change 代替 changed 或 changes
  • 第一个字母小写
  • 结尾不加句号(.)

Body

body 是对本次 commit 的详细描述,可以分成多行。(body 可省略)

跟 subject 类似,用动词开头,body 应该说明修改的原因和更改前后的行为对比。

如果本次提交的代码是突破性的变更或关闭缺陷,则 Footer 必需,否则可以省略。

  • 突破性的变更

    当前代码与上一个版本有突破性改变,则 Footer 以 BREAKING CHANGE 开头,后面是对变动的描述、以及变动的理由。

  • 关闭缺陷

    如果当前提交是针对特定的 issue,那么可以在 Footer 部分填写需要关闭的单个 issue 或一系列 issues。

规范 commit message 的好处

  • 首行就是简洁实用的关键信息,方便在 git history 中快速浏览。

  • 具有更加详细的 body 和 footer,可以清晰的看出某次提交的目的和影响。

  • 可以通过 type 过滤出想要查找的信息,也可以通过关键字快速查找相关提交。

  • 可以直接从 commit 生成 change log。

集成 Commitizen

Commitizen 是一个帮助撰写规范 commit message 的工具。它有一个命令行工具 cz-cli。

  1. 安装Commitizen

    $ pnpm i commitizen -D
    
  2. 使用commitizen init cz-conventional-changelog命令初始化配置

    $ pnpx commitizen init cz-conventional-changelog --save-dev --save-exact
    

    该命令做了两件事

    • 安装cz-conventional-changelog到开发依赖

    • package.json 中增加了 config.commitizen

      image-20210425094216285

      如果使用命令太慢或者报错,可以手动安装配置:

      • 安装依赖

        $ pnpm i cz-conventional-changelog -D
        
      • package.json增加配置

        "config": {
          "commitizen": {
            "path": "./node_modules/cz-conventional-changelog"
          }
        }
        
  3. 使用Commitizen

    以前我们使用的是git commit -m "msg",现在改为git cz,然后按照终端提示,逐步输入信息,就能自动生成规范的commit message

    image-20210425094755418

    image-20210425094856961

    自定义配置提交说明

    从上面的截图可以看到,git cz 终端操作提示都是英文的,如果想改成中文的或者自定义这些配置选项,我们使用 cz-customizable 适配器。

    1. 安装cz-customizable

      $ pnpm i cz-customizable -D
      
    2. 修改 package.json 中的 config.commitizen 字段

      "config": {
        "commitizen": {
          "path": "./node_modules/cz-customizable"
        }
      }
      
    3. 在根目录下新建.cz-config.js文件,配置中文

      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'],
            
        subjectLimit: 100, // subject 限制长度
        breaklineChar: '|' // 换行符,支持 body 和 footer
        // footerPrefix : 'ISSUES CLOSED:'
        // askForBreakingChangeFirst : true,
      }
      
    4. 再次使用git cz,提示信息变为中文

      image-20210425095536894

集成commitlint验证提交规范

代码规范章节,我们已经讲到过,尽管制定了规范,但在多人协作的项目中,总有些人依旧我行我素,因此提交代码这个环节,我们也增加一个限制,并且也要加到Git Hook中。

  1. 安装@commitlint/config-conventional@commitlint/cli

    $ pnpm i @commitlint/config-conventional @commitlint/cli -D
    
  2. 在根目录下新建commitlint.config.js文件

    module.exports = { extends: ['@commitlint/config-conventional'] }
    
  3. 新建.husky/commit-msg文件

    #!/bin/sh
    . "$(dirname "$0")/_/husky.sh"
       
    npx --no-install commitlint --edit
    

使用commitlint验证

  • 不规范的提交信息

    image-20210425100506296

  • 正确的提交信息

    image-20210425100553721

因为已在项目中集成 commitizen,建议大家用 git cz 来代替 git commit 提交代码,可以保证提交信息规范。

单元测试

单元测试是项目开发中一个非常重要的环节,完整的测试能为代码和业务提供质量保证,减少 Bug 的出现。

集成Jest

  1. 安装jest相关依赖

    $ pnpm i @vue/test-utils@next jest vue-jest@next ts-jest babel-jest typescript @types/jest -D
    
  2. 在根目录下创建jest.config.js配置文件,这里配置的同时支持js和ts

    module.exports = {
      moduleFileExtensions: [
        'js',
        'json',
        'vue',
        'ts'
      ],
      transform: {
        '^.+\\.js$': 'babel-jest',
        '^.+\\.vue$': 'vue-jest',
        '^.+\\.ts$': 'ts-jest'
      },
      testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(js|ts)$'
    }
    
  3. 安装babel支持

    $ pnpm i @babel/preset-env babel-plugin-transform-import-meta -D
    
  4. 在根目录创建babel.config.js配置文件

    module.exports = {
      presets: [['@babel/preset-env', { targets: { node: 'current' } }]],
      env: {
        test: {
          plugins: [
            'babel-plugin-transform-import-meta',
          ],
        },
      },
    }
    
  5. 配置eslint规则

    • 安装依赖

      $ pnpm i eslint-plugin-jest -D
      
    • 修改.eslintrc.js文件里的extends

      extends: [
        "plugin:vue/essential",
        "airbnb-base",
        "plugin:prettier/recommended",
        'plugin:jest/recommended'				 // 添加 jest 插件
      ]
      
  6. package.json里的scripts新增属性

    "scripts": {
      "dev": "vite",
      "build": "vite build",
      "serve": "vite preview",
      "lint": "eslint ./src --ext .vue,.js,.jsx",
      "lint-fix": "eslint --fix ./src --ext .vue,.js,.jsx",
      "prepare": "husky install",
      "test": "jest"			// 测试命令
    }
    

创建单元测试文件

  1. 先修改下src/views/Test.vue文件

    <template>
      <div class="test-container page-container">
        <div class="page-title">Unit Test Page</div>
        <p>count is: 8</p>
        <button @click="increment">increment</button>
      </div>
    </template>
       
    <script>
    import { ref } from 'vue'
       
    export default {
      name: "Test",
      setup() {
        const count = ref(0)
          const increment = () => {
            count.value += 1
          }
          return { count, increment }
      }
    };
    </script>
    
  2. 创建test/Test.spec.js测试文件

    ├── src/
    └── tests/                           // 单元测试目录
        ├── Test.spec.js                 // Test 组件测试
    
    import { mount } from '@vue/test-utils'
    import Test from '../src/views/Test.vue'
       
    test('Test.vue', async () => {
      const wrapper = mount(Test)
      expect(wrapper.html()).toContain('Unit Test Page')
      expect(wrapper.html()).toContain('count is: 0')
      await wrapper.find('button').trigger('click')
      expect(wrapper.html()).toContain('count is: 1')
    })
    
  3. 执行单元测试

    $ pnpm run test
    
    • 成功示例

      image-20210425113752268

    • 失败示例

      image-20210425113848214

集成至Git Hook

同样单元测试也需要添加到Git Hook里面,只有单元测试全部通过才能推送到远程代码仓库中,否者就终止push

.husky/目录下新增pre-push文件

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npm run test

现在,我们在 git push 时就能先进行单元测试了,只有单元测试全部通过,才能成功 push

写在最后

到这里一个基本的前端工程就完成了,自动部署这里就先不写了,后面我单独再写一篇讲讲这部分内容。

Tags:

Categories:

Updated: