【开箱即用脚手架】Vue2.7 + Vite + Eslint + Stylelint + Commitlint 工程化实践

5,678 阅读8分钟

Vue2.7 已经发布正式版啦,不出意外的话这应该是 Vue2 的最后一个版本了,但是很多公司目前还没有升级 Vue3 的打算(比如我们)。

所以封装了这么一个开箱即用的脚手架模板,脚手架的功能可以看下面的功能列表小节,并且配备完整的技术文档。

JS 代码格式化

在工作中,一个项目往往是很多人共同开发,因为每个人的编码习惯都不一样,就会增加后期维护成本。

为了解决这个问题,我们一般会定义一个开发规范文档,使用约定的方式来统一项目的编码规范。

有了规范文档之后,我们就会在开发中小心翼翼的调整代码风格,每行、每列、每个双引号。这样无疑也会影响开发效率,而且忙的时候可能还会忘记某些规范条例。

为了更好的统一团队的编码规范,在这里我使用了 EditorConfig + ESLint + Prettier 这些工具来辅助解决规范性问题。

EditorConfig

EditorConfig 主要用于统一不同 IDE 编辑器的编码风格。

当然每个团队最好还是统一一个代码编辑器。

在根目录下添加 .editorconfig 文件:

# EditorConfig is awesome: https://EditorConfig.org

# top-most EditorConfig file
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = false

很多 IDE 中会默认支持此配置,但是也有些不支持,如:VSCode、Atom、Sublime Text 等。

具体列表可以参考官网,如果在 VSCode 中使用需要安装 EditorConfig for VS Code 插件。

ESLint

ESLint 是针对 EScript 的一款代码检测工具,它可以检测项目中编写不规范的代码,如果写出不符合规范的代码会被警告。

由此我们就可以借助于 ESLint 强大的功能来统一团队的编码规范。

  1. 安装依赖
  • eslint - Eslint 本体
  • eslint-define-config- 改善 ESLint 规范编写体验
  • eslint-plugin-vue- 适用于 Vue 文件的 ESLint 插件
  • vue-eslint-parser- 使用 eslint-plugin-vue 时必须安装的 eslint 解析器
pnpm add eslint eslint-define-config eslint-plugin-vue vue-eslint-parser -D
  1. 添加 ESLint 配置文件

在根目录添加一个 .eslintrc.js 文件,内容如下:

const { defineConfig } = require('eslint-define-config')

module.exports = defineConfig({
  root: true,
  env: {
    browser: true,
    node: true,
    es6: true
  },
  parser: 'vue-eslint-parser',
  parserOptions: {
    ecmaFeatures: {
      jsx: true
    }
  },
  extends: [
    /**
     * 继承 eslint-plugin-vue 插件的规则
     * @link https://eslint.vuejs.org/user-guide/#installation
     */
    'plugin:vue/recommended'
  ],
  rules: {
    'vue/multi-word-component-names': 'off'
  }
})

关于配置文件中的选项大家去看官方文档,已经写得很详细了。

  1. 添加 ESLint 过滤规则

在根目录添加一个 .eslintignore 文件,内容如下:

public
dist

Prettier

Prettier 是一款强大的代码格式化工具,这里我们使用 ESLint + Prettier 来格式化代码。

  1. 安装依赖
  • prettier - prettier 本体
  • eslint-config-prettier - 关闭 ESLint 中与 Prettier 中发生冲突的规则
  • eslint-plugin-prettier - 将 Prettier 的规则设置到 ESLint 的规则中
pnpm add prettier eslint-config-prettier eslint-plugin-prettier -D
  1. 添加 Prettier 配置文件

在根目录添加一个 .prettierrc.js 文件,内容如下:

module.exports = {
  semi: false,
  singleQuote: true,
  printWidth: 80,
  trailingComma: 'none',
  arrowParens: 'avoid',
}
  1. 修改 ESLint 配置,使 Eslint 兼容 Prettier 规则
const { defineConfig } = require('eslint-define-config')

