在日常項目開發中,ESLint常常扮演者可有可無的角色,我們想讓它來幫助我們檢查代碼,同時又害怕它帶來的報錯無法處理;本文帶你深入的了解ESLint的配置以及原理。
ESLint是一個插件化的代碼檢測工具,正如它官網描述的slogan:
❝可組裝的JavaScript和JSX檢查工具
❞ESLint不僅可以檢測JS,還支持JSX和Vue,它的高可擴展性讓它能夠支持更多的項目。
ESLint的前輩們提到ESLint,我們就不得不提及他的前輩們JSLint和JSHint,以及它們的區別;首先就是JSLint,它是由Douglas Crockford開發的;JSLint的靈感來源於C語言的檢查工具Lint,Lint最初被發明用來掃描C語言源文件以便找到其中的錯誤,後來隨著語言的成熟以及編譯器能夠更好的找到問題,Lint工具也逐漸不再被需要了。
JavaScript最開始被發明只是用來在網頁上做一些簡單的工作(點擊事件、表單提交等),隨著JS語言的發展完善以及項目複雜程度的增加,急需一個用來檢查JS語法或者其他問題的校驗工具,因此JSLint就誕生了;它是由Douglas Crockford在2010年開源的第一款針對JS的語法檢測工具,它和Lint做著相同的事,掃描JS的源文件來找到錯誤;它內部也是通過fs.readFile來讀取文件然後逐行來進行檢查。
JSLint Logo我們可以在全局安裝jslint,然後jslint source.js對我們的代碼進行檢查;JSLint剛開始確實幫助很多JS開發者節省了不少排查錯誤的時間,但是JSLint的問題也很明顯:所有的配置項都內置不可配置,因此你要用JSLint只能遵循Douglas Crockford老爺子自己定義的代碼風格和規範;再加上他本身推崇愛用不用的傳統,不像開發者開放配置或者修改他覺得對的規則,因此很多人也無法忍受他的規則。
由於JSLint讓很多人無法忍受,所以Anton Kovalyov基於JSLint開發了JSHint,它的初衷就是為了能讓開發者自定義規則lint rules,因此提供了豐富的配置項,給開發者極大的自由;同時它也提供了一套相當完善的編輯器插件,我們常用的VIM、Sublime、Atom、Vs Code等都有插件支持,方便開發。
JSHint LogoJSHint一開始就保持了開源軟體的風格,並且由社區來驅動,因此一推出就很快發展起來,我們熟知的一些項目或者公司也使用了JSHint,比如:Facebook、Google、Jquery、Disqus等。
JSHint相比於JSLint,最大的特點就是可配置,我們可以在項目中放入一個.jshintrc的配置文件,JSLint就會加載配置文件用於代碼分析,配置文件的部分內容如下:
{
// 禁止有未使用的變量
"unused": true,
// 禁止有未定義的變量
"undef": true,
// 無視沒有加分號行尾
"asi": true,
// 全局變量
"globals": {
"jQuery": true
}
}由於JSHint是基於JSLint開發的,因此JSLint的一些問題也繼承下來了,比如不易擴展以及不容易直接根據報錯定位到具體的配置規則等;在2013年,Zakas大佬發現JSHint無法滿足自己定製化規則的需要,因此設想開發一個基於AST的Linter,可以動態執行額外的規則,同時可以很方面的擴展規則,於是在13年6月份開源推出了全新的ESLint。
ESLint LogoESLint號稱下一代的JS Linter工具,它的靈感來源於PHP Linter,將源碼解析成AST,然後檢測AST是否符合規則;ESLint最開始使用esprima解析器將源碼解析成AST,然後就可以使用任意規則來檢測AST是否符合預期,這也是ESLint高可擴展的原因。
剛開始ESlint的推出並沒有撼動JSHint的霸主地位,由於ESlint需要將源碼轉為AST,而JSHint直接檢測源文件字符串,因此執行速度比JSHint慢很多;真正讓ESLint實現彎道超車的是ES6的出現。
2015年,ES6規範發布後,由於大部分瀏覽器支持程度不高,因此需要Babel將代碼轉換編譯成ES5或者更低版本;同時由於ES6變化很大,短期內JSHint無法完全支持,這時ESLint的高擴展性的優點顯現出來了,不僅可以擴展規則,連默認的解析器也能替換;Babel團隊就為ESLint開發了babel-eslint替換默認的解析器esprima,讓ESLint率先支持ES6。
配置ESLint被設計成完全可配置的,我們可以用多種方式配置它的規則,或者配置要檢測文件的範圍。
初始化如果想在現有的項目中引入eslint,我們可以在項目中進行初始化:
npm i eslint --save-dev
npx eslint --init在經過一系列問答後,會在項目根目錄創建一個我們熟悉的.eslintrc.js配置文件;安裝後就可以通過命令行對項目中的文件需要檢測了:
# 檢測單個文件
npx eslint file1.js file2.js
# 檢測src和scripts目錄
npx eslint src scripts一般我們會把eslint命令行配置到packages.json中:
"scripts": {
"lint": "npx eslint src scripts",
"lint:fix": "npx eslint src scripts --fix",
"lint:create": "npx eslint --init"
}這裡有一個--fix後綴,是ESLint提供自動修復基礎錯誤的功能,我們運行lint:fix後發現有一些報錯信息消失了,代碼也改變了;不過它只能修復一些基礎的不影響代碼邏輯的錯誤,比如代碼末尾加上分號、表達式的空格等等。
ESLint默認只會檢測.js後綴的文件,如果我們想對更多類型的文件進行檢測,比如.vue、.jsx,可以使用--ext選項,參數用逗號分隔:
"scripts": {
"lint": "npx eslint --ext .js,.jsx,.vue src",
}對於一些公共的js,或者測試腳本,不需要進行檢測,我們可以通過在項目根目錄創建一個.eslintignore告訴ESLint去忽略特定的目錄或者文件:
public/
src/main.js除了.eslintignore中指定的文件或目錄,ESLint總是忽略/node_modules/* 和/bower_components/*中的文件;因此對於一些目前解決不了的規則報錯,但是我們需要打包上線,在不影響運行的情況下,我們就可以利用.eslintignore文件將其暫時忽略。
ESLint一共有兩種配置方式,第一種方式是直接把lint規則嵌入原始碼中;
/* eslint eqeqeq: "error" */
var num = 1
num == '1'eqeqeq代表eslint校驗規則,error代表校驗報錯級別,後面會詳細說明;這個eslint校驗規則只會對該文件生效:
eqeqeq我們還可以使用其他注釋,更精確地管理eslint對某個文件或某一行代碼的校驗:
/* eslint-disable */
alert('該注釋放在文件頂部,eslint不會檢查整個文件')
/* eslint-enable */
alert('重新啟用eslint檢查')
/* eslint-disable eqeqeq */
alert('只禁止某一個或多個規則')
/* eslint-disable-next-line */
alert('下一行禁止eslint檢查')
alert('當前行禁止eslint檢查') // eslint-disable-line第二種方式是直接把lint規則放到我們的配置文件中,上面init初始化生成的.eslintrc.js就是一個配置文件,官方還提供了其他幾種配置文件名稱(優先級從上到下):
.eslintrc.js
.eslintrc.yaml
.eslintrc.yml
.eslintrc.json
.eslintrc
package.json一般情況下我們使用.eslintrc.js就可以了。
配置詳解我們詳細看下.eslintrc.js文件內部有哪些配置選項:
module.exports = {
"globals": {},
"env": {
"browser": true,
"es2021": true
},
"extends": "eslint:recommended",
"parse": "babel-eslint",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"rules": {}
};首先是我們的globals,ESLint會檢測未聲明的變量,並發出報錯,比如node環境中的process,瀏覽器環境下的全局變量console,以及我們通過cdn引入的jQuery定義的$等;我們可以在globals中進行變量聲明:
{
"globals": {
// true表示該變量可讀寫,false表示變量是只讀
"$": true,
"console": false
}
}但是node或者瀏覽器中的全局變量很多,如果我們一個個進行聲明顯得繁瑣,因此就需要用到我們的env,這是對環境定義的一組全局變量的預設:
{
"env": {
"browser": true,
"node": true,
"jquery": true
}
}更多的環境參數可以看ESLint聲明環境。
然後就是我們的解析器parse和parserOptions;我們上面說到ESLint可以更換解析器,"parse": "babel-eslint"就是用來指定要使用的解析器,它有以下幾個選擇:
espree:默認,ESLint自己基於esprima v1.2.2開發的一個解析器babel-eslint:一個對Babel解析器的包裝,使其能夠與ESLint兼容。@typescript-eslint/parser:將TypeScript轉換成與estree兼容的形式,以便在ESLint中使用。那麼這幾個解析器怎麼選擇呢?如果你想使用一些先進的語法(ES6789),就使用babel-eslint(需要npm安裝);如果你想使用typescript,就使用@typescript-eslint/parser。
選好了解析器,我們可以通過parserOptions給解析器傳入一些其他的配置參數:
{
"parser": "babel-eslint",
"parserOptions": {
// 代碼模塊類型,可選script(默認),module
"sourceType": "module",
// es版本號,默認為5,可以使用年份2015(同6)
"ecamVersion": 6,
// es 特性配置
"ecmaFeatures": {
"globalReturn": true, // 允許在全局作用域下使用 return 語句
"impliedStrict": true, // 啟用全局 strict mode
"jsx": true // 啟用 JSX
}
},
}
規則ESLint可以配置大量的規則,我們可以在配置文件的rules屬性自定義需要的規則:
{
"rules":{
// "semi": "off",
"semi": 0,
// "quotes": "warn",
"quotes": 1,
// "no-console": "error"
"no-console": 2
}
}對於檢驗規則,有3個報錯等級:
"warn" 或 1:開啟規則,warn級別的錯誤 (不會導致程序退出)"error" 或 2:開啟規則,error級別的錯誤(當被觸發的時候,程序會退出)有些規則沒有屬性,只需控制開啟還是關閉;有些規則可以傳入屬性,我們通過數組的方式傳入參數:
{
"rules":{
// 代碼縮進,使用tab縮進,switch語句的case縮進級別,1表示2個空格
"indent": ["error", "tab", { "SwitchCase": 1 }],
// 引號,雙引號
"quotes": ["error", "double"],
// 在語句末尾使用分號
"semi": ["error", "always"]
}
}對於剛接觸ESLint的同學,看到這麼多的規則肯定很懵逼,難道要一條一條來記麼?肯定不是的;項目的ESLint配置文件並不是一次性完成的,而是在項目開發中慢慢完善起來的,因為並不是所有的規則都是我們項目所需要的。因此我們可以先進行編碼,在編碼的過程中使用npm run lint校驗代碼規範,如果報錯,可以通過報錯信息去詳細查看是那一條規範報錯:
規範報錯查看比如這裡的報錯no-unused-vars我們可以看到它來自第六行,再去文檔查找,發現是我們在js中有一個定義了卻未使用的變量;在團隊協商後可以進一步來確定項目是否需要這條規範。
擴展如果每條規則都需要團隊協商配置還是比較繁瑣的,在項目開始配置時,我們可以先使用一些業內已經成熟的、大家普遍遵循的編碼規範(最佳實踐);我們可以通過extends欄位傳入一些規範,它接收String/Array:
{
"extends": [
"eslint:recommended",
"plugin:vue/essential",
"@vue/prettier",
"eslint-config-standard"
]
}extends可以使用以下幾種類型的擴展:
eslint:開頭的ESLint官方擴展,有兩個:eslint:recommended(推薦規範)和eslint:all(所有規範)。eslint-config:開頭的來自npm包,使用時可以省略eslint-config-,比如上面的可以直接寫成standard@:開頭的擴展和eslint-config一樣,是在npm包上面加了一層作用域scope❝需要注意的是:多個擴展中有相同的規則,以後面引入的擴展中規則為準。
❞eslint:recommended推薦使用的規則在規則列表的右側用綠色√標記。
規範報錯查看插件類型的擴展一般先通過npm安裝插件,以上面的vue為例,我們先來安裝:
npm install --save-dev eslint eslint-plugin-vue安裝後一個插件中會有很多同類型擴展可供選擇,比如vue就有以下幾種擴展:
plugin:vue/essential:必不可少的plugin:vue/recommended:推薦的plugin:vue/strongly-recommended:強烈推薦針對擴展中的規則,我們也能夠通過rules來對它進行覆寫:
{
"extends": [
"plugin:vue/recommended"
],
"rules": {
// 覆寫規則
"vue/no-unused-vars": "error"
}
}除了上面的eslint-config-standard,還有以下幾個比較知名的編碼規範:
編碼規範不過需要注意的是,很多規範不僅需要安裝擴展本身,還需要配合插件,比如eslint-config-standard,我們還需要安裝下面幾個插件才能有效:
npm i eslint-config-standard -D
npm i eslint-plugin-promise eslint-plugin-import eslint-plugin-node -D
插件在Webpack中,插件是用來擴展功能,讓其能夠處理更多的文件類型以及功能,ESLint中的插件也是同樣的作用;雖然ESLint提供了幾百種規則可供選擇,但是隨著JS框架和語法的發展,這麼多規則還是顯得不夠,因為官方的規則只能檢查標準的JS語法;如果我們寫的是vue或者react的jsx,那麼ESLint就不能檢測了。
這時就需要安裝ESLint插件,用來定製一些特色的規則進行檢測;eslint插件以eslint-plugin-開頭,使用時可以省略;比如我們上面檢測.vue文件就用到eslint-plugin-vue插件;需要注意的是,我們在配置eslint-plugin-vue這個插件時,如果僅配置"plugins": ["vue"],vue文件中template內容還是會解析失敗。
這是因為不管是默認的espree還是babel-eslint解析器都無法解析.vue中template的內容;eslint-plugin-vue插件依賴vue-eslint-parser解析器,而vue-eslint-parser解析器只會解析template內容,不會檢測script中的JS內容,因此我們還需要指定一下解析器:
{
"extends": ["eslint:recommended"],
"plugins": ["vue"],
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "babel-eslint",
"ecmaVersion": 12,
"sourceType": "module",
},
}上面parserOptions.parser不少同學肯定看的有點迷糊,這是由於外層的解析器只能有一個,我們已經用了vue-eslint-parser就不能再寫其他的;因此vue-eslint-parser的做法是在解析器選項中再傳入一個解析器選項用來處理script中的JS內容。
❝如果想讓ESLint檢測vue文件,確保將.vue後綴加入--ext選項中。
❞而react配置則較為簡單了,引入插件,選擇對應的擴展規則即可:
{
"extends": [
"eslint:recommended",
"plugin:react/recommended"
],
"parserOptions": {
// 啟用jsx語法支持
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"react"
],
}
配合prettier雖然ESLint會對我們的代碼格式進行一些檢測(比如分號、單雙引號等),但是並不能完全統一代碼風格,我們還需要一個工具Prettier;Prettier是什麼?Prettier是一個支持很多語言的代碼格式化工具,官網用了一個「貶義」的單詞來形容它opinionated,翻譯過來就是固執己見的。
PrettierPrettier還有以下四個特點:
An opinionated code formatterIntegrates with most editors那麼為什麼Prettier要用opinionated這個詞呢?每個團隊成員可能會用不同的編輯器或是不同的插件,每個插件也會有自己的格式化規範,這樣就導致了我們在開發時代碼風格極大的不統一,甚至造成不必要的衝突;Prettier就給我們定義好了風格,按照它的風格來(是不是很像JSLint);但是又沒有完全封閉,開放了一些必要的設置,這也是最後一點few options的含義;因此我們只需要將代碼的美化交給Prettier來做就好了。
首先還是安裝,我們將所需的插件進行安裝,這裡用到prettier的三個包:
npm i prettier
eslint-plugin-prettier
eslint-config-prettier首先就是這個eslint-plugin-prettier插件,它會調用prettier對你的代碼風格進行檢查,其原理是先使用prettier對你的代碼進行格式化,然後與格式化之前的代碼進行對比,如果過出現了不一致,這個地方就會被prettier進行標記。
被標記後Prettier並不會有任何提示,我們還需要對標記後的代碼進行報錯處理,在rules中進行添加配置:
{
"plugins": ["prettier"],
"rules": {
"prettier/prettier": "error",
}
}
Prettier報錯❝如果不希望Prettier影響項目打包,我們也可以將prettier的報錯由error改為warn
❞藉助ESLint的自動修復--fix,我們可以修復這種簡單的樣式問題;那如果我們想自定義一些樣式怎麼辦呢?沒關係,雖然Prettier是一個固執己見的工具,但是人家也是開放了一些配置可供我們進行自定義的,我們可以在項目中新建一個.prettierrc.json文件:
{
// 尾逗號
"trailingComma": "es5",
// 縮進長度
"tabWidth": 4,
// 代碼末尾分號
"semi": false,
// 單引號
"singleQuote": true,
// 單行代碼最大長度
"printWidth": 100,
// 對象字面量的括號
"bracketSpacing": true,
// 箭頭函數參數加括號
"arrowParens": "always",
}這裡簡單貼一些常用的,我們可以在官網選項配置找到更多的配置規則。
這樣配置後雖然能修復代碼了,但是如果遇到另一個也固執己見的擴展,比如我們引入eslint-config-standard這個擴展,它也有自己的代碼風格;如果通過Prettier格式化,standard不幹了;如果通過standard自動修復,那麼Prettier又要報錯了,兩邊都是大爺這可咋整呢?
可咋整機智的Prettier已經幫我們考慮到這個問題了,利用extends中最後一個覆蓋前面擴展的特性,我們將eslint-config-prettier配置在extends最後,就能夠關閉一些與Prettier的規則:
{
"extends": ["standard", "prettier"],
"plugins": ["prettier"],
"rules": {
"prettier/prettier": "error",
}
}另外eslint-plugin-prettier插件也附帶有plugin:prettier/recommended擴展配置,可以同時啟用插件和eslint-config-prettier擴展,因此我們可以只需要配置recommended就可以了:
{
"extends": ["standard", "plugin:prettier/recommended"],
"rules": {
"prettier/prettier": "error",
}
}Vue中為了支持Prettier,也將eslint-plugin-prettier和eslint-config-prettier整合到一起,放到了node_modules/@vue/eslint-config-prettier目錄中(加了一層作用域),因此我們在Vue腳手架生成的項目經常能看到@vue/prettier這個擴展,打開它的目錄發現其本質是一樣的:
module.exports = {
plugins: ['prettier'],
extends: [
require.resolve('eslint-config-prettier'),
require.resolve('eslint-config-prettier/vue')
],
rules: {
'prettier/prettier': 'warn'
}
}