前言
兩年多以前,我剛剛學前端沒多久。老師教我們使用可eslint。當我把eslint安裝上的時候,滿屏的錯誤一度讓人崩潰,我當時心裏想,這麼難用的鬼東西
,怎麼會有人用呢?
時至今日,身邊還有些朋友,對eslint
望而生畏。
今天這篇文章,主要聊聊什麼是Eslint,爲什麼要用它。它的實現原理是什麼。工作中如何使用的Eslint,以及如何自定義Eslint規則。
本文整理自以下文章:
- 掘金:eslint工作原理探討 @zhangwang
- 手摸手教你寫eslint插件)
- 慕課網:《大前端》第七週「團隊協作」
What & Why
爲什麼要使用Eslint?
JS是一個動態的弱類型語言,在代碼編寫的過程中,經常會出錯,而因爲其沒有編譯程序,爲了尋找代碼錯誤的地方,需要在執行的過程中不斷的調試。
ESlint的作用就是讓你在開發過程中發現自己的代碼問題以及不規範的地方,提前發現問題所在,並且可以規範團隊的代碼風格保持一致。
什麼是Eslint?
ESlint是一個開源的Js代碼檢查工具,它的目標是提供一個插件化的javascript代碼檢測工具。
ESLint 的核心可能就是其中包含的各種規則,這些規則大多爲衆多開發者經驗的結晶:
總的來說,ESlint是一套每一個人都應該瞭解的並且遵循的JS代碼規範。它可以讓我們的代碼風格一致、更加健壯、減少錯誤並用上社區的最佳實踐。
原理
在許多方面,它和 JSLint、JSHint 相似,除了少數的例外:
- ESLint 使用 Espree 解析 JavaScript。
- ESLint 使用 AST 去分析代碼中的模式
- ESLint 是完全插件化的。每一個規則都是一個插件並且你可以在運行時添加更多的規則。
AST是Abstract Syntax Tree(抽象語法樹)的縮寫。
也就是說,eslint使用Espress把js語法轉換成AST。然後通過 AST selectors
找到靜態代碼中的內容,再根據rule
的規則去判斷這一段js是否符合eslint的規範。
安裝和初始化ESlint
新建一個空的文件夾,執行以下的命令:
1、 npm init -y
2、 npm install eslint -D
2、 npx eslint --init
完成以上的步驟,我們將會得到以下的文件夾:
rule是如何工作的?
ESLint 的核心就是規則(rule),每條規則都是獨立的,且都可以被設置爲禁止off
🈲️,警告warn
⚠️,或者報錯error
❌。
我們選擇"no-debugger": "error"
來看看 rule 是如何工作的。源碼如下:
module.exports = {
meta: {
type: "problem",
docs: {
description: "disallow the use of `debugger`",
category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-debugger"
},
fixable: null,
schema: [],
messages: {
unexpected: "Unexpected 'debugger' statement."
}
},
create(context) {
return {
DebuggerStatement(node) {
context.report({
node,
messageId: "unexpected"
});
}
};
}
};
一條 rule 就是一個 node 模塊,其主要由 meta
和 create
兩部分組成,其中
meta
代表了這條規則的元數據,如其類別,文檔,可接收的參數的schema
等等。create
:如果說 meta 表達了我們想做什麼,那麼create
則用表達了這條 rule 具體會怎麼分析代碼;
Create 返回的是一個對象,其中最常見的鍵的名字可以是上面我們提到的選擇器,在該選擇器中,我們可以獲取對應選中的內容,隨後我們可以針對選中的內容作一定的判斷,看是否滿足我們的規則,如果不滿足,可用 context.report()
拋出問題,ESLint 會利用我們的配置對拋出的內容做不同的展示。
上面的代碼實際上表明在匹配到 debugger
語句時,會拋出 "Unexpected 'debugger' statement." 。
關於更多的Rules規則,可以查看「eslint工作原理探討」
plugin
plugin 有兩重概念:
- 一是 ESLint 配置項中的字段,如
plugins: ['react',],
; - 二是社區封裝的 ESLint plugin,在 npm 上搜索
eslint-plugin-
就能發現很多,比較出名的有 eslint-plugin-react ,eslint-plugin-import
plugin 其實可以看作是第三方規則的集合,ESLint 本身規則只會去支持標準的 ECMAScript
語法,但是如果我們想在 React 中也使用 ESLint 則需要自己去定義一些規則,這就有了 eslint-plugin-react 。
我們在日常的工作中,也可以自定義符合自己團隊風格的plugin
提供給其他的隊友使用。
工作中是如何使用的?
通常我們再日程的工作中,不會使用npx eslint
執行代碼檢查,而是在IDE中自動提醒Eslint的錯誤。
在Vscode中,需要安裝Eslint插件。
如果使用該插件,需要在項目中或者全局安裝eslint
.否則,eslint插件會報錯。
當然,如果你用的webstorm,就不用這麼麻煩安裝插件啦。
VsCode中可以使用自動保存autoSave
, ctrl P
,使用保存時
自動格式化eslint
這裏有一份「大前端」課程中提供的簡單settings
配置,可以供小夥伴們添加到settings.json中使用:
...
"eslint.validate": [
"javascriptreact",
"typescriptreact",
{
"language": "html",
"autoFix": true
},
{
"language": "vue",
"autoFix": true
},
{
"language": "javascript",
"autoFix": true
},
{
"language": "typescript",
"autoFix": true
}
],
...
"editor.codeActionsOnSave": {
"source.fixAll.tslint": true,
"source.fixAll.eslint": true
},
// prettier
"prettier.trailingComma": "es5",
// vetur
"vetur.format.defaultFormatter.js": "none",
"vetur.validation.template": false,
// default use eslint, NO NEED re-config for twice
// "vetur.format.defaultFormatterOptions": {
// "prettier": {
// "semi": false,
// "singleQuote": true,
// "eslintIntegration": true,
// "trailingComma": "all"
// }
// },
// Files exclude from tree
自定義的ESLint規則
只需要滿足 ESLint 的規定,ESLint 支持自定義 parser,實際上社區在這方面也做了很多工作。比如
- babel-eslint,A wrapper for Babel's parser used for ESLint
- typescript-eslint-parser,讓我們可以 lint TS 代碼
自定義的 parser 使用方法如下:
{
"parser": "./path/to/awesome-custom-parser.js"
}
通過自定義 parser ,ESLint 的使用場景又被大大拓展。
自定義eslint的流程是這樣的:
下面,我們結合一個小例子,看看自定義的規則是如何實現的:
插件目標:禁止項目中setTimeout
的第二個參數是數字。
實現步驟
1、安裝NPM包
ESLint官方爲了方便開發者開發插件,提供了使用Yeoman模板(generator-eslint
)。
對於Yeoman我們只需知道它是一個腳手架工具,用於生成包含指定框架結構的工程化目錄結構。
npm install -g yo generator-eslint
創建一個文件夾:
mkdir eslint-plugin-demo
cd eslint-plugin-demo
命令行初始化ESLint插件的項目結構:
yo eslint:plugin
下面進入命令行交互流程,流程結束後生成ESLint插件項目框架和文件。
? What is your name? OBKoro1
? What is the plugin ID? korolint // 這個插件的ID是什麼
? Type a short description of this plugin: XX公司的定製ESLint rule // 輸入這個插件的描述
? Does this plugin contain custom ESLint rules? Yes // 這個插件包含自定義ESLint規則嗎?
? Does this plugin contain one or more processors? No // 這個插件包含一個或多個處理器嗎
// 處理器用於處理js以外的文件 比如.vue文件
create package.json
create lib/index.js
create README.md
現在可以看到在文件夾內生成了一些文件夾和文件,但我們還需要創建規則具體細節的文件。
2、創建規則
上一個命令行生成的是ESLint插件的項目模板,這個命令行是生成ESLint插件具體規則的文件。
yo eslint:rule // 生成 eslint rule的模板文件
創建規則命令行交互:
? What is your name? OBKoro1
? Where will this rule be published? (Use arrow keys) // 這個規則將在哪裏發佈?
❯ ESLint Core // 官方核心規則 (目前有200多個規則)
ESLint Plugin // 選擇ESLint插件
? What is the rule ID? settimeout-no-number // 規則的ID
? Type a short description of this rule: setTimeout 第二個參數禁止是數字 // 輸入該規則的描述
? Type a short example of the code that will fail: 佔位 // 輸入一個失敗例子的代碼
create docs/rules/settimeout-no-number.md
create lib/rules/settimeout-no-number.js
create tests/lib/rules/settimeout-no-number.js
加了具體規則文件的項目結構
.
├── README.md
├── docs // 使用文檔
│ └── rules // 所有規則的文檔
│ └── settimeout-no-number.md // 具體規則文檔
├── lib // eslint 規則開發
│ ├── index.js 引入 導出rules文件夾的規則
│ └── rules // 此目錄下可以構建多個規則
│ └── settimeout-no-number.js // 規則細節
├── package.json
└── tests // 單元測試
└── lib
└── rules
└── settimeout-no-number.js // 測試該規則的文件
3、安裝項目依賴
npm install
rule完整文件
lib/rules/settimeout-no-number.js
:
module.exports = {
meta: {
docs: {
description: "setTimeout 第二個參數禁止是數字",
},
fixable: null, // 修複函數
},
// rule 核心
create: function (context) {
// 公共變量和函數應該在此定義
return {
// 返回事件鉤子
'CallExpression': (node) => {
if (node.callee.name !== 'setTimeout') return // 不是定時器即過濾
const timeNode = node.arguments && node.arguments[1] // 獲取第二個參數
if (!timeNode) return // 沒有第二個參數
// 檢測報錯第二個參數是數字 報錯
if (timeNode.type === 'Literal' && typeof timeNode.value === 'number') {
context.report({
node,
message: 'setTimeout第二個參數禁止是數字'
})
}
}
};
}
};
context.report():這個方法是用來通知ESLint這段代碼是警告或錯誤的,用法如上。在這裏查看context
和context.report()
的文檔。
規則寫完了,原理就是依據AST
解析的結果,做針對性的檢測,過濾出我們要選中的代碼,然後對代碼的值進行邏輯判斷。
4、發佈插件
eslint插件都是以npm
包的形式來引用的,所以需要把插件發佈一下:
- 註冊:如果你還未註冊npm賬號的話,需要去註冊一下。
- 登錄npm:
npm login
- 發佈
npm
包:npm publish
即可,ESLint已經把package.json
弄好了。
5、集成到項目:
安裝npm
包:npm i eslint-plugin-korolint -D
常規的方法: 引入插件一條條寫入規則
// .eslintrc.js
module.exports = {
plugins: [ 'korolint' ],
rules: {
"korolint/settimeout-no-number": "error"
}
}
extends
繼承插件配置:
當規則比較多的時候,用戶一條條去寫,未免也太麻煩了,所以ESLint可以繼承插件的配置:
修改一下lib/rules/index.js
文件:
'use strict';
var requireIndex = require('requireindex');
const output = {
rules: requireIndex(__dirname '/rules'), // 導出所有規則
configs: {
// 導出自定義規則 在項目中直接引用
koroRule: {
plugins: ['korolint'], // 引入插件
rules: {
// 開啓規則
'korolint/settimeout-no-number': 'error'
}
}
}
};
module.exports = output;
使用方法:
使用extends
來繼承插件的配置,extends
不止這種繼承方式,即使你傳入一個npm包,一個文件的相對路徑地址,eslint也能繼承其中的配置。
// .eslintrc.js
module.exports = {
extends: [ 'plugin:korolint/koroRule' ] // 繼承插件導出的配置
}
總結
ESLint 可謂是現代前端開發過程中必備的工具了。ESLint 做爲必備工具之一,能減少團隊協作的問題,個人代碼風格問題,減少Bug的錯誤,是非常值得我們深入瞭解學習的。
這篇文章,我在咖啡廳花了整整一下午整理學習,收穫頗豐。感謝掘金作者@OBKoro1,@zhangwang的分享。
更多參考文章