module.exports = defineConfig({
  /// ...
  extends: [
    'plugin:vue/vue3-recommended',
    /**
     * 继承 eslint-plugin-prettier 插件的规则
     * @link https://github.com/prettier/eslint-plugin-prettier
     */
    'plugin:prettier/recommended'
  ],
  // ...
})

自动格式化代码

做好以上配置之后,在编码时不符合规范的地方就会被编辑器标注出来,可以使我们更好的发现问题。

如果你用的是VScode,还可以工作区配置中,添加如下代码,之后就可以享受保存时自动格式化的功能了:

{
  "editor.codeActionsOnSave": {
    "source.fixAll": true,
    "source.fixAll.eslint": true
  }
}

CSS 代码格式化

Stylelint 是一个强大、先进的 CSS 代码检查器(linter),可以帮助你规避 CSS 代码中的错误并保持一致的编码风格。

  1. 安装依赖
pnpm add stylelint stylelint-config-prettier stylelint-config-rational-order stylelint-config-standard stylelint-order -D
  1. 添加 StyleLint 配置文件

在根目录添加一个 .stylelintrc.js 文件,内容如下:

module.exports = {
  root: true,
  extends: [
    'stylelint-config-standard',
    'stylelint-config-rational-order',
    'stylelint-config-prettier'
  ],
  defaultSeverity: 'warning',
  plugins: ['stylelint-order'],
  rules: {
    'no-empty-source': null,
    'selector-class-pattern': null
  }
}

在根目录添加一个 .stylelintignore 文件,内容如下:

public
dist
  1. 启用 Vue 文件支持

stylelint v14 版本默认是不支持 vue 文件中的 style 代码自动检测,详情我们可以查看官方迁移指南,具体配置如下:

  • stylelint-config-html 解析 vue 文件
  • postcss-html 使用 stylelint-config-html 依赖的模块
  • postcss-less 对 less 文件进行解析
pnpm add stylelint-config-html postcss-html postcss-less -D
  1. 修改 .stylelintrc.js 文件:
module.exports = {
  root: true,
  extends: [
    'stylelint-config-standard',
    'stylelint-config-rational-order',
    'stylelint-config-prettier',
    'stylelint-config-html/vue' // 需要放在最后一位
  ],
  defaultSeverity: 'warning',
  plugins: ['stylelint-order'],
  rules: {
    'no-empty-source': null,
    'selector-class-pattern': null
  },
  overrides: [
    {
      files: ['*.vue', '**/*.vue'],
      rules: {
        'selector-pseudo-class-no-unknown': [
          true,
          {
            ignorePseudoClasses: ['deep', 'global']
          }
        ],
        'selector-pseudo-element-no-unknown': [
          true,
          {
            ignorePseudoElements: ['v-deep', 'v-global', 'v-slotted']
          }
        ]
      }
    }
  ]
}
  1. 在 VSCode 工作区配置中,添加如下代码:
{
  "stylelint.validate": ["vue"] // Add "vue" language.
}

Husky Git Hook 工具

husky Git Hook 工具,为 git 提供一系列钩子函数,在提交前(pre-commit)、提交消息(commit-msg)等钩子触发时可以为我们执行一些脚本。

我们可以使用 husky 工具来进行代码提交前的自动格式化,以及 commit message 的校验。

提交前代码格式化

  1. 首先安装 husky
pnpm add husky -D
  1. 初始化 husky
pnpm husky install

并在 package.json 中添加如下内容

{
	"scripts": {
    //...
    "prepare": "husky install"
  }
}
  1. 添加 git hook
pnpm husky add .husky/pre-commit

到这里之后我们还需要使用另外一个工具: lint-staged,它是对 git 暂存区文件进行 lint 检查的工具。

  1. 安装 lint-staged
pnpm add lint-staged -D
  1. package.json 中添加如下配置
{
  //...
	"lint-staged": {
    "*.{js,ts,jsx,tsx}": [
      "prettier --write",
      "eslint --fix"
    ],
    "*.vue": [
      "stylelint --fix",
      "prettier --write",
      "eslint --fix"
    ],
    "*.{less,css}": [
      "stylelint --fix",
      "prettier --write"
    ]
  }
}
  1. .husky/pre-commit 文件中写入以下内容
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

