基于 Vue 项目代码风格整理

408 阅读9分钟

编辑器基本配置

.editorconfig

# 表明是最顶层的配置文件,发现设为true时,才会停止查找.editorconfig文件
root = true

#
#  通配符	      说明
#  *	          匹配除/之外的任意字符串
#  **	        匹配任意字符串
#  ?	          匹配任意单个字符
#  [name]	    匹配name字符
#  [!name]	    匹配非name字符
#  {s1,s3,s3}	匹配任意给定的字符串(0.11.0起支持)
[*]

# 编码格式,支持latin1、utf-8、utf-8-bom、utf-16be和utf-16le,不建议使用uft-8-bom
charset = utf-8

# tab为hard-tabs,space为soft-tabs。
indent_style = space

# 设置整数表示规定每级缩进的列数和soft-tabs的宽度(译注:空格数)。如果设定为tab,则会使用tab_width的值(如果已指定)
indent_size = 2

# 设置整数用于指定替代tab的列数。默认值就是indent_size的值,一般无需指定
# tab_width

# 定义换行符,支持lf、cr和crlf
end_of_line = lf

# 设为true表明使文件以一个空白行结尾,false反之
insert_final_newline = true

# 设为true表示会除去换行行首的任意空白字符,false反之
trim_trailing_whitespace = true

Eslint配置

Vue项目

.eslintrc.js

module.exports = {
  // 当前文件目录是否为规则执行的根目录
  root: true,
  // 设置环境,会预定义所有环境对应的全局变量
  env: {
    node: true,
    browser: true,
    /* 开启后,默认设置 parserOptions: {ecmaVersion: 6 } */
    es6: true,
    jquery: true,
    //  web workers global variables.
    worker: true
  },
  // 解析器选项
  parserOptions: {
    // 文件解析器
    parser: 'babel-eslint',
    /*
   * esma 版本
   * 2015 - 6
   * ...
   * 2018 - 9
   * */
    ecmaVersion: 9,
    // es6 模块系统
    sourceType: 'module',
    // 其他语言功能
    ecmaFeatures: {
      // 允许jsx
      jsx: true
    }
  },
  // 引用外部规则集 对应 eslint-config-*
  extends: [
    'plugin:vue/recommended',
    '@vue/standard'
  ],
  // 插件里面的项对应module为 eslint-plugin-*
  plugins: [
    // 此插件用来识别.html 和 .vue文件中的js代码
    'vue'
  ],
  // 此处设置全局变量
  globals: {
    process: true,
    qiniu: true,
    ClipboardJS: true,
    PingPP: true,
    wx: true,
    ProfX: true
  },
  /*
  * "off" or 0 - 关闭规则
  * "warn" or 1 - 开启规则,只警告
  * "error" or 2 - 开启规则,报错
  * */
  rules: {
    // 缩进为两个空格,switch...case也是
  	indent: [2, 2, {
      SwitchCase: 1
    }],
    // tab缩进
    'vue/html-indent': [2, 'tab'],
    // 一行一个属性
    'vue/max-attributes-per-line': 0,
    // 必须声明类型
    // 'vue/require-prop-types': 0,
    // 需要默认值。Boolean除外
    'vue/require-default-prop': 0,
    // 允许使用v-html
    'vue/no-v-html': 0,
    'vue/html-closing-bracket-newline': [2, {
      'singleline': 'never',
      'multiline': 'never'
    }],
    // 关闭单闭合标签模式
    'vue/html-self-closing': 0,
    'no-tabs': 0,
    'no-new': 0,
    'prefer-promise-reject-errors': 0,
    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
    'no-mixed-spaces-and-tabs': 0
  }
}

构建项目模板配置

代码风格检查主要采用eslint+prettier实现,同时由vue-eslint-parser、typescript-eslint、eslint-plugin-import、pretty-quick等扩展其功能。

eslint

用来规定 js 语法的代码风格,同时支持对部分代码风格进行自动格式化

安装依赖

yarn add -D eslint eslint-define-config

添加配置文件

在项目根目录下,创建 .eslintrc.js.eslintignore 文件

.eslintrc.js:该文件为语法检查配置文件
.eslintignore:该文件为语法检查忽略文件(同.gitignore)

添加扩展

由于eslint本身只能对js语法进行检查,为了使其支持其他语言,需添加一些必要的扩展

添加vue支持:yarn add -D eslint-plugin-vue vue-eslint-parser

添加ts支持:yarn add -D @typescript-eslint/eslint-plugin @typescript-eslint/parser

添加import导入检查:yarn add -D eslint-plugin-import

完善配置

.eslintrc.js

