一、JSLint JSHint ESLint
參考
前端工具考 - ESLint 篇
JS Linter 進化史
C 語言誕生之初,程序員編寫的代碼風格各異,在移植時會出現一些因為不嚴謹的代碼段導致無法被編譯器執行的問題。于是在 1979 年,一款叫 lint[1] 的程序被開發出來,能夠通過掃描源代碼檢測潛在的錯誤。此后 lint 的功能不斷完善,類似的工具相繼出現。不僅可以檢測代碼中的潛在 Bug,還能做一些類型檢查。
1、JavaScript 為什么需要 lint
起初 JavaScript 被開發出來的目的只是用在 Web 頁面里實現一些簡單的交互(例如表單提交)。隨著互聯網發展,網站需要展示的內容更加豐富,交互也變得復雜,前端項目也越來越龐大;2009 年,NodeJS 的誕生使得 JavaScript 可以跑在服務端,更是讓其地位更加突出。在 2017 年 GitHub 開發語言排行榜中,JavaScript 毫無疑問排在第一位。[2]
再加上 JavaScript 本身設計上存在許多缺陷[3],代碼不嚴謹也可能就會觸發神奇的錯誤。例如 == 和 === 的混用,可能會產生類型不一致引起的 Bug,經驗不足的開發者很難察覺出異常。
2002 年,Douglas Crockford (譯注:《JavaScript 語言精粹》的作者)開發了可能是第一款針對 JavaScript 的語法檢測工具 —— JSLint[4],并于 2010 年開源。
2、JSLint 和 JSHint
JSLint 面市后,確實幫助許多 JavaScript 開發者節省了不少排查代碼錯誤的時間。但是 JSLint 的問題也很明顯——幾乎不可配置,所有的代碼風格和規則都是內置好的;再加上 Douglas Crockford 老爺子推崇道系「愛用不用」的優良傳統,不會向開發者妥協開放配置或者修改他覺得是對的規則。于是 Anton Kovalyov 吐槽:「JSLint 是讓你的代碼風格更像 Douglas Crockford 的而已」[5],并且在 2011 年 Fork 原項目開發了 JSHint。
JSHint 的特點就是可配置,同時文檔也相對完善,而且對開發者友好。很快大家就從 JSLint 轉向了 JSHint。
3、ESLint 的誕生
起初幾年,JSHint 一直是前端代碼檢測工具的首選,包括 Nicholas C. Zakas (《JavaScript高級程序設計》作者)也是 JSHint 的用戶。但在 2013 年,Zakas 大佬發現 JSHint 已經無法滿足自己定制化規則的需求,而且和 Anton 討論后達成共識這根本在不可能在 JSHint 上實現。同時 Zakas 還設想發明一個基于 AST 的 lint,可以動態執行額外的規則,同時可以很方便的擴展規則。[6]
2013 年的 6 月份,Zakas 發布了全新的 lint 工具——ESLint[7]。
幾乎同一時間,另一款和 ESLint 實現機制類似的代碼風格檢測工具——JSCS[8]——也誕生了。
4、可擴展性的勝利
ESLint 的出現并沒有撼動 JSHint 的霸主地位。由于前者是利用 AST 處理規則,用 Esprima 解析代碼,執行速度要比只需要一步搞定的 JSHint 慢很多;其次當時已經有許多編輯器對 JSHint 支持完善,生態足夠強大。真正讓 ESLint 逆襲的是 ECMAScript 6 的出現。
2015 年 6 月,ES2015 規范正式發布。但是發布后,市面上瀏覽器對最新標準的支持情況極其有限。如果想要提前體驗最新標準的語法,就得靠 Babel 之類的工具將代碼編譯成 ES5 甚至更低的版本,同時一些實驗性的特性也能靠 Babel 轉換。這時 JSHint 就略尷尬,ES2015 變化很大,短期內無法完全支持。ESLint 可擴展的優勢一下就體現出來了,不僅可以擴展規則,甚至連解析器也能替換。Babel 團隊就為 ESLint 開發了 babel-eslint[9] 替換默認解析器,讓 ESLint 率先支持 ES2015 語法。
也是在 2015 年,React 的應用越來越廣泛,誕生不久的 JSX 也愈加流行。ESLint 本身也不支持 JSX 語法。還是因為可擴展性,eslint-plugin-react[10] 的出現讓 ESLint 也能支持當時 React 特有的規則。
2016 年,JSCS 開發團隊認為 ESLint 和 JSCS 實現原理太過相似,而且需要解決的問題也都一致,最終選擇合并到 ESLint,并停止 JSCS 的維護。[11]
至此,ESLint 完美躺贏,替代 JSHint 成為前端主流工具。[12]
二、ESLint入門
參考
利用 ESLint 檢查代碼質量
ESLint中文網
1.安裝
npm install -g eslint
2.準備一個js文件來測試一下
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}));
3.執行eslint merge.js
這時是沒有輸出結果的,原因是沒有指定配置文件。現在添加一個.eslintrc.js,使用內置配置
module.exports = {
extends: 'eslint:recommended',
};
重新執行eslint merge.js可以看到輸出了 2 個錯誤:
/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改為這樣:
module.exports = {
extends: 'eslint:recommended',
rules: {
'no-console': 'off',
},
};
說明:配置規則寫在rules對象里面,key表示規則名稱,value表示規則的配置。每條規則有 3 個等級:off、warn和error。off表示禁用這條規則,warn表示僅給出警告,并不會導致檢查不通過,而error則會導致檢查不通過??梢詤⒖?a target="_blank" rel="nofollow">規則說明文檔
重新執行檢查還是提示no-undef:
/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對象,而瀏覽器環境下沒有。
所以在配置文件中我們還需要指定程序的目標環境:
module.exports = {
extends: 'eslint:recommended',
env: {
node: true,
},
rules: {
'no-console': 'off',
},
};
再重新執行檢查時,已經沒有任何提示輸出了,說明merge.js已經完全通過了檢查。
4.使用共享的配置文件
上文我們以eslint:recommended為基礎配置,然后在此之上修改no-console這條規則。而在大多數時候,我們可能會根據自己個人或團隊的習慣,定制更多的規則,比如限定縮進是 2 個空格和使用單引號的字符串等。而如果每一個項目都要這樣寫到.eslintrc.js文件上,管理起來會比較麻煩。
我們可以將定義好規則的.eslintrc.js文件存儲到一個公共的位置,比如public-eslintrc.js:
module.exports = {
extends: 'eslint:recommended',
env: {
node: true,
},
rules: {
'no-console': 'off',
'indent': [ 'error', 2 ],
'quotes': [ 'error', 'single' ],
},
};
然后將原來的.eslintrc.js文件改成這樣:
module.exports = {
extends: './public-eslintrc.js',
};
為了驗證這樣的修改是否生效,將merge.js中的var ret = {};這一行前面多加一個空格,再執行 ESLint 檢查:
/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已經生效了。
5.代碼格式化
在規則說明文檔中有如下說明
為了讓你對規則有個更好的理解,ESLint 對其進行了分門別類。
所有的規則默認都是禁用的。在配置文件中,使用 "extends": "eslint:recommended" 來啟用推薦的規則,報告一些常見的問題,在下文中這些推薦的規則都帶有一個對勾標記。
命令行的 --fix 選項用來自動修復規則所報告的問題(目前,大部分是對空白的修復),在下文中會有一個扳手的圖標。
使用上述配置,將merge.js中的var ret = {};這一行前面多加一個空格。然后執行eslint merge.js --fix
,可以發現merge.js文件被修改了,新添加的空格被去除了。我們可以利用這個特性來自動格式化項目代碼,這樣就可以保證代碼書寫風格的統一。
5.例外情況
盡管我們在編碼時懷著嚴格遵守規則的美好愿景,而凡事總有例外。定立 ESLint 規則的初衷是為了避免自己犯錯,但是我們也要避免不顧實際情況而將其搞得太過于形式化,本末倒置。
ESLint 提供了多種臨時禁用規則的方式,比如我們可以通過一條eslint-disable-next-line
備注來使得下一行可以跳過檢查:
// 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 - 使用行內注釋禁用規則
6.總結
剛開始接觸 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 規則詳解(一)
ESLint 規則詳解(二)
三、其它問題
1.eslint --init
在第二部分入門示例時,是手動創建的.eslintrc.js。其實也可以使用eslint --init來創建
可以選個模板,也可以自己回答問題
PS E:\eslint> eslint --init
? How would you like to configure ESLint? Use a popular style guide
? Which style guide do you want to follow? (Use arrow keys)
> Airbnb (https://github.com/airbnb/javascript)
Standard (https://github.com/standard/standard)
Google (https://github.com/google/eslint-config-google)
比如選airbnb
module.exports = {
"extends": "airbnb"
};
也可以自己回答問題來創建
> eslint --init
? How would you like to configure ESLint? Answer questions about your style
// 是否校驗 Es6 語法
? Are you using ECMAScript 6 features? Yes
// 是否校驗 Es6 模塊語法
? Are you using ES6 modules? Yes
// 代碼運行環境,Browser 指瀏覽器
? Where will your code run? Browser
// 是否校驗 CommonJs 語法
? Do you use CommonJS? Yes
// 是否校驗 JSX 語法
? Do you use JSX? Yes
// 是否校驗 React 語法
? Do you use React? Yes
// 首行空白選擇 Tab 鍵還是 Space
? What style of indentation do you use? Tabs
// 字符串使用單引號 'string' 還是雙引號 "string"
? What quotes do you use for strings? Double
// 操作系統
? What line endings do you use? Windows
// 每行代碼結尾是否校驗加分號 ;
? Do you require semicolons? Yes
// 以 .js 格式生成配置文件
? What format do you want your config file to be in? JavaScript
// 因為要校驗 Reac 語法,所以這里需要下載一個 React 語法規則的包
Installing eslint-plugin-react@latest
2."extends": "eslint:recommended"
值為 "eslint:recommended" 的 extends 屬性啟用一系列核心規則,這些規則是經過前人驗證的最佳實踐(所謂最佳實踐,就是大家伙都覺得應該遵循的編碼規范),想知道最佳實踐具體有哪些編碼規范,可以在 eslint規則表 中查看被標記為 √ 的規則項。
如果覺得官方提供的默認規則不好用,可以自定義規則配置文件,然后發布成 Npm 包,eslint-config-airbnb 就是別人自定義的編碼規范,使用 npm 安裝后,在我們自己的 .eslintrc.js 中進行配置 "extends": "airbnb",eslint-config 這個前綴可以省略不寫,這樣我們就使用了 eslint-config-airbnb 中的規則,而不是官方的規則 "extends": "eslint:recommended" 了。關于如何創建自定義規則配置并共享可以參考: 如何自定義規則配置
關于 "airbnb" 編碼規范說兩句,在接觸到大多數開源項目中,大多數的作者都會使用 "airbnb" 編碼規范而不是 官方的"extends": "eslint:recommended" 編碼規范。如果我們覺得 eslint-config-airbnb 規則配置中個別規則并不符合當前項目的要求,可以直接在 .eslintrc.js 配置 rules 屬性,優先級高于共享規則 airbnb。
3.rules
ESLint 附帶有大量的規則,修改規則應遵循如下要求:
- "off" 或 0 - 關閉規則
- "warn" 或 1 - 開啟規則,使用警告級別的錯誤:warn (不會導致程序退出)
- "error" 或 2 - 開啟規則,使用錯誤級別的錯誤:error (當被觸發的時候,程序會退出)
有的規則沒有屬性,只需控制是開啟還是關閉,像這樣:"eqeqeq": "off",有的規則有自己的屬性,使用起來像這樣:"quotes": ["error", "double"]
4.【文件配置】
ESLint 支持幾種格式的配置文件
- JavaScript - 使用 .eslintrc.js 然后輸出一個配置對象。
- YAML - 使用 .eslintrc.yaml 或 .eslintrc.yml 去定義配置的結構。
- JSON - 使用 .eslintrc.json 去定義配置的結構,ESLint 的 JSON 文件允許 JavaScript 風格的注釋。
- (棄用) - 使用 .eslintrc,可以使 JSON 也可以是 YAML。
- package.json - 在 package.json 里創建一個 eslintConfig屬性,在那里定義配置。
如果同一個目錄下有多個配置文件,ESLint 只會使用一個。優先級順序如下
1 .eslintrc.js
2 .eslintrc.yaml
3 .eslintrc.yml
4 .eslintrc.json
5 .eslintrc
6 package.json
除了配置一個獨立的 .eslintrc.* 文件,也可以直接在 package.json 文件里的 eslintConfig 字段指定配置,ESLint 將自動在要檢測的文件目錄里尋找它們,緊接著是父級目錄,一直到文件系統的根目錄
{
"name": "mypackage",
"version": "0.0.1",
"eslintConfig": {
"env": {
"browser": true,
"node": true
}
}
}
四、gulp-eslint
npm install gulp-eslint --save
const eslint = require('gulp-eslint');
task('default', () => {
return src(['scripts/*.js'])
// eslint() attaches the lint output to the "eslint" property
// of the file object so it can be used by other modules.
.pipe(eslint())
// eslint.format() outputs the lint results to the console.
// Alternatively use eslint.formatEach() (see Docs).
.pipe(eslint.format())
// To have the process exit with an error code (1) on
// lint error, return the stream and pipe to failAfterError last.
.pipe(eslint.failAfterError());
});
比如,針對上面的例子,可以寫一個gulpfile.js
var gulp = require('gulp'),
eslint = require('gulp-eslint');
gulp.task('lint', function () {
return gulp.src(['merge.js'])
.pipe(eslint())
.pipe(eslint.format())
});
gulp.task('default', ['lint'], function () {
console.log("suc");
});
執行結果
PS E:\eslint> gulp default
[16:10:52] Using gulpfile E:\eslint\gulpfile.js
[16:10:52] Starting 'lint'...
[16:10:53]
E:\eslint\merge.js
4:3 error Unexpected console statement no-console
12:1 error Unexpected console statement no-console
? 2 problems (0 errors, 2 warnings)
[16:10:53] Finished 'lint' after 495 ms
[16:10:53] Starting 'default'...
suc
[16:10:53] Finished 'default' after
這里明明報錯了,還是執行到console.log("suc")了。原來是gulpfile.js還要再加一行
gulp.task('lint', function () {
return gulp.src(['merge.js'])
.pipe(eslint())
.pipe(eslint.format())
.pipe(eslint.failAfterError());
});
這下子就過不去了
? 2 problems (2 errors, 0 warnings)
[16:21:13] 'lint' errored after 503 ms
[16:21:13] ESLintError in plugin "gulp-eslint"
Message:
Failed with 2 errors
這里其實可以通過一個bool變量來控制:.pipe(exit ? eslint.failAfterError() : eslint.result(function () {}));
五、在vscode中使用eslint
1.首先,打開 VSCode 擴展面板并搜索 ESLint 擴展,然后點擊安裝
安裝完成之后,點擊重新加載,或者重啟vscode
2.依次點擊vscode中的 文件 > 首選項 > 設置 打開 VSCode 配置文件
可以看到已經啟用eslint了
3.打開merge.js可以看到報錯,和上面命令行中的相同:
4.注意,我們已經安裝過全局的eslint并且生成eslintrc.js了。如果沒有這些操作,也是不行的。參考提升效率之搭建eslint+vscode開發環境