pnpm lint-staged

经过以上配置之后,我们就可以在每次提交之前对所有代码进行格式化,保证线上代码的规范性。

在实际中如果遇见 Use the --allow-empty option to continue, or check your task configuration 这个问题。

我们可以修改 pnpm lint-stagedpnpm lint-staged --allow-empty 来暂时屏蔽这个问题。

Commitlint 提交信息校验

我们在使用 git commit 时,git 会记录每一次的 commit message(提交信息)。

正确的描述 commit message 在多人协同开发一个项目时,显得尤其重要。

这里我们可以看一下 angularcommit message,会发现它的描述特别的清晰明了。

commitlint 就是对 commit message 进行的检查的一个工具,当不规范时会终止提交。

  1. 安装依赖
  • @commitlint/cli- Commitlint 本体

  • @commitlint/config-conventional- 通用的提交规范

pnpm add @commitlint/cli @commitlint/config-conventional -D
  1. 创建 commitlint 配置

在根目录添加一个 .commitlintrc.js 文件,内容如下:

module.exports = {
  extends: ['@commitlint/config-conventional']
}
  1. 在 git commit-msg 时进行检查

执行下面这条命令即可:

pnpm husky add .husky/commit-msg "pnpm commitlint --edit $1"

Commit Message 格式

配置完成之后就可以在每次 git commit 时对 commit message 进行校验了,规范有两种格式:单行信息和多行信息。

  1. 单行信息

用于业务代码提交时使用,业务代码一般来说更改比较多而且无法具体说明其信息,具体的还需要看产品文档,所以用单行信息即可。

<type>(<scope>): <subject>
  1. 多行信息

用于提交一些不经常更改的功能型代码时使用,如:某个功能函数的新增、修改或重构,目录结构的调整(工程化调整),架构的更改等,这些我们需要进行详细说明防止出现遗忘。

<type>(<scope>): <subject>
<BLANK LINE> // 空行
<body>
<BLANK LINE>
<footer>

字段描述:

  • type 类型
  • scope 影响的范围, 比如: *(全局),route, component, utils, build,readme,css 等,
  • subject 概述, 建议符合 50/72 formatting
  • body 具体修改内容,描述为什么修改, 做了什么样的修改, 以及开发的思路等等,可以分为多行, 建议符合 50/72 formatting
  • footer 一些备注, 通常是 Breaking Changes(重要改动) 或 Closed Issues(修复 Bug 的链接)

type 类型有以下几种:

类型描述
build发布版本
chore改变构建流程、或者增加依赖库、工具等
ci持续集成修改
docs文档修改
feat新特性
fix修改问题
perf优化相关,比如提升性能、体验
refactor代码重构
revert回滚到上一个版本
style代码格式修改
test测试用例修改

示例:

feat(eslint): 集成 eslint - xcder - 2022.07.01

1. Vscode 安装 Eslint 插件即可在保存时自动格式化
2. 运行 pnpm lint:eslint 可全局进行代码格式化

可以浏览 docs/1.工程化实践/1.eslint 文件了解详情。

VueRouter 路由

Vue2.x 中只能使用 vue-router 3 版本。

目录说明

有关路由的功能都在以下的目录中。

.
├── src # 主目录
│   ├── main.js # 主入口
│   ├── router # 路由配置
│   │   ├── guard # 路由守卫
│   │   ├── modules # 路由模块
│   │   └── index.js
│   └── views # 页面

route modules

关于路由表,建议根据功能的不同来拆分到 modules 文件夹中。

这样做的好处是:

  1. 方便后期维护
  2. 减少 Git 合并代码时的冲突的可能

Composition-API 中使用

vue-router 3 版本目前对 Composition-API 的支持不是很友好,这里在 hooks/useRouter 中封装了两个方法,还原 vue-router 4 中的使用方法:

  1. useRouter
  2. useRoute
import { watch } from 'vue'
import { useRoute, useRouter } from '@/hooks/useRouter'