const { defineConfig } = require('eslint-define-config');
// 通过defineConfig声明配置,让配置可以有语法提示
module.exports = defineConfig({
  // eslint会逐级向上查找配置文件,直到文件系统根目录,置为true后关闭逐级向上查找
  root: true,
  // 设置vue-eslint-parser作为vue语法解析器,该解析器应在上层,防止被覆盖
  parser: 'vue-eslint-parser',
  parserOptions: {
    // 此解析器选项由vue-eslint-parser提供,用来设置其他解析器
    // 设置@typescript-eslint/parser作为ts语法解析器
    parser: '@typescript-eslint/parser',
    // 设置项目模块类型为ECMAScript 模块
    sourceType: 'module',
    // 额外支持jsx,tsx语法
    ecmaFeatures: {
      jsx: true,
      tsx: true,
    },
  },
  // 启用的脚本执行环境
  env: {
    browser: true,
    node: true,
  },
  // 加载第三方插件,可以配置第三方插件支持的所有规则。可省略eslint-plugin-前缀
  plugins: ['@typescript-eslint', 'import'],
  // 加载默认规则配置。可省略eslint-config-前缀
  // 注:若不引入plugins,仅配置此项,则rules仅可使用此项配置中的规则
  extends: [
    'eslint:recommended', //eslint推荐规则配置
    'plugin:@typescript-eslint/recommended', //ts推荐规则配置
    'plugin:vue/vue3-recommended', //vue3推荐配置
  ],
  // 指定文件类型做处理
  overrides: [
    {
      files: ['*.ts', '*.vue'],
      rules: {
        'no-undef': 'off',
      },
    },
  ],
  // 自定义规则
  rules: {
    // js 详细规则:http://eslint.cn/docs/rules/
    'no-console': ['warn', { allow: ['error'] }],
    camelcase: ['error', { properties: 'never' }],

    'no-var': 'error',
    'no-empty': ['error', { allowEmptyCatch: true }],
    'no-void': 'error',
    'prefer-const': [
      'warn',
      { destructuring: 'all', ignoreReadBeforeAssign: true },
    ],
    'prefer-template': 'error',
    'object-shorthand': [
      'error',
      'always',
      { ignoreConstructors: false, avoidQuotes: true },
    ],
    'block-scoped-var': 'error',
    'no-constant-condition': ['error', { checkLoops: false }],
    eqeqeq: 'error',
    'no-empty-function': 'error',

    // ts 详细规则:https://typescript-eslint.io/rules/
    '@typescript-eslint/explicit-module-boundary-types': 'off',
    '@typescript-eslint/no-explicit-any': 'off',
    '@typescript-eslint/no-non-null-assertion': 'off',
    '@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
    '@typescript-eslint/consistent-type-imports': [
      'error',
      { disallowTypeAnnotations: false },
    ],

    // vue 详细规则:https://eslint.vuejs.org/rules/
    'vue/no-v-html': 'off',
    'vue/require-default-prop': 'off',
    'vue/require-explicit-emits': 'off',
    'vue/multi-word-component-names': 'off',
    
    // import 详细规则:http://www.npmdoc.org/eslint-plugin-importzhongwenwendangzhongwenshili.html
    'import/first': 'error',
    'import/no-duplicates': 'error',
    'import/order': [
      'error',
      {
        groups: [
          'builtin',
          'external',
          'internal',
          'parent',
          'sibling',
          'index',
          'object',
          'type',
        ],

        pathGroups: [
          {
            pattern: 'vue',
            group: 'external',
            position: 'before',
          },
          {
            pattern: '@vue/**',
            group: 'external',
            position: 'before',
          },
          {
            pattern: '@element-plus/**',
            group: 'internal',
          },
        ],
        pathGroupsExcludedImportTypes: ['type'],
      },
    ],
  },
});

添加命令

在package.json的scripts中添加如下命令即可

"lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx --max-warnings 0",
"lint:fix": "eslint --fix . --ext .vue,.js,.ts,.jsx,.tsx",

Prettier

支持更多语言的代码格式化,相对eslint,对代码风格控制更擅长。

安装依赖

yarn add -D prettier eslint-config-prettier eslint-plugin-prettier

添加配置文件

在项目根目录下,创建 .prettierrc.js.prettierignore 文件

.prettierrc.js:该文件为代码风格配置文件
.prettierignore:该文件为代码风格忽略文件(同.gitignore)

.prettierrc.js

module.exports = {
  semi: true,
  singleQuote: true,
};

添加扩展

eslint-plugin-prettier加入eslint插件中

plugins: ['@typescript-eslint', 'prettier', 'import'],

由于eslint与prettier同时具备设置代码格式的功能,所以可能存在冲突。此时需要将eslint-config-prettier加入到eslint的extends中,用来解决冲突。

extends: [
  'eslint:recommended',
  'plugin:@typescript-eslint/recommended',
  'plugin:vue/vue3-recommended',
  'prettier',
],

开启prettier风格错误提醒

rules: {
  'prettier/prettier': 'error',
}

添加命令

