vscode结合eslint打造一套高效规范的vue开发环境 2018-9-26 18:29:59

成果

前提

在谈成果之前,有一个很重要的前提:你是否愿意抛弃以往坚持的代码风格?正如StandardJS所表达的那样,关于代码规范的争论永远也没个头,不如大家约定好就这样来,如此便可以专注于业务开发了。(虽然仍然可以自己复写规则,但还是建议能统一规范,避免后续规则越来越多,更难维护)

效果

对于普通的js文件,eslint本来就支持;对于vue单文件,可满足以下两条

  1. template模板中内容可以检测语法自动fix
  2. script中内容可以检测语法自动fix

其中fix的规则:模板依靠eslint-plugin-vue官方规则(稍后详细说明,也可自读文档),js内容依靠我们的配置,即StandardJS规范。

配置

  1. 建议如果对eslint配置不了解的先花15分钟左右的时间研读一下配置说明
  2. 清楚了eslint的配置规则,就有了我们接下来的配置(命名为.eslintrc.js,放在项目的根目录下)
module.exports = {
  parserOptions: {
    parser: 'babel-eslint' // 使eslint识别更高级的语法,如动态import。否则会在语法解析步骤时出错阻塞,导致无法进行后续的语法校验
  },
  extends: ['plugin:vue/recommended', 'standard'], // 其中vue的规范共有四种,[文档](https://www.npmjs.com/package/eslint-plugin-vue)讲的很清楚。我们采用约束最严的一种,稍后也会详细说明
  plugins: [
    'vue'
  ]
};

根据配置,我们要下载对应的npm包以提供支持:

a.执行npm i -D eslint,即安装核心

b.为满足extends: ['plugin:vue/recommended']plugins: ['vue'],执行npm i -D eslint-plugin-vue

c.为满足extends: ['standard'],执行npm i -D eslint-config-standard eslint-plugin-standard eslint-plugin-promise eslint-plugin-import eslint-plugin-node,具体说明参见wiki

d.为满足parser: 'babel-eslint',执行npm i -D babel-eslint

  1. 修改vscode的配置项,如下所示

此处的validate字段写法很重要,只有采用这种方式来写,eslint插件才能依照规则自动修复vue单文件。引用文档中的一段说明:

eslint.validate - an array of language identifiers specify the files to be validated. Something like "eslint.validate": [ "javascript", "javascriptreact", "html" ]. If the setting is missing, it defaults to ["javascript", "javascriptreact"]. You can also control which plugins should provide autofix support. To do so simply provide an object literal in the validate setting with the properties language and autoFix instead of a simple string. An example is: `"eslint.validate": [ "javascript", "javascriptreact", { "language": "html", "autoFix": true } ]`
"eslint.autoFixOnSave": true,
"eslint.validate": [
  "javascript",
  "javascriptreact",
  {
    "language": "vue",
    "autoFix": true
  }
],
"eslint.alwaysShowStatus": true

  1. 重启vscode,打开vue单文件,确认效果

如果依然没有效果,打开控制台,切换到输出面板,查看当前是否处于eslint服务

起因

市面上的格式化工具实在太多了。就拿vscode生态来说,常见的就有eslint、standardjs、prettier,而针对vue单文件的还有vetur(虽然大多数人安装vetur是为了语法高亮,但是其内置的格式工具、检测工具也发挥了不少作用。尤其是当你根本不知道它的存在的时候,有时候语法错误提示你会觉得这是vscode默认提供的,实际则是vetur的功能。因为离开了vetur,vscode可是不认识vue文件的,除非你自己更改默认配置:files.associations: { *.vue: html },将vue文件作为html文件来处理。当然,这种做法也会造成另外的问题,这也是我最终放弃standardjs这个插件而使用eslint的原因)。

而vetur格式化的有力支撑则来源于prettier,当然,在html格式化上还依赖于js-beautify-html。

现在我有几个需求,其实也就是文章开头我所展示的成果:因为平日写vue文件较多,而自己在写模板文件的时候又有些洁癖,喜欢遵循官方推荐的一些风格来写,比如每行的属性最多只有一个,多的话就要换行对齐。在实际操作中其实是很麻烦的,所以我第一个想解决的问题就是在template里面能够自动格式化,不需要我人为的来控制。第二点就是团队大了自然要制定一些代码规范,自己写的项目多了也该趋向于统一。所以我想摸索制定出一套属于自己代码风格的规范(后来当我看了一下eslint的创建规则文档后,emmmmm,考虑了读文档写规则再加上fix的逻辑所消耗的时间,算了算了惹不起,没必要舍近求远。这也是后来我干脆直接选择standardjs规则的原因)。当然,有了规范也要能满足自动fix啊。因为尝过被修改语法支配的恐惧,真的折磨(话说当初宁可一个一个改都没拿出半天时间系统研究一下fix的事,嗯,理由也只能是业务太紧了)。

