vue项目集成eslint🏆
前言:相信同学们肯定纠结过eslint和prettier要不要配合使用,eslint侧重于代码语法和内部错误的校验,而prettier侧重于代码风格格式化,纠结的原因是一方面不想如此复杂地配置,另一方面还得兼容两个插件,避免起冲突,那么本文阐述了摒弃prettier的方案,使用eslint-plugin-vue对vue3项目进行eslint相关配置(vue2项目也可以使用,只不过有些规则上的区别,后面会阐述)。
介绍一下两个工具:
- eslint:eslint是一个可配置的 JavaScript 检查器。 它可以帮助你发现并修复 JavaScript 代码中的问题。 问题可以是任何东西,从潜在的运行时错误,到不遵循最佳实践,再到风格问题。
- eslint-plugin-vue:在eslint基础上开发的代码检测插件,这个插件允许我们用eslint检查vue文件中的 < template >和< script >,以及js文件中的vue代码(mixins等)。
本文解决了两个大家最关心的问题:
- 使项目在coding阶段能够通过vscode编辑器实时检测代码语法、内部错误和风格问题并高亮提示,可随时通过快速修复按钮修复可自动修复的问题(部分语法问题无法自动修复)
- 使项目能通过package.json文件的scripts模块配置的脚本自动检测、自动修复代码语法、内部错误和风格问题,并能够输出检测报告
如何集成eslint❓
创建项目时集成eslint
如果通过vue cli创建vue项目,可在创建时选择eslint选项:
项目创建好后可在package.json文件中看到这两个依赖:
是不是似曾相识?可以看看本文第一段介绍。
已有项目集成eslint
如果使用vite创建项目或者vue cli创建项目时没有选择eslint选项,就可以看这里了。
其实仔细看过vue3官网的同学会有印象
我们可以在项目中安装eslint和eslint-plugin-vue依赖:
npm install -D eslint eslint-plugin-vue
这里科普一下 -D 和 -S 的区别:
- -D:将依赖安装到package.json文件下的devDependencies模块,表示开发环境下的依赖,不用于生产环境,不会参与打包,由于eslint只在开发环境下规范代码使用,所以需要加 -D
- -S:将依赖安装到package.json文件下的dependencies模块,表示生产环境下的依赖,会参与打包,从npm5开始,加不加 -S 都是一样的效果
安装好依赖之后可以在package.json中看到:
配置eslint🍟
如果创建项目时选择了eslint,那么项目根目录下会有一个.eslintrc.js文件,如果后面集成的eslint,那么就没有这个文件,需要自己在项目根目录添加:
经历了两天的eslint(378个规则)、eslint-plugin-vue(504个规则)官网的学习,将每个规则了解一遍,得到下面的葵花宝典🌻,史上最全最规范的代码规则,包含了语法、风格、内部错误等方面的检验,无需和prettier死缠烂打,拒绝复杂配置和插件冲突。哈哈哈~~像不像打广告,雀食有些激动,话不多说,打开.eslintrc.js文件,添加以下代码:
// eslint-plugin-vue官网:https://eslint.vuejs.org/rules/
// eslint中文官网:https://eslint.nodejs.cn/docs/latest/rules/
module.exports = {
root: true,
parserOptions: {
ecmaVersion: 2020, // 支持的最新的ECMAScript语法版本
sourceType: 'module', // 如果你的代码在 ECMAScript 模块中,则设置为 "script"(默认)或 "module"
},
parser: 'vue-eslint-parser',
// eslint:recommended:eslint推荐的默认规则,eslint中文官网可查看所有推荐的默认规则
// plugin:vue/vue3-essential、plugin:vue/vue3-recommended、plugin:vue/vue3-strongly-recommended:eslint-plugin-vue推荐的基于vue3的默认规则,eslint-plugin-vue官网可查看所有推荐的默认规则
extends: [
'eslint:recommended',
'plugin:vue/vue3-essential',
'plugin:vue/vue3-recommended',
'plugin:vue/vue3-strongly-recommended',
],
// no-undef规则会检测没有声明的变量,但有些情况不是我们理想的,例如elementUI-plus、uniapp的全局变量等,在此声明即可
globals: {
ElMessage: 'readonly',
ElNotification: 'readonly',
},
env: {
browser: true, // 支持浏览器全局变量,如window、document
node: true, // 支持nodejs全局变量,如global
es6: true, // 支持es6的全局变量,如Set
},
// 跳过检测的文件
ignorePatterns: [
'auto-imports.d.ts',
'components.d.ts',
],
// "off" 或 0 - 关闭规则
// "warn" 或 1 - 打开规则作为警告(不影响退出代码)
// "error" 或 2 - 打开规则作为错误(触发时退出代码为 1)
rules: {
// eslint规则,主要对代码语法进行规范
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-var': 'error',
'no-await-in-loop': 'error',
'no-constructor-return': 'error',
'no-duplicate-imports': 'error',
'no-promise-executor-return': 'error',
'no-self-compare': 'error',
'no-template-curly-in-string': 'error',
'no-unreachable-loop': 'error',
'no-use-before-define': 'error',
'require-atomic-updates': 'error',
'accessor-pairs': 'error',
'arrow-body-style': [
'error',
'as-needed',
{
requireReturnForObjectLiteral: false,
},
],
camelcase: [
'error',
{
properties: 'always',
ignoreDestructuring: true,
ignoreImports: true,
ignoreGlobals: true,
allow: ['^\\$_'],
},
],
curly: 'error',
'default-case': 'error',
'default-case-last': 'error',
'default-param-last': 'error',
eqeqeq: [
'error',
'always',
],
'grouped-accessor-pairs': 'error',
'max-nested-callbacks': [
'error',
{
max: 3,
},
],
'max-params': 'off',
'no-array-constructor': 'error',
'no-bitwise': 'error',
'no-caller': 'error',
'no-continue': 'error',
'no-div-regex': 'error',
'no-empty-function': 'error',
'no-eq-null': 'error',
'no-eval': 'error',
'no-extend-native': 'error',
'no-extra-bind': 'error',
'no-extra-label': 'error',
'no-implicit-coercion': [
'warn',
{
boolean: true,
number: true,
string: true,
disallowTemplateShorthand: false,
allow: ['!!'],
},
],
'no-implicit-globals': 'error',
'no-implied-eval': 'error',
'no-invalid-this': 'error',
'no-iterator': 'error',
'no-label-var': 'error',
'no-labels': 'error',
'no-lone-blocks': 'error',
'no-loop-func': 'error',
'no-multi-assign': 'error',
'no-multi-str': 'error',
'no-new': 'error',
'no-new-func': 'error',
'no-new-object': 'error',
'no-new-wrappers': 'error',
'no-octal-escape': 'error',
'no-param-reassign': 'error',
'no-proto': 'error',
'no-return-assign': 'error',
'no-return-await': 'error',
'no-script-url': 'error',
'no-sequences': 'error',
'no-shadow': 'error',
'no-throw-literal': 'error',
'no-unneeded-ternary': 'error',
'no-unused-expressions': [
'error',
{
allowShortCircuit: true,
allowTernary: true,
allowTaggedTemplates: false,
enforceForJSX: false,
},
],
'no-useless-call': 'error',
'no-useless-computed-key': 'error',
'no-useless-concat': 'error',
'no-useless-constructor': 'error',
'no-useless-rename': 'error',
'object-shorthand': [
'error',
'always',
],
'one-var': [
'error',
'never',
],
'one-var-declaration-per-line': 'error',
'operator-assignment': 'error',
'prefer-arrow-callback': 'error',
'prefer-const': 'error',
'require-await': 'error',
'symbol-description': 'error',
'vars-on-top': 'error',
// eslint stylistic规则,主要对代码风格进行规范
'no-multiple-empty-lines': [
'error',
{
max: 1,
maxBOF: 0,
maxEOF: 1,
},
],
'keyword-spacing': [
'error',
{
before: true,
after: true,
},
],
'space-before-blocks': [
'error',
{
functions: 'always',
keywords: 'always',
classes: 'always',
},
],
'space-before-function-paren': [
'error',
{
anonymous: 'never',
named: 'never',
asyncArrow: 'always',
},
],
'space-in-parens': [
'error',
'never',
],
'space-infix-ops': [
'error',
{
int32Hint: false,
},
],
'switch-colon-spacing': [
'error',
{
after: true,
before: false,
},
],
'space-unary-ops': 'error',
'template-curly-spacing': [
'error',
'never',
],
'key-spacing': [
'error',
{
beforeColon: false,
afterColon: true,
mode: 'strict',
align: 'value',
},
],
'comma-dangle': [
'error',
'always-multiline',
],
semi: [
'error',
'always',
],
'semi-spacing': [
'error',
{
before: false,
after: true,
},
],
'semi-style': [
'error',
'last',
],
'no-extra-semi': 'error',
quotes: [
'error',
'single',
{
allowTemplateLiterals: true,
},
],
'array-bracket-newline': [
'error',
{
multiline: true,
minItems: 2,
},
],
'array-bracket-spacing': [
'error',
'never',
],
'array-element-newline': [
'error',
{
multiline: true,
minItems: 2,
},
],
'arrow-parens': [
'error',
'always',
],
'arrow-spacing': [
'error',
{
before: true,
after: true,
},
],
'block-spacing': [
'error',
'always',
],
'brace-style': [
'error',
'1tbs',
{
allowSingleLine: false,
},
],
'comma-spacing': [
'error',
{
before: false,
after: true,
},
],
'comma-style': [
'error',
'last',
],
'computed-property-spacing': [
'error',
'never',
],
'dot-location': [
'error',
'property',
],
'eol-last': [
'error',
'always',
],
'func-call-spacing': [
'error',
'never',
],
'function-call-argument-newline': [
'error',
'consistent',
],
'function-paren-newline': [
'error',
'multiline',
],
'generator-star-spacing': [
'error',
{
before: false,
after: true,
anonymous: 'neither',
method: {
before: true,
after: true,
},
},
],
'implicit-arrow-linebreak': [
'error',
'beside',
],
indent: [
'error',
2,
],
'linebreak-style': [
'error',
'unix',
],
'lines-around-comment': [
'error',
{
beforeBlockComment: true,
afterBlockComment: false,
beforeLineComment: false,
afterLineComment: false,
allowBlockStart: true,
allowBlockEnd: false,
allowObjectStart: true,
allowObjectEnd: false,
allowArrayStart: true,
allowArrayEnd: false,
allowClassStart: true,
allowClassEnd: false,
},
],
'multiline-ternary': [
'error',
'never',
],
'new-parens': [
'error',
'always',
],
'newline-per-chained-call': [
'error',
{ ignoreChainWithDepth: 2 },
],
'no-extra-parens': [
'warn',
'all',
{
conditionalAssign: false,
returnAssign: false,
nestedBinaryExpressions: false,
ignoreJSX: 'multi-line',
enforceForArrowConditionals: false,
enforceForSequenceExpressions: false,
enforceForNewInMemberExpressions: false,
enforceForFunctionPrototypeMethods: false,
},
],
'no-mixed-spaces-and-tabs': 'error',
'no-multi-spaces': 'error',
'no-trailing-spaces': [
'error',
{
skipBlankLines: false,
ignoreComments: false,
},
],
'no-whitespace-before-property': 'error',
'object-curly-newline': [
'error',
{
multiline: true,
minProperties: 2,
consistent: true,
},
],
'object-curly-spacing': [
'error',
'always',
{
arraysInObjects: false,
objectsInObjects: true,
},
],
'object-property-newline': [
'error',
{
allowAllPropertiesOnSameLine: false,
},
],
'operator-linebreak': [
'error',
'before',
{
overrides: {
'?': 'none',
':': 'none',
},
},
],
'padded-blocks': [
'error',
'never',
],
'rest-spread-spacing': [
'error',
'never',
],
'template-tag-spacing': [
'error',
'never',
],
'unicode-bom': [
'error',
'never',
],
'wrap-iife': [
'error',
'outside',
],
'yield-star-spacing': [
'error',
'after',
],
'no-confusing-arrow': 'error',
'no-floating-decimal': 'error',
'quote-props': [
'error',
'as-needed',
],
// eslint-plugin-vue规则,对代码语法和风格都进行了规范
'vue/attribute-hyphenation': 'error',
'vue/attributes-order': 'error',
'vue/multiline-html-element-content-newline': 'error',
'vue/component-definition-name-casing': 'off',
'vue/component-name-in-template-casing': [
'error',
'PascalCase',
{
registeredComponentsOnly: true,
ignores: [],
},
],
'vue/component-options-name-casing': [
'error',
'PascalCase',
],
'vue/custom-event-name-casing': [
'error',
'kebab-case',
],
'vue/html-comment-content-newline': [
'error',
{
singleline: 'never',
multiline: 'always',
},
],
'vue/html-comment-content-spacing': [
'error',
'always',
],
'vue/match-component-file-name': [
'error',
{
extensions: [
'jsx',
'tsx',
'vue',
],
shouldMatchCase: false,
},
],
'vue/no-child-content': 'error',
'vue/no-multiple-objects-in-class': 'error',
'vue/no-potential-component-option-typo': [
'error',
{
presets: ['vue'],
},
],
'vue/no-reserved-component-names': [
'error',
{
disallowVueBuiltInComponents: true,
disallowVue3BuiltInComponents: true,
},
],
'vue/no-template-target-blank': [
'error',
{
allowReferrer: false,
enforceDynamicLinks: 'always',
},
],
'vue/no-this-in-before-route-enter': 'error',
'vue/no-unused-properties': [
'error',
{
groups: ['props'],
deepData: false,
ignorePublicMembers: false,
},
],
'vue/no-unused-refs': 'error',
'vue/no-use-computed-property-like-method': 'error',
'vue/no-useless-mustaches': [
'error',
{
ignoreIncludesComment: false,
ignoreStringEscape: false,
},
],
'vue/no-useless-v-bind': [
'error',
{
ignoreIncludesComment: false,
ignoreStringEscape: false,
},
],
'vue/no-v-text-v-html-on-component': 'error',
'vue/no-v-text': 'error',
'vue/padding-line-between-blocks': [
'error',
'always',
],
'vue/prefer-import-from-vue': 'error',
'vue/prefer-prop-type-boolean-first': 'error',
'vue/prefer-separate-static-class': 'error',
'vue/prefer-true-attribute-shorthand': 'error',
'vue/require-direct-export': [
'error',
{
disallowFunctionalComponentFunction: true,
},
],
'vue/require-emit-validator': 'error',
'vue/v-for-delimiter-style': [
'error',
'in',
],
'vue/v-on-function-call': [
'error',
'never',
{
ignoreIncludesComment: false,
},
],
'vue/array-bracket-newline': [
'error',
{
multiline: true,
minItems: 2,
},
],
'vue/array-bracket-spacing': [
'error',
'never',
],
'vue/eqeqeq': [
'error',
'always',
],
'vue/no-constant-condition': 'error',
'vue/no-empty-pattern': 'error',
'vue/no-irregular-whitespace': 'error',
'vue/no-loss-of-precision': 'error',
'vue/no-restricted-syntax': [
'error',
{
selector: 'WithStatement',
message: 'With statement are not allowed.',
},
],
'vue/no-sparse-arrays': 'error',
'vue/no-useless-concat': 'error',
'vue/object-shorthand': [
'error',
'always',
],
'vue/prefer-template': 'error',
'vue/quote-props': [
'error',
'as-needed',
],
'vue/multi-word-component-names': 'off', // 组件和页面的name必须是多单词组成的,可用-或者大驼峰连接
'vue/no-undef-properties': [ // 禁止使用未定义的属性,这个规则无法检测到外部文件、外部组件、mixins中的变量,可在ignores中加入需要忽略的关键字
'error',
{
ignores: [],
},
],
'vue/no-undef-components': [ // 禁止使用未定义的组件,这个规则无法检测到外部文件、外部组件、mixins中的变量,可在ignores中加入需要忽略的关键字
'error',
{
ignorePatterns: [
'^el-',
'^g-gantt-',
'router-view',
],
},
],
'vue/script-indent': [
'error',
2,
{
baseIndent: 0,
switchCase: 0,
ignores: [],
},
],
'vue/block-tag-newline': [
'error',
{
singleline: 'always',
multiline: 'always',
maxEmptyLines: 0,
},
],
},
// 当eslint默认的indent规则使用tab时,vue/script-indent规则会和eslint默认的indent规则冲突,为了兼容两者,所以这里要关闭eslint的默认规则
overrides: [
{
files: ['*.vue'],
rules: {
indent: 'off',
},
},
],
};
通过阅读代码中的注释相信大家能明白各种配置的含义,大部分规则的含义请移步:eslint官网、eslint-plugin-vue官网。
eslint、eslint-plugin-vue官网阅读技巧🧀
为了让大家能更有效率地理解规则及相关配置,下面做一些技巧性提示。如果不需要请直接跳过。
eslint官网阅读技巧🍻
默认开启的规则🍝
眼熟吧?打开.eslintrc.js文件:
此处加入eslint:recommended规则集,英译过来就是eslint推荐规则,eslint:recommended默认是开启的,就不需要在rules选项里面配置该规则了,但是如果不需要某个规则的时候需要去禁用它(使用off或0),如下:
需要使用非默认规则🍔
相反的,如果我们想使用的规则不在推荐规则集内,就需要自行添加(error或2表示检测到时提示报错,warn或1表示提示警告),例如:
规则弃用了怎么办❓
细心的小伙伴发现eslint官网有很多废弃的规则:
而且担心还能不能使用,如果不能使用,那现在正在使用的规则以后废弃了怎么办?请阅读eslint规则废弃政策,翻译一下:
所以不用担心,是可以一直使用的,废弃代表着不维护而已。
但是有的同学又有疑问:我想看看废弃的规则的含义,在哪里找?
问得好!其实大部分废弃的规则都是与代码风格相关的,可以在eslint stylistic官网找到,eslint从v8.53.0版本开始废弃了代码风格规则集,由eslint stylistic代替维护。从官网中可以看到,eslint中废弃的规则迁移到了@stylistic/eslint-plugin-js规则集中:
下面两个规则集分别是针对typescript、jsx语法的规则集,其余的请自行查阅。
因此eslint废弃的规则会映射到@stylistic/eslint-plugin-js规则集,所以我们可以直接使用这些废弃的规则,也可以使用@stylistic/eslint-plugin-js规则集,像这样:
eslint-plugin-vue官网阅读技巧🍻
规则的分类🍝
这是eslint-plugin-vue规则的菜单,可以看出分成了很多种类,Available rules为可用的规则(全部规则),下面的英译过来分别是:基本规则、优先级A:基本规则、优先级A:基于vue3的基本规则、优先级A:基于vue2的基本规则、优先级B:强烈推荐、优先级B:基于vue3的强烈推荐、优先级C:推荐、未分类的规则、扩展规则、已废弃的规则。根据分类名称可知有的规则集适用于vue2,有的适用于vue3,所以前面我说此文章是对vue3进行eslint配置,意为我使用的都是基于vue3的规则,大家如果是vue2项目,自行在.eslintrc.js文件的extends选项中添加vue2相关的规则集,记得删除vue3的规则集哦。
我们随便打开一条规则:
证明这个规则默认在这些规则集中,这些规则集是不是又很眼熟?我们打开.eslintrc.js文件:
所以我们已经继承了这些规则集,默认是开启了上面举例的规则,就不需要在rules选项里面配置该规则了,但是若不想用此规则则需要将其置为off或0。
未分类的规则🍝
可以看到此规则并不属于任何规则集,所以如果我们需要使用它,必须在.eslintrc.js文件中的rules选项中进行配置:
扩展规则🍝
随便点开一个扩展规则,是不是很眼熟哇?就是前面eslint讲废弃规则讲到的eslint stylistic,这段话翻译过来:
所以至此形成了一个闭环,eslint-plugin-vue基于eslint,eslint代码风格相关规则迁移至eslint stylistic,它主要是基于js的,也就是< script >,而eslint-plugin-vue的扩展规则也是代码风格相关规则,他主要是基于< template >的,根据需求可以两者结合或者只取其一。
配置package.json脚本自动检测代码🥗
scripts选项中加入以下代码:
"scripts": {
"lint": "npx eslint --ext .js,.vue ./src vite.config.js .eslintrc.js",
"lint:fix": "npx eslint --ext .js,.vue --fix ./src vite.config.js .eslintrc.js",
"lint:out": "npx eslint --ext .js,.vue -o eslint-output.html -f html ./src vite.config.js .eslintrc.js",
"lint:fixout": "npx eslint --ext .js,.vue --fix -o eslint-output.html -f html ./src vite.config.js .eslintrc.js"
},
各脚本含义:
- lint:检测 js、vue文件,包括src目录、vite.config.js文件、.eslintrc.js文件
- lint:fix:检测 js、vue文件,包括src目录、vite.config.js文件、.eslintrc.js文件,并自动修复
- lint:out:检测 js、vue文件,包括src目录、vite.config.js文件、.eslintrc.js文件,并输出检测报告
- lint:fixout:检测 js、vue文件,包括src目录、vite.config.js文件、.eslintrc.js文件,并自动修复且输出检测报告
此时可以运行 npm run lint,在终端可以看到输出的检测结果:
具体到哪个文件多少行都会显示,按住ctrl并单击能直接定位到有问题的地方。
运行 npm run lint:out,可以生成检测报告(报告名称在npm run lint:out命令中有配置):
打开此文件可在浏览器中查看详情:
lint:fix、lint:fixout请自行实验。至此,大家有没有发现好像编辑器里面的代码并没有给出任何报错或警告,好,接下来我们就对vscode做手脚了。
vscode自动检测代码及手动修复🥙
vscode扩展搜索eslint并安装:
此时我们代码中如果有问题,会出现报错或警告:
鼠标移入报错或警告会提示相应内容:
点击快速修复,出现以下选项:
第一个选项是只修复这一条问题,最后一个选项是修复此文件中所有可修复的问题,其余选项为使用禁用语句来禁用此报错或警告。
如果有时候该插件不工作了,可以运行脚本的命令,可以唤醒插件,感觉很玄学,但很有用,如果还是不行,可以禁用eslint插件再开启或者重新打开vscode。
谢谢捧场~~🍗