前端框架系列之(eslint入门)

本文详细介绍了如何创建和配置eslint工程,包括安装、创建配置文件、运行命令、配置选项如env、parserOptions、解析器、处理器、全局变量、插件、规则等,并通过示例代码解释了各项配置的作用。同时,文章还提到了如何忽略文件、使用自定义配置文件和继承配置。通过这篇文章,读者可以深入理解eslint的配置机制。
摘要由CSDN通过智能技术生成

创建工程

我们创建一个叫eslint-demo的工程,然后执行npm初始化

https://github.com/913453448/eslint-demo

npm init

安装使用

安装eslint

$ npm install eslint --save-dev

创建配置文件

执行eslint的初始化

npx eslint --init

执行完毕后可以看到一个配置文件,我选的是json格式的配置文件,还有package.json中直接引用、yaml、js格式的配置文件,后面我们会讲到。

.eslintrc.json

{
   

}

可以看到,我们这里是一个空的配置文件。

运行命令

我们创建一个叫src的目录,然后创建一个demo1.js的文件测试

demo1.js:

我们随便写点代码,比如直接document页面输出一个字符串

document.write("hello eslint");

运行eslint测试:

npx eslint ./src/demo1.js

运行完毕后你会发现,没有报错跟提示,这是因为我们还没有进行任何eslint的配置,下面我们就结合demo对eslint配置逐个进行解析。

为了更好的理解eslint,我们直接clone一份源码,https://github.com/eslint/eslint.git

配置

我们这里以一个前端vue+webpack+ts+es2020的工程为demo为例子进行eslint的配置。

env&parserOptions

ESLint 允许你指定你想要支持的 JavaScript 语言选项。默认情况下,ESLint 支持 ECMAScript 5 语法。你可以覆盖该设置,以启用对 ECMAScript 其它版本和 JSX 的支持,为什么要把env跟parserOptions放在一起讲呢? 因为env中包含了对parserOptions的配置,最终两个参数传入供给parse解析器使用。

env

我们的demo需要运行在es2020的浏览器环境中,所以我们的env配置为:

{
   
  "env": {
   
    "browser": true,
    "es2020": true
  }
}

那么,env到底可以为设置为哪些呢?我们直接找到eslint的源码。

conf/environments.js:

...
const newGlobals2015 = getDiff(globals.es2015, globals.es5); // 19 variables such as Promise, 
const newGlobals2017 = {
   
    Atomics: false,
    SharedArrayBuffer: false
};
const newGlobals2020 = {
   
    BigInt: false,
    BigInt64Array: false,
    BigUint64Array: false,
    globalThis: false
};

//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------

/** @type {Map<string, import("../lib/shared/types").Environment>} */
module.exports = new Map(Object.entries({
   

    // Language
    builtin: {
   
        globals: globals.es5
    },
    es6: {
   
        globals: newGlobals2015,
        parserOptions: {
   
            ecmaVersion: 6
        }
    },
    es2015: {
   
        globals: newGlobals2015,
        parserOptions: {
   
            ecmaVersion: 6
        }
    },
    es2017: {
   
        globals: {
    ...newGlobals2015, ...newGlobals2017 },
        parserOptions: {
   
            ecmaVersion: 8
        }
    },
    es2020: {
   
        globals: {
    ...newGlobals2015, ...newGlobals2017, ...newGlobals2020 },
        parserOptions: {
   
            ecmaVersion: 11
        }
    },

    // Platforms
    browser: {
   
        globals: globals.browser
    },
    node: {
   
        globals: globals.node,
        parserOptions: {
   
            ecmaFeatures: {
   
                globalReturn: true
            }
        }
    },
    "shared-node-browser": {
   
        globals: globals["shared-node-browser"]
    },
    worker: {
   
        globals: globals.worker
    },
    serviceworker: {
   
        globals: globals.serviceworker
    },

    // Frameworks
    commonjs: {
   
        globals: globals.commonjs,
        parserOptions: {
   
            ecmaFeatures: {
   
                globalReturn: true
            }
        }
    },
    amd: {
   
        globals: globals.amd
    },
    mocha: {
   
        globals: globals.mocha
    },
    jasmine: {
   
        globals: globals.jasmine
    },
    jest: {
   
        globals: globals.jest
    },
    phantomjs: {
   
        globals: globals.phantomjs
    },
    jquery: {
   
        globals: globals.jquery
    },
    qunit: {
   
        globals: globals.qunit
    },
    prototypejs: {
   
        globals: globals.prototypejs
    },
    shelljs: {
   
        globals: globals.shelljs
    },
    meteor: {
   
        globals: globals.meteor
    },
    mongo: {
   
        globals: globals.mongo
    },
    protractor: {
   
        globals: globals.protractor
    },
    applescript: {
   
        globals: globals.applescript
    },
    nashorn: {
   
        globals: globals.nashorn
    },
    atomtest: {
   
        globals: globals.atomtest
    },
    embertest: {
   
        globals: globals.embertest
    },
    webextensions: {
   
        globals: globals.webextensions
    },
    greasemonkey: {
   
        globals: globals.greasemonkey
    }
}));