说到这里,那当前我们要解决的问题是什么呢?其实从上边读到这里,也就汇成了一句话:选择哪种格式化工具能支持不符合规范的代码报错、并支持自动修正vue文件中的template和script部分呢?带着这个问题开始以下实践。

历程

放弃vetur

首先是考虑先尝试哪一个。无非是vetur和eslint这两个插件(一开始并不清楚standardjs这个插件)。分析了下vetur的实践成本:因为它依赖于prettier和js-beautify-html的配置,所以我们要先了解一下prettier和js-beautify-html。大概扫了一眼prettier的配置文档,好像提供的配置项较少,但是它提供了和eslint混合的功能,即:先应用eslint的规则,找不到的则以prettier的规则为准。这个时候就需要了解一下eslint是怎么使用了。

拥抱eslint

但是看过eslint之后,发现本身这个vscode插件就支持语法校正,而且配合vue官方提供的eslint-plugin-vue插件,还能校正template中的内容,简直是一举两得。这时就没必要使用vetur了。

在使用vscode的eslint插件之前也有个疑问:它依靠哪里的配置来检测语法呢?其实也很简单,就是根据你项目根目录的那个配置文件(配置文件可以是哪种格式参见官网说明)。除了默认的,你也可以在vscode的配置项中指定一个配置文件路径,具体如图(但是我感觉这种方式太死板,与vscode的耦合太重,会影响本地的所有项目,不建议使用):

但是一开始由于没有细致看文档的原因,走了弯路。我简单的以为在eslint.validate字段中加上vue这个字符串值配合配置文件就能达到效果(事实是稍加修改确实就达到了效果)。然而就因为这个疏忽,让我一度以为eslint并不支持vue单文件的校验,之后便开启了standardjs的认知之路。

误拐standardjs

虽然最后没有直接采用standardjs这个插件,但是实质上还是用的它的规范。也正是了解了standardjs,才明白eslint中extends: ['standard']中的standard并非eslint的标准规范,而是standardjs的规范。

standardjs是基于eslint做了一次上层封装,所以它依赖于eslint,如果要使用它,还是需要装几个相关的eslint包才行。正因为如此,它的插件配置项和eslint插件的配置项大同小异。

为什么在eslint配置不生效的情况下选择了standardjs呢? 其实很简单,因为它的文档中介绍了怎么兼容vue单文件的语法校验及fix,具体如图:

通过以上几个配置项就能支持vue单文件。那这些配置具体是干什么的呢?首先增加了eslint-plugin-html插件,在eslint-plugin-html的github主页中是这样介绍它自己的:从html文件中提取并校验script部分。那自然而然的,我们需要将vue文件当作html文件来对待了。于是就有了上图中红框部分。然后问题就随之而来了:

  • 首先vetur一些功能不生效了(因为有些功能是针对vue文件的,现在被当成了html文件),这是次要的,毕竟我们已经放弃了vetur
  • 再者vue文件中style部分被作为css来解析,如果你有less或是scss语法,是不识别的。因为html中不存在预处理语言这一说
  • ... (应该还有一些问题,但我没继续深究下去,总之该是什么文件就是什么文件,把vue视作html文件本身就不能忍)

在我将要放弃standardjs这个插件的时候,同时从它的配置项中得到了些启发。首先你是基于eslint的,然后你如果要支持autoFix就必须配置成一个对象形式。我再跑去看一眼eslint的介绍,果然如出一辙。带着猜想卸载了standardjs重装了eslint,更新了配置项之后,一切都ok了,便是自己期望的那个样子。

附录

最后整理下eslint-plugin-vue中对template中语法的规范,方便日后查阅及参考,同时在结尾也对部分规则进行了重写。

此插件提供了四种规范,是逐级递增的,后者包含前者的所有规范。依次为base、Essential、Strongly Recommended、Recommended。可根据需求各取所需,但强烈建议用全量的,因为后面的大多数都支持自动fix,这样能保证代码统一。

目前规范的版本为5.x。正如github主页所说,这是一个预发布版本。npm正常安装的版本实际要低一些。所以要全量使用,在安装的时候要npm i eslint-plugin-vue@next -D

未归类的几项规则暂未列入。

通用规则