export default {
  setup() {
    const route = useRoute()
    const router = useRouter()

    watch(route, () => {
      console.log('route 变化', route.value)
    })

    function routeChange() {
      router.push({ path: '/home', query: { key: Date.now() } })
    }

    return {
      routeChange
    }
  }
}

全局状态管理

这里全局状态管理器选择了 Pinia,之所以没有选择 Vuex,对于我来说最主要的原因有两点:

  1. Vue3 官方文档中,已经将 Pinia 放入官方推荐的核心库位置中了。
  2. 对 Composition-API 具有更好的支持。

接下来看一段官方的说明:

Pinia 最初是为了探索 Vuex 的下一次迭代会是什么样子,结合了 Vuex 5 核心团队讨论中的许多想法。最终,我们意识到 Pinia 已经实现了我们在 Vuex 5 中想要的大部分内容,并决定实现它 取而代之的是新的建议。

与 Vuex 相比,Pinia 提供了一个更简单的 API,具有更少的规范,提供了 Composition-API 风格的 API,最重要的是,在与 TypeScript 一起使用时具有可靠的类型推断支持。

目录说明

有关全局状态管理的功能都在以下的目录中。

.
├── src # 主目录
│   ├── main.js # 主入口
│   ├── store # store 配置
│   │   ├── modules # store 模块
│   │   └── index.js
│   └── views # 页面

store modules

我们在开发中需要将不同功能所对应的状态,拆分到不同的 modules,好处在 route moduels 中已经描述过了。

组件库

关于组件库可选择的就有很多了,因为可能每个人需要的都不相同,这里就不集成到脚手架模板中了。

有一点需要注意的是,Vue2.7 还是只能使用 Vue2.x 相关的组件库。

Axios 封装及接口管理

这里接口请求使用的是基于 Promise 封装的请求库 Axios,也是现在使用很广泛的一个请求库。

目录说明

有关请求库的功能都在以下的目录中。

.
├── src # 主目录
│   ├── api # api 方法配置
│   │   └── demo.js # 演示方法
│   └── utils # 公共方法
│       └── request.js # axios 请求库二次封装

在开发时,我们先在 utils/requset.js 中配置好适合自己业务的请求拦截和响应拦截:

import axios from 'axios'

const services = axios.create({
  baseURL: '/api',
  timeout: 8000
})

// 请求拦截
services.interceptors.request.use(
  config => {
    /**
     * 在这里一般会携带前台的参数发送给后台,比如下面这段代码:
     * const token = getToken()
     * if (token) {
     *  config.headers.token = token
     * }
     */

    return config
  },
  error => {
    return Promise.reject(error)
  }
)

// 响应拦截
services.interceptors.response.use(
  response => {
    const res = response.data

    /**
     * 这里使用的是自定义 Code 码来做统一的错误处理
     * code 等于 -1 则代表接口响应出错(可根据自己的业务来进行修改)
     */
    if (res.code === -1) {
      const msg = res.message || '未知错误,请联系管理员查看'

      console.error('[api]', msg)

      return Promise.reject(msg)
    }

    return res.data
  },
  error => {
    const { response } = error
    if (response && response.data) {
      return Promise.reject(error)
    } else {
      const { message } = error
      console.error('[api]', message)
      return Promise.reject(error)
    }
  }
)

export default services

之后在 api 文件夹中定义请求方法,这里我一般会以功能进行拆分,同一个功能的请求方法封装在一个文件,之后在需要的地方进行调用。

Css 样式处理

统一浏览器默认样式

在 Web 开发时,因为浏览器内核的不同,所以会导致某些 Html 元素默认的渲染样式有所差别,虽然很细微,但是会影响我们在不同浏览器中的显示效果。

为了解决这个问题,聪明的开发者就将某个元素在各个浏览器中表现不同的样式,通过一段 CSS 进行重置,然后再进行开发,这样就可以保证每个浏览器显示效果的统一性。

目前常用的有两种解决方案:

  1. Normalize - normalize.css 偏向于修复浏览器的默认 BUG 和一致性,但是保留元素的默认样式。
  2. Reset - reset.css 偏向于完全重置浏览器默认样式,可控性更高。