可以看到,默认是“builtin” 也就是es5,我们可以看到“es6”还可以叫“es2015”,然后还有一个“parserOptions”的配置:

 es2015: {
   
        globals: newGlobals2015,
        parserOptions: {
   
            ecmaVersion: 6
        }
    },

那么parserOptions到底是什么呢? 其实是给解析器用的参数,告诉解析器你需要利用ecmaVersion:6的语法去解析我们的源文件,那么globals属性里面又是什么东西呢?我们直接找到newGlobals2015然后点开源码,我们找到一个叫globals的第三方库,然后找到了一个叫globals.json的文件:

{
   
  ...
  "es2015": {
   
		"Array": false,
		"ArrayBuffer": false,
		"Boolean": false,
		"constructor": false,
		"DataView": false,
		"Date": false,
		"decodeURI": false,
		"decodeURIComponent": false,
		"encodeURI": false,
		"encodeURIComponent": false,
		"Error": false,
		"escape": false,
		"eval": false,
		"EvalError": false,
		"Float32Array": false,
		"Float64Array": false,
		"Function": false,
		"hasOwnProperty": false,
		"Infinity": false,
		"Int16Array": false,
		"Int32Array": false,
		"Int8Array": false,
		"isFinite": false,
		"isNaN": false,
		"isPrototypeOf": false,
		"JSON": false,
		"Map": false,
		"Math": false,
		"NaN": false,
		"Number": false,
		"Object": false,
		"parseFloat": false,
		"parseInt": false,
		"Promise": false,
		"propertyIsEnumerable": false,
		"Proxy": false,
		"RangeError": false,
		"ReferenceError": false,
		"Reflect": false,
		"RegExp": false,
		"Set": false,
		"String": false,
		"Symbol": false,
		"SyntaxError": false,
		"toLocaleString": false,
		"toString": false,
		"TypeError": false,
		"Uint16Array": false,
		"Uint32Array": false,
		"Uint8Array": false,
		"Uint8ClampedArray": false,
		"undefined": false,
		"unescape": false,
		"URIError": false,
		"valueOf": false,
		"WeakMap": false,
		"WeakSet": false
	},
   ...
}

可以看到,其实就是我们es6中内置的对象、属性、方法,所以env是提供了一个es环境,parserOptions则是负责解析es语法。我们可以看到每一个变量都是一个boolean值,false代表这个变量不允许修改,true代表可以修改。

我们继续运行一下我们的demo:

$npx eslint ./src/demo1.js

我们可以发现,我们控制台还是没啥反应,这是为什么呢?因为我们还没配置我们的rules,我们继续往下走。

parserOptions
  • sourceType - 设置为 "script" (默认) 或 "module"(如果你的代码是 ECMAScript 模块)。

在看env的时候我们看到了parserOptions的一个参数“ecmaVersion”,那么ecmaVersion还有哪些配置呢?

ecmaVersion:

  • globalReturn - 允许在全局作用域下使用 return 语句
  • impliedStrict - 启用全局 strict mode (如果 ecmaVersion 是 5 或更高)
  • jsx - 启用 JSX
  • experimentalObjectRestSpread - 启用实验性的 object rest/spread properties 支持。(**重要:**这是一个实验性的功能,在未来可能会有明显改变。 建议你写的规则 不要 依赖该功能,除非当它发生改变时你愿意承担维护成本。)

我们可能会用到jsx,所以我们把jsx配置成为true

.eslintrc.json:

{
   
  "env": {
   
    "browser": true,
    "es2020": true
  },
  "parserOptions": {
   
    "ecmaFeatures": {
   
      "jsx": true
    }
  },
}

因为我们demo需要用到webpack模块打包,所以我们需要把sourceType设置成为module

{
   
  "env": {
   
    "browser": true,
    "es2020": true
  },
 "parserOptions": {
   
    "ecmaFeatures": {
   
      "jsx": true
    },
    "sourceType": "module"
  }
}

解析器(Parser)

抽象语法树(Abstract Syntax Tree,AST),parse会把我们的源代码转换成抽象语法树,然后再对每个节点做eslint的校验,可以说是eslint中最重要的模块了,eslint默认使用的Esprima作为ast工具,ast的工具还有acorn等,之前写过一篇关于babel的文章,感兴趣的小伙伴可以去看看babel源码解析一,里面用到的就是acorn。

{
    "parser": "esprima",
    "rules": {
        "semi": "error"
    }
}

以下解析器与 ESLint 兼容:

注意,在使用自定义解析器时,为了让 ESLint 在处理非 ECMAScript 5 特性时正常工作,配置属性 parserOptions 仍然是必须的。解析器会被传入 parserOptions,但是不一定会使用它们来决定功能特性的开关。