自动Fix Rule ID 描述 默认规则 其他规则
vue/comment-directive 在template区域支持eslint的注释指令。
因为写js时往往需要针对某些特殊代码禁用语法检测,这时候就通过特殊注释的方式实现。template中的内容为类html内容,不同于js。
支持以下四种注释
eslint-disable
eslint-enable
eslint-disable-line
eslint-disable-next-line
vue/jsx-uses-vars 解决了在jsx语法中使用的变量不能被eslint的no-unused-vars规则检测到的问题

Essential

自动Fix Rule ID 描述 默认规则 其他规则
vue/no-async-in-computed-properties computed值中禁止包含异步代码
vue/no-dupe-keys 禁止props、computed、data、methods等这些官方字段中有重复的键名 groups: [] 可向groups数组中追加额外字段
vue/no-duplicate-attributes 禁止html标签中有重复的属性名(<input :value="" value=""></input>是禁止的),默认允许自定义和原生的class、style共存 allowCoexistClass: true,
allowCoexistStyle: true
vue/no-parsing-error 检查template中的语法 配置项过多,参见文档
vue/no-reserved-keys 禁止使用vue的保留字,保留字参见文档 reserved: [],
groups: []
可追加保留字,可定义对除了官方字段外的其他字段进行检测
yes vue/no-shared-component-data 组件中的data字段必须是一个函数且返回一个对象(避免单例)
vue/no-side-effects-in-computed-properties computed属性中,禁止对this中的变量有更改操作
vue/no-template-key template标签禁止使用key属性
vue/no-textarea-mustache textarea赋值用v-model而不是{{}}
vue/no-unused-components 检查是否存在引入的子组件未使用的情况。默认如果检测到有<component :is就不再抛任何错误 ignoreWhenBindingPresent: true ignoreWhenBindingPresent: false
设置为false则不能用:is绑定的形式,只能用条件判断了
vue/no-unused-vars 主要检测v-for和scope中定义的变量有没有使用
vue/no-use-v-if-with-v-for v-if和v-for不能并存(同一标签上) allowUsingIterationVar: false allowUsingIterationVar: true
这个设置允许使用v-if访问遍历器中的变量,但是对于外部的变量依然不允许
vue/require-component-is component组件必须有绑定的is属性
yes vue/require-prop-type-constructor 检查你props中的变量声明的类型有没有错(它没有依据白名单来检查而是列出了个黑名单)
vue/require-render-return render函数要有返回值
vue/require-v-for-key v-for要有key
vue/require-valid-default-prop 检查默认值与声明的类型是否对的上,另外array和object的默认值要是一个函数并返回对应的类型
vue/return-in-computed-property computed属性值中始终要有返回语句 treatUndefinedAsUnspecified: true
禁止隐式返回(没太理解)
vue/valid-template-root 说白了就是验证根元素。有五种情况报错:空、纯文本、多个根元素、根元素带v-for,根元素为template或slot
vue/valid-v-* 检查内置指令有无使用上的语法错误
自动Fix Rule ID 描述 默认规则 其他规则
yes vue/attribute-hyphenation 规定自定义组件上的属性名称是用驼峰还是以“-”连接 [2, 'always']
以“-”连接
[2, 'never']
驼峰,默认忽略对‘data-’, 'aria-', 'slot-scope-'的检查
ignore: ['custom-prop']
可追加忽略的字段名
yes vue/html-closing-bracket-newline html闭合标签前要不要换行,never为不换,always为换。分为单行和多行两种情况 singleline: 'never',
multiline: 'always'
建议multiline复写为'never'
yes vue/html-closing-bracket-spacing html闭合标签前或后要不要有空格,never为没有,always为有。分为<、>和/>三种情况 startTag: 'never',
endTag: 'never',
selfClosingTag: 'always'
建议selfClosingTag复写为'never'
yes vue/html-end-tags 两种情况下报错:空元素存在闭合标签,非空元素缺少闭合或自闭合标签
yes vue/html-indent 主要是规定html缩进,有五个配置项,按照默认的来就行(缩进为两个空格,可通过type来配置) type: 2,
attribute: 1,
closeBracket: 0,
alignAttributesVertically: true,
ignores: []
yes vue/html-self-closing 规定了所有标签的自闭合规范。分为html空元素,html除空元素外的其他元素,vue组件三类。可以有三个值,always规定只要没有内容就自闭合,never是禁用自闭合,any可以理解为随意 html: {
void: 'never',
normal: 'always',
component: 'always'
},
svg: 'always',
math: 'always'
建议html.normal复写为never,因为有时候没有内容的元素也可能有别的用途
yes vue/max-attributes-per-line 每行允许的最大属性个数 singleline: 1,
multiline: {
max: 1,
allowFirstLine: false
}
yes vue/mustache-interpolation-spacing 控制{{ var }} 大括号中间的变量两边是否有空格,默认有 always never
yes vue/name-property-casing 组件name变量名的写法,默认为帕斯卡 PacalCase kebab-case
可选“-”连接
yes vue/no-multi-spaces 删除属性间多余的空格
vue/no-template-shadow 规定同一作用域中不能声明重复的变量名,看例子主要是针对v-for
yes vue/prop-name-casing 规定props中的变量名写法,默认为驼峰 camelCase snake_case
vue/require-default-prop 规定除了Boolean类型的属性,在没有声明required: true的情况下,必须都得有默认值
vue/require-prop-types 规定props中的属性得声明type类型
yes vue/v-bind-style 强制v-bind的写法,默认为: shorthand longform
yes vue/v-on-style 强制v-on的写法,默认为@ shorthand longform
自动Fix Rule ID 描述 默认规则 其他规则
yes vue/attributes-order 规定了属性书写的先后顺序。也很容易理解,大概是分了几种类别,提供了一个默认顺序,也可以自己定义这几种类别的顺序。 order: ['默认省略', '详见文档'] order: ['']
可自定义顺序
yes vue/html-quotes html标签中属性值周围的引号类型 double single
vue/no-v-html 禁用v-html指令 建议开启
yes vue/order-in-components 规定了属性定义的先后顺序,太多了自行查阅文档,也很简单。
可以通过order自定义顺序,如果存在平级的(即A和B可前可后),可以写在一个数组里
order: ['默认省略', '详见文档'] order: ['']
vue/this-in-template 规定了在html片段中是否需要写this,默认禁止 never always