在本项目中,我们结合两种方案进行使用,代码在 styles/normalize.cssstyles/reset.css 中。

Less 预处理器

在 Vite 中使用 Less,我们只需要运行以下命令即可:

pnpm add less -D

如果需要使用 sass 则把 less 换成 sass 就行。

安装完成之后,无需多余配置,vite 即可对 less 样式进行解析。

在 Vue 文件中,我们如下,即可使用 less 进行样式的编写:

<style lang="less" scoped>
// less 样式代码
.card {
  &-body {}

  &-header {}
}
</style>

这里 CSS 命名规范推荐 BEM 命名规范,具体可以自行搜索了解。

Less 全局变量/方法

一般在项目中会有自己的设计规范,比如像 Element、Antd,它就有一套自己的样式规范。

其中像颜色、边框、边距等都有统一的规范,当我们改一处,所有地方都会改变,这就用到了 less 的变量和 mixin。

然而我们每处都 @import 就比较麻烦,这个时候我们可以使用在 vite 中进行 less 的全局样式配置。

import { defineConfig } from 'vite'
import { resolve } from 'path'

function pathResolve(dir) {
  return resolve(process.cwd(), '.', dir)
}

// 全局变量
const variablesLessPath = pathResolve('./src/styles/global/variables.less')
// 全局方法
const utilsLessPath = pathResolve('./src/styles/global/utils.less')

export default defineConfig({
  css: {
    preprocessorOptions: {
      less: {
        modifyVars: {
          hask: `
            true;
            @import (reference) "${variablesLessPath}";
            @import (reference) "${utilsLessPath}";
          `
        },
        javascriptEnabled: true
      }
    }
  }
})

Vue 样式穿透

官方文档

在 Vue2.7 中,改变了以往样式穿透的语法,如果继续使用 ::v-deep/deep/>>> 等语法的话,会出现一个警告,下面是新的语法:

/* 深度选择器 */
:deep(selector) {
  /* ... */
}

/* 插槽选择器 */
:slotted(selector) {
  /* ... */
}

/* 全局选择器 */
:global(selector) {
  /* ... */
}

Vite 基础配置

目录说明

有关 vite 配置的功能都在以下的目录中。

.
├── build # 打包配置文件
├── src # 主目录
│   ├── utils 
│   │   └── env.js # 环境变量判断方法
├── .env # 基础环境变量
├── .env.development # 开发环境变量
├── .env.production # 生产环境变量
└── vite.config.js # vite 配置

基础配置

完整的 vite 配置如下所示,配置了大概有以下的功能:

  1. 环境变量
  2. alias 别名
  3. 跨域配置
  4. 构建兼容性
  5. less 全局样式
import vue from '@vitejs/plugin-vue2'
import legacy from '@vitejs/plugin-legacy'
import serverProxy from './build/proxy'
import { defineConfig, loadEnv } from 'vite'
import { pathResolve, wrapperEnv } from './build/utils'

const variablesLessPath = pathResolve('./src/styles/global/variables.less')
const utilsLessPath = pathResolve('./src/styles/global/utils.less')

// https://vitejs.dev/config/
export default ({ mode, command }) => {
  const root = process.cwd()
  const isBuild = command === 'build'

  // 在 vite 配置文件中使用环境变量,需要通过 loadEnv 来加载
  const env = loadEnv(mode, root)

  const { VITE_PUBLIC_PATH, VITE_OUTPUT_DIR, VITE_PORT, VITE_LEGACY } =
    wrapperEnv(env)

  const plugins = [vue()]

  /**
   * vite 默认打包文件带有 ES6 语法,在旧版浏览器中是不支持的。
   * 为了支持旧版浏览器,可以在 .env.production 中开启 VITE_LEGACY 设置
   */
  if (isBuild && VITE_LEGACY) {
    plugins.push(legacy())
  }

  return defineConfig({
    root,
    base: VITE_PUBLIC_PATH,
    plugins,
    resolve: {
      alias: {
        // @/xxxx => src/xxxx
        '@': pathResolve('./src')
      }
    },
    server: {
      host: true,
      port: VITE_PORT,
      proxy: serverProxy
    },
    build: {
      /**
       * 最终构建的浏览器兼容目标
       * https://www.vitejs.net/config/#build-target
       */
      target: 'es2015',
      outDir: VITE_OUTPUT_DIR,
      brotliSize: false, // 关闭 brotli 压缩大小报告,可提升构建速度
      chunkSizeWarningLimit: 2000 // chunk 大小警告的限制(以 kbs 为单位)
    },
    css: {
      preprocessorOptions: {
        less: {
          modifyVars: {
            hask: `
              true;
              @import (reference) "${variablesLessPath}";
              @import (reference) "${utilsLessPath}";
            `
          },
          javascriptEnabled: true
        }
      }
    }
  })
}