在线转换工具:https://astexplorer.net/

打开esprima的文档我们简单看一下:

README.md

​```javascript
const espree = require("espree");

const ast = espree.parse(code, options);

​```js
const options = {
    // attach range information to each node
    range: false,

    // attach line/column location information to each node
    loc: false,

    // create a top-level comments array containing all comments
    comment: false,

    // create a top-level tokens array containing all tokens
    tokens: false,

    // Set to 3, 5 (default), 6, 7, 8, 9, or 10 to specify the version of ECMAScript syntax you want to use.
    // You can also set to 2015 (same as 6), 2016 (same as 7), 2017 (same as 8), 2018 (same as 9), 2019 (same as 10), or 2020 (same as 11) to use the year-based naming.
    ecmaVersion: 5,

    // specify which type of script you're parsing ("script" or "module")
    sourceType: "script",

    // specify additional language features
    ecmaFeatures: {

        // enable JSX parsing
        jsx: false,

        // enable return in global scope
        globalReturn: false,

        // enable implied strict mode (if ecmaVersion >= 5)
        impliedStrict: false
    }
}
​```

可以看到,esprima接受的其实就是我们传递的parserOptions参数,那么在eslint的配置中我们怎么使用parse呢?

因为我们demo是需要加载.vue文件的,所以用默认的esprima解析肯定是不行的,所以我们安装一个vue-eslint-parser,

因为vue-eslint-parser直接是包含在eslint-plugin-vue中的,所以我们直接安装一个eslint-plugin-vue:

npm install -D eslint-plugin-vue

然后直接把vue-eslint-parser配置到eslint中

.eslintrc.json:

{
   
  "env": {
   
    "browser": true,
    "es2020": true
  },
 "parserOptions": {
   
    "ecmaFeatures": {
   
      "jsx": true
    },
    "sourceType": "module"
  },
  "parser": "vue-eslint-parser"
}

我们继续运行eslint:

npx eslint ./src/*

运行后还是没任何反应,这是为什么呢? 别慌,还没到我们的rules,我们继续~

处理器(Processor)

processor可以理解为在parse解析器要解析源文件之前跟eslint的rules处理过后的构造函数,也就是说可以在parse解析之前跟eslint的rules处理过后做一些事情,processor提供了两个钩子函数,我们先看一眼eslint-plugin-vue中提供的processor:

xxx/node_modules/eslint-plugin-vue/lib/processor.js

/**
 * @author Toru Nagashima <https://github.com/mysticatea>
 */
'use strict'

module.exports = {
   
  preprocess (code) {
   
    console.log("code",code)
    return [code]
  },

  postprocess (messages) {
   
    const state = {
   
      block: {
   
        disableAll: false,
        disableRules: new Set()
      },
      line: {
   
        disableAll: false,
        disableRules: new Set()
      }
    }

    // Filter messages which are in disabled area.
    return messages[0].filter(message => {
   
      if (message.ruleId === 'vue/comment-directive') {
   
        const rules = message.message.split(' ')
        const type = rules.shift()
        const group = rules.shift()
        switch (type) {
   
          case '--':
            state[group].disableAll = true
            break
          case '++':
            state[group].disableAll = false
            break
          case '-':
            for (const rule of rules) {
   
              state[group].disableRules.add(rule)
            }
            break
          case '+':
            for (const rule of rules) {
   
              state[group].disableRules.delete(rule)
            }
            break
          case 'clear':
            state.block.disableAll = false
            state.block.disableRules.clear()
            state.line.disableAll = false
            state.line.disableRules.clear()
            break
        }
        return false
      } else {
   
        return !(
          state.block.disableAll ||
          state.line.disableAll ||
          state.block.disableRules.has(message.ruleId) ||
          state.line.disableRules.has(message.ruleId)
        )
      }
    })
  },

  supportsAutofix: true
}

里面的具体代码我们就一一解析了,我们后面在做自定义plugin的时候会详细说明一下processor,我们可以简单的看到两个回调函数,preprocess跟postprocess,preprocess是在parse解析源文件之前调用的方法,postprocess则是eslint的rules处理完毕后的回调函数。

那我们用一下eslint-plugin-vue的processor.

如果插件提供了processor的话,eslint会自动根据文件后缀调用processor,比如在eslint-plugin-vue的清单文件中我们可以看到:

xxx/node_modules/eslint-plugin-vue/lib/index.js

/*
 * IMPORTANT!
 * This file has been automatically generated,
 * in order to update it's content execute "npm run update"
 */
'use strict'

module.exports = {
   
  rules: {
   
    ....
  },
  configs: {
   
    'base': require('./configs/base'),
    'essential': require('./configs/essential'),
    'no-layout-rules': require('./configs/no-layout-rules'),
    'recommended': require(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值