重写规则

以下重写部分只是根据自身进行调整,大家自行借阅

rules: {
  'vue/html-closing-bracket-newline': ['error', {
    'singleline': 'never',
    'multiline': 'never' // default: always
  }],
  'vue/html-closing-bracket-spacing': ['error', {
    'startTag': 'never',
    'endTag': 'never',
    'selfClosingTag': 'never' // default: always
  }],
  'vue/html-self-closing': ['error', {
    'html': {
      'void': 'never',
      'normal': 'never', // default: always
      'component': 'always'
    },
    'svg': 'always',
    'math': 'always'
  }],
  'vue/mustache-interpolation-spacing': ['error', 'never'], // default: always
  'vue/no-v-html': [0], // default: 'error'
}

后续工作

以上准备工作就足以满足日常开发,但是要应用到团队中去,还需要做几件事

  • 在rules中覆写eslint-plugin-vue的部分规则(团队达成一致),以下配置是我的最终配置,仅代表个人习惯(主要是rules部分)
module.exports = {
  parserOptions: {
    parser: 'babel-eslint'
  },
  env: {
    browser: true,
    node: true
  },
  globals: {},
  extends: ['plugin:vue/recommended', 'standard'],
  plugins: [
    'vue'
  ],
  rules: {
    'prefer-promise-reject-errors': [0],
    'no-throw-literal': [0],
    // 以下两条是避免eslint在fix的时候报 Cannot read property 'range' of null 的错误
    'template-curly-spacing': [0],
    'indent': [0],
    // vue 相关
    'vue/html-closing-bracket-newline': ['error', {
      'singleline': 'never',
      'multiline': 'never' // default: always
    }],
    'vue/html-closing-bracket-spacing': ['error', {
      'startTag': 'never',
      'endTag': 'never',
      'selfClosingTag': 'never' // default: always
    }],
    'vue/html-self-closing': ['error', {
      'html': {
        'void': 'never',
        'normal': 'never', // default: always
        'component': 'always'
      },
      'svg': 'always',
      'math': 'always'
    }],
    'vue/mustache-interpolation-spacing': ['error', 'never'], // default: always
    'vue/no-v-html': [0], // default: 'error'
  }
};
  • 将eslint的配置文件与webpack的dev-server融合,实现开发过程中即时看到语法错误提示。结合vscode的提示双重保险。
// 需要安装 eslint-loader 和 eslint-friendly-formatter 的npm包
{
  test: /\.(js|vue)$/,
  loader: 'eslint-loader',
  enforce: 'pre',
  exclude: [
    /node_modules/,
    /public/,
    /lib/
  ],
  options: {
    cache: true,
    formatter: require('eslint-friendly-formatter')
  }
},
  • 实现pre-commit-hook,即:如果存在语法错误,git将不能提交(todo)
  • (可选)其他富文本编辑器或IDE的语法检测及自动fix的配置