跨域配置

这里我们使用的是 vite 自带的 http-proxy 来解决跨域,这也是我们在开发中比较常见的解决跨域的一种方式。

我们打开 build/proxy.js 文件可以看到其中有一个 proxyList 变量,我们的跨域配置写在这里即可。

const proxyList = [
  { prefix: '/demo', target: 'http://localhost:3000/demo', removePrefix: true },
  { prefix: '/api', target: 'http://localhost:3000/api' }
]

// 以上配置最终会被转换为 vite 所需要的格式:

{
  '/demo': {
    target: 'http://localhost:3000/demo',
    changeOrigin: true,
    ws: true,
    rewrite: path => path.replace(new RegExp('^/demo'), '')
   },
   '/api': {
    target: 'http://localhost:3000/demo',
    changeOrigin: true,
    ws: true,
   }
}

环境变量

官方文档

在本脚手架中配置了2种环境:

  1. development 开发环境
  2. production 生产环境

环境变量数据在 vue 项目中可以直接使用 import.meta.env 这个变量获取,比如 src/utils/env.js 中:

/**
 * 是否为开发环境
 * @returns {Boolean}
 */
export function isDev() {
  return import.meta.env.DEV
}

/**
 * 是否为生产环境
 * @returns {Boolean}
 */
export function isProd() {
  return import.meta.env.PROD
}

但是如果需要在 vite.config.js 中使用的话,需要用到 loadEnv 这个方法:

import { defineConfig, loadEnv } from 'vite'


export default ({ mode }) => {
  // 通过 loadEnv 获取环境变量数据
  const env = loadEnv(mode, process.cwd())

  return defineConfig({
    /* vite 配置 */
  })
}

代码规范

代码规范大家可以参考 Vue 官方风格指南 这里写的还是很详细的。

下面简单写一下在项目中开发的一些规范:

  1. 命名规范

    • 整体采用小驼峰命名(JS 变量名,函数名等等);
    • 组件和类名采用大驼峰命名;
    • 文件采用全小写命名,单词之间由短横线分割;
    • 变量,方法,文件命名保持语义化,使人一眼就能确定其作用。
  2. 注释规范

    • 写注释的作用是使代码更加方便阅读,所以没必要每行都写注释,大部分可以通过规范的命名来解决代码阅读问题;
    • 功能、算法相关的函数一定要写注释,采用 JSDoc 规范。
  3. 开发规范

    • 在编写代码时,首先保证代码可读性
    • 功能开发时,遵守单一功能原则(每部分只负责一个功能),多进行拆分,保证可维护性;
    • 组件开发时,把通用、常用的组件进行拆分,保证可维护性。

案例代码

以上这些配置都是项目中比较常用而且没有很大难度的工程化配置内容,只要用心走一遍,基本就可以掌握了。

这里适配的为 Vue2.7 版本,如果需要使用 Vue3 的话,把以下 3 个依赖以及对应的功能修改一下,即可适用:

  1. vue 2.7.0 -> 3.x
  2. vue-router 3.5.4 -> 4.x
  3. @vitejs/plugin-vue2 -> @vitejs/plugin-vue

最后放上案例代码:vue2.7-vite-cli

如果以上内容对您有帮助的话,请给我一个小🌟🌟,有什么还需要改进的地方,也欢迎大家来指点。