其實很早的時候就想嘗試 ESLint 了,但是很多次都是玩了一下就覺得這東西巨複雜,一執行檢查就是滿屏的
error
,簡直是不堪入目,遂放棄。直到某天終於下定決心深入看了文件,才發現其實挺簡單的,只是當時沒有看到合適入門教程而已。我相信很多人也有著跟我一樣的經歷,所以希望將自己的踩坑心得記錄下來,讓後來者更輕易地掌握 ESLint 的使用,因為它確實是個好東西。
JavaScript 是一門神奇的動態語言,它在帶給我們程式設計的靈活性的同時也悄悄埋下了一些地雷。除了基本的語法錯誤能在程式一啟動的時候被檢測到之外,很多隱含的錯誤都是在執行的時候才突然地蹦出來。除非你的程式有著 100%的測試覆蓋率,否則說不定哪天就會因為一個xxx is undefined
而導致程式崩潰,而為了避免這樣的錯誤可能你只需要在提交程式碼的時候用工具靜態分析一下,僅此而已。
ESLint 是一個外掛化的 javascript 程式碼檢測工具,它可以用於檢查常見的 JavaScript 程式碼錯誤,也可以進行程式碼風格檢查,這樣我們就可以根據自己的喜好指定一套 ESLint 配置,然後應用到所編寫的專案上,從而實現輔助編碼規範的執行,有效控制專案程式碼的質量。
手把手入門
在開始使用 ESLint 之前,我們需要通過 NPM 來安裝它:
1 |
$ npm install -g eslint |
我從 Gist 上找到了自己幾年前寫的一個小函式,將其儲存為檔案merge.js
:
1 2 3 4 5 6 7 8 9 10 |
function merge () { var ret = {}; for (var i in arguments) { var m = arguments[i]; for (var j in m) ret[j] = m[j]; } return ret; } console.log(merge({a: 123}, {b: 456})); |
然後執行node merge.js
確保它是可以正確執行的(輸出結果為{ a: 123, b: 456 }
)。
接著我們執行以下命令來使用 ESLint 檢查:
1 |
$ eslint merge.js |
可以看到,沒有任何輸出結果。這是因為我們沒有指定任何的配置,除非這個檔案是有語法錯誤,否則應該是不會有任何提示的。現在我們先使用內建的eslint:recommended
配置,它包含了一系列核心規則,能報告一些常見的問題。
首先新建 ESLint 配置檔案.eslintrc.js
:
1 2 3 |
module.exports = { extends: 'eslint:recommended', }; |
重新執行eslint merge.js
可以看到輸出了 2 個錯誤:
1 2 3 4 5 |
/example/merge.js 10:1 error Unexpected console statement no-console 10:1 error 'console' is not defined no-undef ✖ 2 problem (2 error, 0 warnings) |
這兩條提示資訊還是足夠我們搞清楚是怎麼回事的:
- Unexpected console statement no-console – 不能使用
console
- ‘console’ is not defined no-undef –
console
變數未定義,不能使用未定義的變數
針對第 1 條提示,我們可以禁用no-console
規則。將配置檔案.eslintrc.js
改為這樣:
1 2 3 4 5 6 |
module.exports = { extends: 'eslint:recommended', rules: { 'no-console': 'off', }, }; |
說明:配置規則寫在rules
物件裡面,key
表示規則名稱,value
表示規則的配置,具體說明見下文。
重新執行檢查還是提示no-undef
:
1 2 3 4 |
/example/merge.js 10:1 error 'console' is not defined no-undef ✖ 1 problem (1 error, 0 warnings) |
這是因為 JavaScript 有很多種執行環境,比如常見的有瀏覽器和 Node.js,另外還有很多軟體系統使用 JavaScript 作為其指令碼引擎,比如 PostgreSQL 就支援使用 JavaScript 來編寫儲存引擎,而這些執行環境可能並不存在console
這個物件。另外在瀏覽器環境下會有window
物件,而 Node.js 下沒有;在 Node.js 下會有process
物件,而瀏覽器環境下沒有。
所以在配置檔案中我們還需要指定程式的目標環境:
1 2 3 4 5 6 7 8 9 |
module.exports = { extends: 'eslint:recommended', env: { node: true, }, rules: { 'no-console': 'off', }, }; |
再重新執行檢查時,已經沒有任何提示輸出了,說明merge.js
已經完全通過了檢查。
配置檔案
ESLint 還可以在專案的package.json
檔案中指定配置,直接將上文中的module.exports
的值寫到eslintConfig
裡面即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{ "name": "my-package", "version": "0.0.1", "eslintConfig": { "extends": "eslint:recommended", "env": { "node": true }, "rules": { "no-console": "off" } } } |
另外還可以在執行eslint
命令時通過命令列引數來指定,詳細文件可以參考這裡:Configuring ESLint – 配置
規則
每條規則有 3 個等級:off
、warn
和error
。off
表示禁用這條規則,warn
表示僅給出警告,並不會導致檢查不通過,而error
則會導致檢查不通過。
有些規則還帶有可選的引數,比如comma-dangle
可以寫成[ "error", "always-multiline" ]
;no-multi-spaces
可以寫成[ "error", { exceptions: { "ImportDeclaration": true }}]
。
規則的詳細說明文件可以參考這裡:Rules – 規則
使用共享的配置檔案
上文我們以eslint:recommended
為基礎配置,然後在此之上修改no-console
這條規則。而在大多數時候,我們可能會根據自己個人或團隊的習慣,定製更多的規則,比如限定縮排是 2 個空格和使用單引號的字串等。而如果每一個專案都要這樣寫到.eslintrc.js
檔案上,管理起來會比較麻煩。
我們可以將定義好規則的.eslintrc.js
檔案儲存到一個公共的位置,比如public-eslintrc.js
:
1 2 3 4 5 6 7 8 9 10 11 |
module.exports = { extends: 'eslint:recommended', env: { node: true, }, rules: { 'no-console': 'off', 'indent': [ 'error', 2 ], 'quotes': [ 'error', 'single' ], }, }; |
然後將原來的.eslintrc.js
檔案改成這樣:
1 2 3 |
module.exports = { extends: './public-eslintrc.js', }; |
為了驗證這樣的修改是否生效,將merge.js
中的var ret = {};
這一行前面多加一個空格,再執行 ESLint 檢查:
1 2 3 4 |
/example/merge.js 2:4 error Expected indentation of 2 space characters but found 3 indent ✖ 1 problem (1 error, 0 warnings) |
這時候提示的是縮排只能為 2 個空格,而檔案的第 2 行卻發現了 3 個空格,說明公共配置檔案public-eslintrc.js
已經生效了。
我們還可以使用已經發布到 NPM 上的 ESLint 配置,這些配置的模組名一般以eslint-config-
為字首,比如我在學習 ESLint 時自己編寫的一個配置名為eslint-config-lei
。要使用這個配置,先執行以下命令安裝它:
1 |
$ npm install -g eslint-config-lei |
注意:用於我們的eslint
命令是全域性安裝的,所有用到的eslint-config-*
模組也必須全域性安裝,否則將無法正確載入。這是一個已知的 Bug,參考這裡:Error: Cannot read config package for shareable config using global eslint #4822
然後將.eslintrc.js
檔案改成這樣:
1 2 3 |
module.exports = { extends: 'lei', }; |
再執行 ESLint 檢查,可以看到輸出如下的提示:
1 2 3 4 5 6 7 8 9 10 11 12 |
/example/merge.js 1:15 warning Unexpected space before function parentheses space-before-function-paren 2:3 error Unexpected var, use let or const instead no-var 3:8 error Unexpected var, use let or const instead no-var 4:5 error Unexpected var, use let or const instead no-var 5:10 error Unexpected var, use let or const instead no-var 10:19 warning A space is required after '{' object-curly-spacing 10:26 warning A space is required before '}' object-curly-spacing 10:29 warning A space is required after '{' object-curly-spacing 10:36 warning A space is required before '}' object-curly-spacing ✖ 9 problems (4 errors, 5 warnings) |
ESLint 配置檔案中的extends
還可以用來指定各種來源的配置引用,具體說明可以參考以下連結:
- Using a shareable configuration package – 使用共享的模組
- Using the configuration from a plugin – 使用外掛
- Using a configuration file – 使用配置檔案
- Using “eslint:all” – 使用”eslint:all”
程式碼格式化
在ESLint 規則列表頁面,我們發現有些規則的旁邊會帶有一個橙色扳手圖示,表示在執行eslint
命令時指定--fix
引數可以自動修復該問題。
接著上文使用eslint-config-lei
配置的檢查,我們嘗試在執行檢查時新增--fix
引數:
1 |
$ eslint merge.js --fix |
執行完畢,沒有發現任何提示。再開啟merge.js
檔案發現已經變成了這樣:
1 2 3 4 5 6 7 8 9 10 |
function merge() { const ret = {}; for (const i in arguments) { const m = arguments[i]; for (const j in m) ret[j] = m[j]; } return ret; } console.log(merge({ a: 123 }, { b: 456 })); |
主要的變化有以下三部分:
- 宣告函式時,函式名與引數列表的空格不見了:
merge ()
修改為merge()
var
宣告的變數變成了const
宣告:var ret = {}
修改為const ret = {}
- 物件的內容與花括號之間增加了空格:
{a: 123}
修改為{ a: 123 }
我們可以利用這個特性來自動格式化專案程式碼,這樣就可以保證程式碼書寫風格的統一。
釋出自己的配置
前文關於「共享的配置檔案」一小節已經提到,可以在extends
中指定一個檔名,或者一個eslint-config-
開頭的模組名。為了便於共享,一般推薦將其釋出成一個 NPM 模組。
其原理就是在載入模組時輸出原來.eslintrc.js
的資料。比如我們可以建立一個模組eslint-config-my
用於測試。
新建檔案eslint-config-my/index.js
:
1 2 3 4 5 6 7 8 9 10 11 12 |
module.exports = { extends: 'eslint:recommended', env: { node: true, es6: true, }, rules: { 'no-console': 'off', 'indent': [ 'error', 2 ], 'quotes': [ 'error', 'single' ], }, }; |
再新建檔案eslint-config-my/package.json
:
1 2 3 4 5 |
{ "name": "eslint-config-my", "version": "0.0.1", "main": "index.js" } |
為了能讓eslint
正確載入這個模組,我們需要執行npm link
將這個模組連結到本地全域性位置:
1 |
$ npm link eslint-config-my |
然後將檔案.eslintrc.js
改成這樣:
1 2 3 |
module.exports = { extends: 'my', }; |
說明:在extends
中,eslint-config-my
可簡寫為my
。
在執行eslint merge.js
檢查,可看到沒有任何錯誤提示資訊,說明eslint
已經成功載入了eslint-config-my
的配置。如果我們使用npm publish
將其釋出到 NPM 上,那麼其他人通過npm install eslint-config-my
即可使用我們共享的這個配置。
另外可以參考我自己寫的一個 ESLint 配置模組:eslint-config-lei
關於共享 ESLint 配置的詳細文件可參考:Shareable Configs – 可共享的配置
例外情況
儘管我們在編碼時懷著嚴格遵守規則的美好願景,而凡事總有例外。定立 ESLint 規則的初衷是為了避免自己犯錯,但是我們也要避免不顧實際情況而將其搞得太過於形式化,本末倒置。
ESLint 提供了多種臨時禁用規則的方式,比如我們可以通過一條eslint-disable-next-line
備註來使得下一行可以跳過檢查:
1 2 3 |
// eslint-disable-next-line var a = 123; var b = 456; |
在上面的示例程式碼中,var a = 123
不會受到檢查,而var b = 456
則右恢復檢查,在上文我們使用的eslint-config-lei
的配置規則下,由於不允許使用var
宣告變數,則var b
這一行會報告一個error
。
我們還可以通過成對的eslint-enable
和eslint-disable
備註來禁用對某一段程式碼的檢查,但是稍不小心少寫了一個eslint-disable
就可能會導致後面所有檔案的檢查都被禁用,所以我並不推薦使用。
詳細使用方法可以參考文件:Disabling Rules with Inline Comments – 使用行內註釋禁用規則
總結
剛開始接觸 ESLint 時覺得太難,是因為過太過於迷信權威。比如 Airbnb 公司的 JavaScript 風格,在 GitHub 上受到了很大的好評,其實我自己也非常認可這樣的編碼風格。但每個團隊都會根據自己的的實際情況來定製不同的東西,我們並不能隨便照搬過來。所以當使用eslint-config-airbnb
這個配置進行 ESLint 檢查時,滿屏都是error
和warning
,從而覺得這東西根本沒啥卵用。
另外我也犯了「大忌」:直接使用eslint-config-airbnb
這種某個公司高度定製化的配置,而不是eslint:recommended
這樣保守的。而且是直接用來檢查整個專案好幾十個 JS 檔案,可想而知那是怎樣的畫面(本文最後版本的merge.js
檔案使用airbnb
的配置,總共 12 行的程式碼就提示了 8 個問題:✖ 8 problems (7 errors, 1 warning))。
本文的目的是讓讀者以一個比較低的姿態開始接觸 ESLint,先學會簡單地配置規則,如果要更深入地定製自己的規則,建議閱讀「相關連結」中的 ESLint 文件。