做者:Morrain
【前端科普系列】幫助閱讀者瞭解web前端,主要覆蓋web前端的基礎知識,但不深刻講解,定位爲大而全並不是細而精,適合非前端開發的同窗對前端有一個系統的認識,能更好的與前端開發協做。儘量的寫成科普類文章,對於前端開發而言,只適合剛入門的新手。javascript
本文爲第五章,主要講前端工程化中的很重要工具 ESLint,主要介紹 ESLint 的歷史、用法以及如何基於ESLint 打造保護代碼倉庫優雅的護城河。html
戰國時期強大的趙國想要一舉攻打併吞併北邊的燕國,而小國「梁城」位居兩國之間,爲戰略要衝,是必取之地。因而趙國派遣大將巷淹中,率領十萬大軍攻打僅有四千人的「梁城」,梁城王向以守城著稱的墨家求救。但梁城等到的倒是一個其貌不揚、孤身應戰的墨家俠客革離,誰料革離神機妙算,指揮梁城四千軍民抵禦十萬趙軍,功成身退。前端
(圖片來源:網絡)vue
「梁城」就比如咱們的項目倉庫,「梁城」的秩序取決於「革離」有沒有守好它!那咱們的項目倉庫呢? 你願意看到城中雜亂無章、毫無規則、亂象叢生仍是願意看到城中秩序井然、風格統1、整齊有序?如何成爲程序員裏的「革離」,守好屬於咱們的天空之城?java
先來看下它在官網上的定義:node
Find and fix problems in your JavaScript code
沒錯就一句話,發現並修復你 JavaScript 代碼中的問題!jquery
ESLint 最初是由 Nicholas C. Zakas 於 2013 年 6 月建立的開源項目。它的目標是提供一個插件化的 JavaScript 代碼檢測工具。git
那爲何須要 JavaScript 代碼檢查工具呢?仍是從 JavaScript 的語言特性提及。程序員
在 C 語言發展初期,源程序中存在不少不可移植的代碼,但卻不能被編譯器識別,所以貝爾實驗室 SteveJohnson 於 1979 年在PCC(PortableC Compiler)基礎上開發了一個靜態代碼分析的工具,用來掃描 C 源文件並對源程序中不可移植的代碼提出警告,這個工具被起名爲 lint ,也所以後續相似的檢查代碼的工具都叫 xxLint。github
咱們在《前端科普系列(1):前端簡史》中講過 JavaScript 語言的歷史,有提到 JavaScript 是在 1995年4月,由 Netscape 公司僱傭的程序員 Brendan Eich 開發的網頁腳本語言,目的是嵌入到網頁中在提交前作一些簡單的校驗。
Brendan Eich 只用了10天,就設計完成了這種語言的初版,估計做者也未曾想過 JavaScript 這門語言會發展到今天這個地步,因此在當初設計時存在很是多的不合理的地方。
因而就須要代碼校驗工具來分析使用不當的地方,JSlint 就應運而生,在 JavaScript 語言 lint 工具進化史中,有三個里程碑式的工具:JSLint、JSHint 和 ESLint。
2008年,有一本很是著名的書《JavaScript語言精粹》出版,由於封面圖是一個蝴蝶,因此俗稱"蝴蝶書"。很是薄的一本書,是適合全部入門 JavaScript 必讀而且要讀不少遍的一本書。這本書的做者叫 Douglas Crockford。
從《JavaScript語言精粹》的書名就能夠看出來,全書悉數了 JavaScript 語言的優美特性,同時在書籍的最後面,做者也絕不客氣的列舉了 JavaScript 的糟粕和雞肋的地方,從書籍中的筆風就能看出,Douglas 是個眼裏容不得瑕疵的人,因而在書籍最後也介紹了做者在 2002 年開發的 JSLint 工具,Douglas 定義了全部 JSLint 的規則,對於糟粕的語法是嚴格不讓使用的,若是你要使用JSLint,就必須接受它全部規則。
2011 年 12 月 20 日,Anton Kovalyov 發表了一篇標誌性的文章《Why I forked JSLint to JSHint》,指出了 JSLint 存在的幾個主要問題:
因而 JSHint 就誕生了,它在 JSLint 的基礎上,在社區開發者共同努力下,加入了以下特性:
諸多優點,讓 JSLint 迅速取代 JSHint 成爲一種必然。
JSLint 是從 JSHint 繼承而來,因此沿用了JSLint Top Down Operator Precedence(自頂向下的運算符優先級)技術實現源碼的解析,但前端爆發式增加帶來的巨大需求讓 JSHint 變得越發難以應對,經過暴露 AST 信息來支持第三方插件無疑是一劑良方。
AST:抽象語法樹
因而《JavaScript 高級程序設計》做者 Nicholas C. Zakas 於 2013 年 6 月建立了 ESLint,ESLint 將源代碼解析成 AST,而後檢測 AST 來判斷代碼是否符合規則,爲 ESLint 的高可擴展性奠基告終實的基礎。ESLint 保持內核足夠簡單,只有 100 行代碼,規則的實現徹底和內核分離。
可是,那個時候 ESLint 並無大火,由於須要將源代碼轉成 AST,運行速度上輸給了 JSHint ,而且 JSHint 當時已經有完善的生態(編輯器的支持)。真正讓 ESLint 大火是由於 ES6 的出現。
參考 《前端科普系列(1):前端簡史》中 ES6 的相關內容。
ES6 發佈後,由於新增了不少語法,JSHint 短時間內沒法提供支持,而 ESLint 只須要有合適的解析器就可以進行 lint 檢查。這時 Babel 爲 ESLint 提供了支持,開發了 babel-eslint,讓 ESLint 成爲最快支持 ES6 語法的 lint 工具。
瞭解了 ESLint 工具的歷史意義和發展過程,接下來看下 ESLint 到底怎麼用?
先決條件:Node.js (>=6.14), npm version 3+。
// 新建demo工程目錄,初始化 npm 項目 npm init // 安裝 eslint 推薦安裝爲項目的開發依賴 npm i -D eslint // 初始化 eslint 配置文件 由於不是安裝到全局的,因此不能直接使用 eslint --init ./node_modules/.bin/eslint --init
在初始化的過程當中,會讓你選擇一些配置,譬如 如何使用 ESLint?咱們選擇第三項,功能最多。
逐一選擇完 ESLint 的使用配置後,會在項目根目錄生成 .eslintrc.js 配置文件,同時會安裝須要的 npm 包。demo 中安裝的 npm 包有:eslint-config-standard、eslint-plugin-import、eslint-plugin-node、eslint-plugin-promise、eslint-plugin-standard
demo 中選擇以下所示:
初始化後,生成的配置內容以下所示,具體配置項的含義,後面咱們再聊。
// .eslintrc.js module.exports = { env: { es2020: true, node: true }, extends: [ 'standard' ], parserOptions: { ecmaVersion: 11, sourceType: 'module' }, rules: { } }
須要強調的是在選擇代碼風格時,我選擇了比較流行的standard規範。
接下來咱們就可使用 ESLint 來檢查和修復代碼了。首先在 demo 項目中,新建 src 目錄,並新建 index.js 文件,內容以下:
// src/index.js let a = 10; let b = 15; let sum = a + d; console.log(sum);
同時,在 package.json 中,增長 eslint 命令 eslint src/** 來檢查 src 目錄下的全部文件。
//package.json "scripts": { "eslint": "eslint src/**" }
在 demo 目錄執行 npm run eslint 結果以下:
能夠看到,檢查出來了如此多的錯誤,其中‘let 要使用 const 替換’,‘不能使用封號’等屬於 standard 規範中指定的規則,除了風格外,還檢查出了‘未定義的變量’等語法錯誤,並逐一給出提示。
若是想自動修復檢查出來的問題,怎麼辦呢?eslint 支持使用 --fix 參數。修改 package.json 中的 eslint 命令爲 eslint src/** --fix
再次執行 npm run eslint 結果以下:
打開 src/index.js 文件發現,內容已經發生了改變:
// src/index.js const a = 10 const b = 15 const sum = a + d console.log(sum)
能夠看到,ESLint 自動修復了能夠被修復的風格問題,同時對於不能被自動修復問題給出提示。
更多 eslint cli 配置參數,請參考官網cli的詳細介紹。
到這裏咱們已經‘挖好了護城河’,但是河裏並無水,敵人想要過來依然能夠暢行無阻。徹底依賴開發人員自覺手動運行 npm run eslint 來完成,那怎麼樣才能讓讓‘護城河’真正發揮做用呢?咱們先看下 ESLint 常見的配置含義,而後在 如何守住優雅的護城河詳細介紹。
剛纔在初始化以後,在項目根目錄生成了 .eslintrc.js 文件,這裏存放了全部 eslint 的配置項。
module.exports = { env: { es2020: true, node: true }, extends: [ 'standard' ], parserOptions: { ecmaVersion: 11, sourceType: 'module' }, rules: { } }
(1)環境與全局變量
demo 中的 env 配置就是爲相應的環境定義了一組預約義的全局變量。從以前的例子中咱們已經看到,ESLint 會檢測出來未定義的變量並報錯,但有一些是運行環境或者框架提供的全局變量,譬如 jQuery 提供的 $,此時有以下幾種解決方案:
/* global $ */ const dom = $('id')
{ "globals": { "$": "readonly" } }
demo 中 env 的配置,es2020:true 表示增長了 es2020 的語法特性,node:true 表示增長 node 中全部的全局變量。更多的環境能夠參考官網 指定環境 相關章節。
在 .eslintrc.js 中 rules 用來配置 ESLint 的規則,具體配置規則的方法請參考官網 如何配置規則 以及 全部規則的說明,這裏不做詳細介紹,一樣爲了方便使用,ESLint 使用 extends 配置來一次性生效一整套規則。譬如 demo 中 rules 中沒有配置任何規則,由於經過 extends 配置了符合 standard 規範的規則集合。
ESLint支持三種類型的擴展:
- eslint:' 開頭的 ESLint 官方擴展。
包括 eslint:recommended 和 eslint:all,其中 eslint:recommended是推薦的一套規則,eslint:all 是 ESLint 中的全部規則,不推薦使用,由於可能隨時被 ESLint 更改。
- 共享的擴展。
經過 npm 包提供一套共享的配置,包名前綴必須爲 eslint-config-,extends 屬性值能夠省略包名的前綴 eslint-config-。demo 中 stanard 對應的就是 package.json 中 'eslint-config-standard' 這個包提供的一套規則。
- 插件中提供的擴展。
在 demo 初始化時,咱們能夠看到 eslint-plugin-node 等插件包被安裝,這些插件包是 eslint-config-standard 的依賴,因此會被自動安裝,這些插件包也提供了一些規則可供擴展。
譬如以下代碼在 node 的模塊中寫法是錯誤的,應該寫成 module.exports,若是想要 ESLint 能檢查出這個錯誤,就須要增長 eslint-plugin-node 包中提供的規則到擴展中。
// src/index.js exports = { foo: 1 } // .eslintrc.js extends: [ 'standard', 'plugin:node/recommended' ]
因爲 eslint-plugin-node 已經被默認安裝了,因此不須要再單獨安裝,對於沒有安裝的插件包,若是想使用它提供的規則,須要手動安裝這個插件包。
上面講擴展時,已經提到了如何加載插件中的擴展配置。既然已經有了這麼多擴展可使用,爲何還須要插件呢?由於 ESLint 只能檢查標準的 JavaScript 語法,若是你使用 Vue 單文件組件, ESLint 就一籌莫展了。這個時候,相應框架就會提供配套的插件來定製特定的規則進行檢查。插件和擴展相似,也有固定的前綴 eslint-plugin-,配置時也能夠省略前綴。
咱們新加一個 Vue 的單文件組件以下,執行 npm run eslint 後發現沒有效果,並不能檢查 .vue 中的錯誤,此時就須要安裝 eslint-plugin-vue 插件。
// src/index.vue <script> let a = 10 const b = 15 const sum = a + d console.log(sum) </script>
npm i -D eslint-plugin-vue
安裝後在 .eslintrc.js 中配置插件,有兩種方式:
module.exports = { plugins: ['vue'] }
配置完成執行 npm run eslint 發現並無檢查 src/index.vue 文件,原來 plugins: ['vue'] 只是聲明想要引用的 eslint-plugin-vue 提供的規則,可是具體用哪些,怎麼用,仍是須要在 rules 中逐一配置。因此通常咱們使用第二種方式配置插件。
module.exports = { extends: ['plugin:vue/recommended'] }
經過這種方式既加載了插件又指定了使用插件提供的規則,已經能成功檢查 vue 文件中上的代碼,以下圖所示:
module.exports = { parserOptions: { ecmaVersion: 11, sourceType: 'module' } }
demo 中 parserOptions 爲解析器配置,ESLint 默認只支持 ES5 的語法,但能夠經過解析器配置來設置支持的 ES 版本,譬如 demo 中的 ecmaVersion:11 表示支持 ES11(即ES2020) 的語法,這裏須要注意的是經過解析器配置只是支持語法,對於該版本新增長的全局變量依然要經過 env 配置來完成支持,相關說明以及更多的解析器配置請參考官網 指定解析器配置。
ESLint 默認是使用ESPree做爲其解析器的,但也能夠經過 parser 字段指定一個不一樣的解析器,能夠參考官網 指定解析器。
那爲何須要指定解析器呢?由於 ESLint 默認的解析器只支持已經造成 ES 標準的語法特性,對於處於實驗階段以及非標準的(譬如 Flow、TypeScript等)須要使用 Babel 轉換的語法,就須要指定由 Babel 提供的 @babel/eslint-parser了。因而可知,正常狀況下,是不須要指定第三方的解析器的。
以 @babel/eslint-parser 爲例,當指定它做爲 ESLint 的解析器後,咱們開發的源碼首先由 @babel/eslint-parser 根據 Babel 的配置(參考《前端科普系列(4):Babel——把 ES6 送上天的通天塔》) 把源碼轉化爲 ESLint 默認支持的 AST,並保持住源碼的行列數,方便輸出錯誤的定位。光指定 @babel/eslint-parser 還不夠,解析器的做用只是負責把 ESLint 不能識別的語法特性轉化爲 ESLint 能識別的,但它自己不包括規則,還須要使用 @babel/eslint-plugin 插件來提供對應的規則,才能正常執行 ESLint 對代碼的檢測。
更多詳情,請參考 @babel/eslint-parser官方文檔。
至此,常見的配置已經介紹完了,更多配置介紹,請參考 ESLint 官網文檔 配置 ESLint。
前面也提到了到目前爲止,咱們只是‘挖好了護城河’,但是河裏並無水,敵人想要過來依然能夠暢行無阻。源碼檢測徹底依賴開發人員自覺手動運行 npm run eslint 來完成,那怎麼樣才能讓讓‘護城河’真正發揮做用呢?
首當其衝的需求就是在開發的過程當中最好就能作代碼檢測,而不是須要代碼開發完成後,運行 npm run eslint 才能看到錯誤,此時可能已經一堆錯誤了。
以 VS Code 編輯器爲例(其它編輯器應該也有相似的插件),安裝ESLint 擴展插件 。該編輯器插件會讀取當前項目中的 .eslintrc.js 的配置,並在編輯器中把不符合規則的錯誤給提示出來。
首先能夠看到目錄樹上,有問題的文件變紅,點開這個文件,對應的行上也會有錯誤提示,鼠標停留會提示錯誤的信息方便修復。但眼尖的同窗可能已經發現了,運行 npm run eslint 不光能檢測 index.js 中的錯誤,還能檢測 index.vue 中的錯誤,一共是 7 個錯誤。而編輯器只檢測了 index.js 的錯誤。
原來是編輯器的 ESLint 插件默認只能檢測 .js 的文件,須要調整編輯器 ESLint 插件的設置,讓它支持 .vue 文件的檢測。
如上圖所示:
能夠看到,index.vue 文件也已經變紅,裏面的錯誤也可以被檢測了,而且在編輯器的「問題」欄也能顯示項目全部的 7 條錯誤,和運行 npm run eslint 效果同樣了。
這樣一來,開發時就能有錯誤提示,根據提示修改就行了,但咱們以前提到運行 npm run eslint 能夠經過 --fix 參數來自動修復能夠修復的問題,譬如格式問題,let 改爲 const 等這些問題。
那在開發時,是否也能夠對於檢測出來的錯誤自動修復呢?
三種方案,能夠根據自喜愛選擇:
此時咱們發現,自動修復又是隻針對 index.js 文件生效,一樣的依然要配置 ESLint 的插件,使其支持 Vue 文件的自動修復功能。
如今咱們已經能作到了在開發時檢測出來錯誤而且方便開發人員及時修復問題,但這依賴於開發同窗自覺,若是開發同窗不自覺或者忘記了,此時提交代碼就依然會把錯誤的代碼提交到倉庫中去。此時咱們須要藉助husky來攔截 git 操做,在 git 操做以前再進行一次代碼檢測。
(圖片來源:網絡)
npm i -D husky
// package.json { "husky": { "hooks": { "pre-commit": "eslint src/** --fix" } } }
此時提交前會檢查 src 下的全部文件,因爲 demo 中源碼是有問題的,ESLint 檢查通不過,因此如今沒法提交,從而阻斷開發忘記修復 ESLint 檢查出來問題的狀況。
那對於老的項目,可能已經存在不少遺留的風格問題,致使 ESLint 檢查通不過,此時又不可能把全部問題都一一修復掉,全盤阻止提交勢必會形成影響。另外對於單次提交而言,若是每次都檢查 src 下的全部文件,也是沒有必要的。因此咱們須要使用lint-staged工具只針對當前修改的部分進行檢測。
npm i -D lint-staged
// package.json { "husky": { "hooks": { "pre-commit": "lint-staged" } }, "lint-staged": { "*.{js,vue}": [ "eslint --fix", "git add" ] } }
具體配置請參考 lint-staged 官網文檔。示例中配置表示的是,對當前改動的 .js 和 .vue 文件在提交時進行檢測和自動修復,自動修復完成後 add 到 git 暫存區。若是有沒法修復的錯誤會報錯提示。
飛機上都裝有黑匣子,當出現故障時,能夠很方便的回溯航行記錄,發現問題。咱們的代碼倉庫也同樣,每次提交都應該有記錄。但每一個開發同窗提交時輸入的信息各不同,沒有統一的格式,致使後面回溯提交記錄時眼花繚亂,效率很低。
接下來看下,如何約束提交,來守住優雅得提交日誌這道大門。
commitizen是用來格式化 git commit message 的工具,它提供了一種問詢式的方式去獲取所需的提交信息。
cz-conventional-changelog是用來規定提交時須要輸入哪些信息,譬如提交的類型是修復問題仍是功能開發,提交影響範圍等等,cz-conventional-changelog 是官網提供的規則,徹底能夠根據項目實際狀況自已開發適合的規則。
standard-version提交信息並約束後,提交的日誌信息就會比較統一,使用 standard-version 很容易自動生成提交的日誌 CHANGELOG 文件。
npm i -D commitizen cz-conventional-changelog standard-version
//package.json { "scripts": { "c": "git-cz", "version": "standard-version" }, "standard-version": { "changelogHeader": "# Changelog\n\n全部項目的變動記錄會記錄在以下文件.\n", "dryRun": true }, "config": { "commitizen": { "path": "cz-conventional-changelog" } } }
配置完成後,使用 npm run c 來提交修改,會如現以下圖所示的問詢式的交互提示,根據規則要求填寫對應的內容就行了。
經過此方式提交,提交的日誌是格式統一的,而後就是使用 npm run version 來生成 CHANGELOG 文件了。
standard-version 會自動 bump 項目的版本號,並生成兩個版本之間的提交日誌記錄文件,而後打個版本 tag 上傳到倉庫。更多關於 standard-version 的功能請參考官網文檔standard-version。
如今開發若是使用 npm run c 來提交修改,那麼一切都會很是美好,但是萬一開發忘了使用 npm run c 來提交修改,直接使用 git 命令,或者其它工具提交改動,怎麼辦?如何守好最後一道防線?
答案就是在提交時對提交信息進行校驗,若是不符合要求就不讓提交,並提示。校驗的工做由commitlint來完成,校驗的時機則由husky來指定。husky 繼承了 Git 下全部的鉤子,在觸發鉤子的時候,husky 能夠阻止不合法的 commit,push 等等。
// 安裝工具包 npm install --save-dev @commitlint/{config-conventional,cli} // 生成 commitlint 配置文件 echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js
@commitlint/cli 用來執行檢查,@commitlint/config-conventional 是檢查的標準,即提交的信息是否符合這個標準的要求,只有符合要求才容許提交。
生成配置文件,指定要使用的規範,同時增長 husky 中的 'commit-msg' 鉤子。配置完成後,再經過非 npm run c 途徑的提交都會被攔截並報錯。
// package.json { "husky": { "hooks": { "pre-commit": "lint-staged", "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" } } }
基於 ESLint ,咱們成功化身爲程序員界的‘革離’,守好了咱們的戰場,讓屬於咱們的天空之城乾淨純粹、整齊劃一,在優雅裏翱翔!