由于prettier本身并不支持命令行,所以需要额外提供支持

yarn add -D pretty-quick

修改package.json中scripts

"lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx --max-warnings 0 && pretty-quick --check",
"lint:fix": "eslint --fix . --ext .vue,.js,.ts,.jsx,.tsx && pretty-quick",

注意

若使用编辑器进行自动代码格式化时,上述配置完成后需要重新开启自动代码格式化,以保证加载最新eslint配置,防止出现eslint格式化与配置不相符。

commit 校验

代码风格检查完成后,依旧无法保证提交的代码一定符合规定风格,此时就需要在提交代码时主动进行代码风格检查。主要采用lint-staged+husky实现。

lint-staged

由于对项目整体代码进行风格检查比较缓慢,所以lint-staged支持仅对暂存区文件(即git add添加文件) 进行操作

安装依赖

yarn add -D lint-staged

添加配置

在package.json中配置lint-staged命令

"lint-staged": {
  "*.{vue,js,ts,jsx,tsx}": "eslint --fix" // 对暂存区vue/js/ts/jsx/tsx后缀文件执行此命令
},

husky

此插件用于在项目中添加git hooks,当执行git操作时,就会触发指定hooks。

安装依赖

yarn add -D husky

安装husky

依赖添加后,要安装husky才可以使其正常工作,为了方便,可在npm hooks中自动进行该操作。

npm hooks可在执行npm命令的各个阶段插入自定义操作,通常npm命令加前缀pre代表该命令执行前hook,命令加前缀post代表该命令执行后hook,例如:

  • preinstall: 在该包被install之前执行
  • postinstall: 在该包被install之后执行

其中prepare为特殊hook,官方描述其在两种情况前运行,一是npm publish命令前,二是不带参数的npm install命令前。而在npm 8.1.0测试中,其会在不带参数的npm install后执行;而在yarn 1.22.11测试中,其在yarn、yarn add后均会执行。

故在package.json的scripts中,添加如下命令

"prepare": "husky install",

命令执行后,会在项目根目录下生成.husky文件夹。在.husky文件夹下创建git hooks。

添加配置

命令行添加git hooks:

yarn husky add .husky/pre-commit "yarn lint-staged"

pre-commit文件内容

#!/bin/sh
# .在当前shell下执行,不创建子进程,保证多脚本间变量共享
# dirname获取文件所在目录
# $0当前执行文件名
. "$(dirname "$0")/_/husky.sh"

yarn lint-staged

这样,在提交代码时,会优先触发pre-commit钩子,并执行其对应脚本,在脚本中执行lint-staged命令,对暂存区文件进行风格检查。

git支持的hooks详细说明:git-scm.com/book/zh/v2/…

添加扩展

向pre-commit文件添加prettier命令,使其支持eslint的同时支持prettier

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

yarn lint-staged
# --staged暂存区文件经prettier处理后,重新加入暂存区
yarn pretty-quick --staged

注意

在husky7中,已不支持在package.json文件配置hooks,调整为.husky文件夹方式

husky为什么放弃了之前的配置方式

根据官方的说法,之前husky的工作方式是这样的,为了能够让用户设置任何类型的git hooks都能正常工作,husky不得不创建所有类型的git hooks。这样在git 工作的每个阶段都会调用husky所设置的脚本,在这个脚本中husky会检查用户是否配置该hook,如果有就运行用户配置的命令,如果没有就继续往下执行。

这样做的好处就是无论用户设置什么类型的git hook husky都能确保其正常运行。但是缺点也是显而易见的,即使用户没有设置任何git hook,husky也向git中添加了所有类型的git hook。

那有没有可能让husky只添加我们需要的git hook呢?作者尝试过解决这个问题,但是失败了。究其失败的根本原因,就是因为husky需要在两个地方进行配置才能完成一个完整的git hook功能。一个是在package.json中配置git hook所要执行的真正命令,一个是在.git/hooks/中配置相对应的git hook。也就是说无论是添加还是删除git hook就要保证在这两个地方同步执行对应的操作。作者无法找到一个可靠的方法来同步这两个地方的配置,因此失败了。

作者认为这个问题是由husky工作模型的自身缺陷导致的,如果想要解决就不得不另辟蹊径采用一种新的工作模型。因此新版husky做了破坏性的变更。

新版husky的工作原理

新版的husky使用了从git 2.9开始引入的一个新功能core.hooksPath。core.hooksPath可以让你指定git hooks所在的目录而不是使用默认的.git/hooks/。这样husky可以使用husky install将git hooks的目录指定为.husky/,然后使用husky add命令向.husky/中添加hook。通过这种方式我们就可以只添加我们需要的git hook,而且所有的脚本都保存在了一个地方(.husky/目录下)因此也就不存在同步文件的问题了。

来源:zhuanlan.zhihu.com/p/366786798