【W(wǎng)eb技術(shù)】1179- 從0到1落地前端代碼檢測(cè)工具

相關(guān)背景:
中大型公司中前端項(xiàng)目往往不止一個(gè),前端開發(fā)人員多加上前端項(xiàng)目眾多,為了維持一定的項(xiàng)目團(tuán)隊(duì)風(fēng)格往往十分艱難。這篇文章主要是在公司中針對(duì)組內(nèi)現(xiàn)狀問(wèn)題進(jìn)行問(wèn)題收集、調(diào)研、開發(fā)、落地的總結(jié)。
1. 前端項(xiàng)目現(xiàn)狀
前端組內(nèi)項(xiàng)目眾多,但是在代碼質(zhì)量檢測(cè)方面一直不統(tǒng)一。比如像xx系統(tǒng)和移動(dòng)端項(xiàng)目都有簡(jiǎn)單 lintrc 配置、但都是重復(fù)復(fù)制的配置;像 node 方向的項(xiàng)目幾乎只是簡(jiǎn)單配置了幾個(gè)規(guī)則;而內(nèi)部yy系統(tǒng)的項(xiàng)目全部都沒有配置代碼檢測(cè)、導(dǎo)致 CR 的時(shí)候需要重復(fù)性提出相關(guān)代碼風(fēng)格問(wèn)題;除此之外針對(duì)新開的項(xiàng)目大部分是手動(dòng)新建文件并把配置復(fù)制一份過(guò)來(lái),重復(fù)冗余且效率低下。
2. 相關(guān)問(wèn)題、如何解決
鑒于以上現(xiàn)狀,各個(gè)項(xiàng)目相關(guān)檢測(cè)配置不統(tǒng)一也不便于管理。結(jié)合現(xiàn)有的前端代碼檢測(cè)工具及組內(nèi)項(xiàng)目現(xiàn)狀,考慮配置一份通用性規(guī)則,里面集合了不同規(guī)則集,不同項(xiàng)目引入不同的規(guī)則集擴(kuò)展。我們只需要維護(hù)這個(gè)包項(xiàng)目及規(guī)則本身即可,每個(gè)項(xiàng)目 install 下載即可使用。這樣的做法可以帶來(lái)多種好處:
統(tǒng)一團(tuán)隊(duì)里面成員的編碼風(fēng)格,使代碼風(fēng)格統(tǒng)一 提高代碼的可讀性,像一些風(fēng)格上的問(wèn)題如換行等等會(huì)讓代碼更難讀懂 提高項(xiàng)目的代碼質(zhì)量、提高開發(fā)效率,Lint很容易發(fā)現(xiàn)低級(jí)的、顯而易見的錯(cuò)誤,提前發(fā)現(xiàn)可以減少由于語(yǔ)法問(wèn)題帶來(lái)的線上問(wèn)題且節(jié)省排查定位問(wèn)題消耗的時(shí)間
一. lint相關(guān)工具介紹及意義
1. JSLint
JSLint 的核心是 Top Down Operator Precedence(自頂向下的運(yùn)算符優(yōu)先級(jí))技術(shù)實(shí)現(xiàn)的 Pratt 解析器。在不斷發(fā)展過(guò)程中,由于 JSLint 中的所有規(guī)則都由開發(fā)者定義個(gè)人風(fēng)格過(guò)于明顯,若要使用就要接受其定義的所有規(guī)則,這就導(dǎo)致新的 Lint/Linter 工具誕生。
2. JSHint
JSHint 基于 JSLint、其核心實(shí)現(xiàn)仍然基于 Pratt 解析器的基礎(chǔ)上,但解決了很多 JSLint 暴露出來(lái)的缺點(diǎn),比較明顯的就是可添加可配置的規(guī)則,用參考文章中的一句話描述:“ JSHint 的出現(xiàn)幾乎是必然的,前端開發(fā)者日益增長(zhǎng)的代碼質(zhì)量訴求和落后偏執(zhí)的工具之間出現(xiàn)了不可調(diào)和的矛盾”。
3. JSCS 和 ESLint
ESLint 和 JSCS 幾乎在同一時(shí)段發(fā)布,且兩者都是利用 AST 處理規(guī)則,用 Esprima 解析代碼,因?yàn)樾枰獙⒃创a轉(zhuǎn)成 AST,執(zhí)行速度上其實(shí)是輸給了 JSHint;ESLint 真正能夠火的原因其實(shí)是 ES6 的出現(xiàn),ES6 新增許多新語(yǔ)法,但是 JSHint 本身的一些設(shè)計(jì)原因無(wú)法兼容新語(yǔ)法,而 ESlint 由于其高擴(kuò)展性,既能擴(kuò)展自定義規(guī)則又能通過(guò)babel-eslint 更換掉默認(rèn)解析器,對(duì)新框架的語(yǔ)法也能很好的兼容,所以最終 ESlint 被廣泛使用;又由于 JSCS 和 ESLint 實(shí)現(xiàn)原理大同小異,再維護(hù)也沒有太大的意義,最終 JSCS 與 ESLint 合并。

4. Prettier
Prettier 也是一個(gè)代碼樣式檢查優(yōu)化的工具,但其可配置的規(guī)則很少,除了少部分可配置的規(guī)則外,其他都不可更改以此來(lái)達(dá)到強(qiáng)制性統(tǒng)一代碼風(fēng)格的目的。Prettier 與 ESlint 不同,如下圖所示,它不止適用于 JS 代碼,還適用于其他前端常用語(yǔ)言,不過(guò)它的規(guī)則只針對(duì)代碼樣式(Formatting)?而不針對(duì)于代碼質(zhì)量(Code-quality)?。Prettier可以單獨(dú)在項(xiàng)目中使用,也可以與 ESlint 一起搭配使用。

二. 項(xiàng)目配置ESLint
1. 安裝和使用 ESLint
1.1 文件中對(duì)應(yīng)代碼開啟/關(guān)閉對(duì)應(yīng)規(guī)則
執(zhí)行命令 npm install eslint \--save-dev 安裝之后,可以直接在項(xiàng)目源碼中針對(duì)對(duì)應(yīng)的代碼塊開啟規(guī)則:
/*?eslint?console:?"error"?*/?
console.log('66666')
復(fù)制代碼
也可以在代碼塊中直接禁用相關(guān)規(guī)則:
1.?被包裹的代碼塊中取消eslint檢查,也可以帶上對(duì)應(yīng)的規(guī)則表示僅禁用對(duì)應(yīng)規(guī)則
/*?eslint-disable?*/
alert(‘foo’);?
/*?eslint-enable?*/
2. 針對(duì)某一行禁用eslint檢查:
alert(‘foo’);?//?eslint-disable-line
//?eslint-disable-next-line?no-alert?
alert(‘foo’);
復(fù)制代碼
1.2 配置文件中開啟/關(guān)閉規(guī)則
執(zhí)行 npm install eslint \--D 全局安裝ESlint之后,使用命令 eslint \--init 初始化 eslint 配置文件,執(zhí)行過(guò)程中會(huì)詢問(wèn)一些配置化的問(wèn)題,可以根據(jù)自己的需求去進(jìn)行選擇:
選擇結(jié)束之后最終會(huì)生成一份初始化 .eslintrc 文件如下所示:
module.exports?=?{
??env:?{
????browser:?true,
????es6:?true,
??},
??extends:?[
????'plugin:vue/essential',
????'airbnb-base',
??],
??globals:?{
????Atomics:?'readonly',
????SharedArrayBuffer:?'readonly',
??},
??parserOptions:?{
????ecmaVersion:?2018,
????sourceType:?'module',
??},
??plugins:?[
????'vue',
??],
??rules:?{},
};
復(fù)制代碼
2. ESLint 文件配置參數(shù)介紹
Tips:建議還是直接看英文翻譯,有的查閱中文意思根本就翻譯錯(cuò)了
ESLint配置參數(shù)文檔:eslint.org/docs/user-g…[1]
eslintrc文件中的配置參數(shù)非常多,大部分參數(shù)官方文檔都有相關(guān)的說(shuō)明,這里只針對(duì)此次使用中一些比較重要的參數(shù)進(jìn)行羅列:
root:將其設(shè)置為 true 之后,ESLint 就不會(huì)再向上去查找文件檢測(cè)(后續(xù)兩份rc有貼測(cè)試結(jié)論) impliedStrict:ecmaFeatures 中的 impliedStrict 設(shè)置為 true 之后就可以在 整個(gè)項(xiàng)目中開啟嚴(yán)格模式env:指定啟用的環(huán)境,通常開啟的是 browser 環(huán)境和 node 環(huán)境 plugins:引入的相關(guān)插件名,可以直接省略 eslint-plugin- 前綴 extends:extends 中可以直接引入可共享配置包,其中也可以直接省略包名前綴 eslint-config-,其中還可以直接引入基本配置文件的絕對(duì)路徑或相對(duì)路徑 rules:參數(shù) rules 中可以設(shè)置很多 eslint 內(nèi)置的規(guī)則,官方文檔中規(guī)則前面有個(gè)修補(bǔ)圖標(biāo)
的表示在檢測(cè)的時(shí)候可以自動(dòng)修復(fù)規(guī)則,反之會(huì)報(bào) error 且需要自己手動(dòng)修復(fù)。rules 屬性中的規(guī)則都接收一個(gè)參數(shù),參數(shù)的值如下:
"off"?或?0:關(guān)閉規(guī)則
"warn"?或 1:開啟規(guī)則,黃色警告?(不會(huì)導(dǎo)致程序退出)
"error"?或 2:開啟規(guī)則,紅色錯(cuò)誤?(程序執(zhí)行結(jié)果為0表示檢測(cè)通過(guò);執(zhí)行結(jié)果為1表示有錯(cuò)誤待修復(fù))
復(fù)制代碼
有的規(guī)則開啟后還可以配置額外的參數(shù)項(xiàng),可以根據(jù)不同的情況開啟規(guī)則后再添加額外的參數(shù)項(xiàng)。
3. 命令行參數(shù)介紹
ESLint命令行參數(shù)文檔:eslint.cn/docs/user-g…[2]
eslint 相關(guān)的命令行設(shè)置也可以直接參考官方文檔,一般是在 package.json 文件中對(duì) eslint 進(jìn)行特定的命令行設(shè)置,用來(lái)達(dá)到執(zhí)行對(duì)應(yīng)命令呈現(xiàn)不同效果的目的。命令行中主要用到的是 global 語(yǔ)法,這里也只針對(duì)此次使用過(guò)程中個(gè)別參數(shù)進(jìn)行介紹:
--ignore-path: 后面跟上對(duì)應(yīng)的文件名,當(dāng) eslint 檢測(cè)文件的時(shí)候會(huì)忽略檢測(cè)此文件 --fix: 配置這個(gè)參數(shù)之后,執(zhí)行命令時(shí)針對(duì)可以自動(dòng)修復(fù)的問(wèn)題就會(huì)直接被修復(fù),如果不希望執(zhí)行時(shí)進(jìn)行自動(dòng)修復(fù)可不配置 --rulesdir: 引用本地開發(fā)規(guī)則路徑
三. 自定義規(guī)則集
當(dāng)我們知道了怎么在項(xiàng)目中簡(jiǎn)單配置相關(guān)的檢測(cè)規(guī)則之后,就會(huì)碰到開頭所說(shuō)的問(wèn)題,重復(fù)性的配置、重復(fù)的復(fù)制粘貼這樣會(huì)導(dǎo)致效率低下且代碼冗余不便于統(tǒng)一管理。eslint 的高擴(kuò)展性可以讓我們很方便的做很多事:比如把通用性的配置整合成一個(gè)可共享配置包,這樣我們只需要把通用性的一些規(guī)則分類整理并發(fā)布成為一個(gè) npm 包,其他項(xiàng)目只需 install 下載后配置好規(guī)則即可生效。
1. 共享配置包 eslint-config-zly 整體結(jié)構(gòu)
共享配置包主要是將一些配置導(dǎo)出,使用方直接引入即可,在此參考了一些比較出名的 eslint-config 類似 airbnb 和 alloy。
1.1 參考 airbnb
airbnb源碼:自行搜索 github 尋找源碼
最開始看的是目前前端社區(qū)中使用最火的 airbnb,airbnb 主要是在 package 文件中分別建了兩份配置,eslint-config-airbnb 中主要是 react 的一些規(guī)則配置,eslint-config-airbnb-base 主要是基本規(guī)則配置分類。

由于 eslint 對(duì)于 config 配置包沒有提供對(duì)應(yīng)的模版,所以在參照了 airbnb 目錄結(jié)構(gòu)之后開始手動(dòng)新建不同分類的文件,將一些內(nèi)置規(guī)則按照 eslint 中 rules 模塊介紹中不同功能塊進(jìn)行劃分,然后在 .eslintrc.js 文件中通過(guò) extends 相對(duì)路徑引入。但是由于 airbnb 他們項(xiàng)目本身使用的技術(shù)棧比較簡(jiǎn)單所以按照這樣發(fā)布兩個(gè)包也沒什么問(wèn)題,如果多項(xiàng)目多技術(shù)棧按照這樣去發(fā)包的話則需要管理發(fā)布多個(gè)包。

1.2 參考 alloyTeam
alloy源碼:github.com/AlloyTeam/e…[3]
由于 airbnb 其分開發(fā)布的結(jié)構(gòu)方式用在我們的設(shè)計(jì)上不是很適合,于是看了一下 alloy 部分的源碼。alloy 中將一些樣式相關(guān)的規(guī)則給了 Prettier 管理,內(nèi)部主要是 React/Vue/TypeScript 插件中的一些規(guī)則分別列在跟目錄的 base.js、vue.js、react.js、typescript.js 文件中,通過(guò)文檔、示例、測(cè)試、網(wǎng)站等方便大家參考 alloy 的規(guī)則,并在此基礎(chǔ)上做出自己的個(gè)性化,且其內(nèi)部利用 travis-ci 高度的自動(dòng)化,將一切能自動(dòng)化管理的過(guò)程都交給腳本處理,其通過(guò)自動(dòng)化的腳本將成很多個(gè) ESLint 配置文件分而治之,每個(gè)規(guī)則在一個(gè)單獨(dú)的目錄下管理。參考了 alloy 的目錄結(jié)構(gòu)之后,我們將 eslint-config-zly 項(xiàng)目結(jié)構(gòu)進(jìn)行了調(diào)整,主要是 base 規(guī)則和 vue 規(guī)則放在同一集目錄下:

2. 發(fā)包設(shè)計(jì)
因?yàn)樽罱K是要發(fā)布到npm上,所以結(jié)合之前的一些參考項(xiàng)目的源碼結(jié)構(gòu)、對(duì)于如何發(fā)包管理進(jìn)行了一部分的討論,主要是針對(duì)于最終的包怎么發(fā)布以及項(xiàng)目使用時(shí)怎么引入,其中主要發(fā)包設(shè)計(jì)有如下幾種:
2.1 發(fā)布多個(gè)包,組合使用
主要也是不同規(guī)則單獨(dú)發(fā)一個(gè)包,需要組合使用的放在 extends 中一起使用即可。這個(gè)主要是發(fā)包管理過(guò)多,組合使用也不夠直接方便,故棄用。
extends:?[airbnb/base,?airbnb/base]
復(fù)制代碼
2.2 發(fā)布多個(gè)包,無(wú)組合使用:
類似于 airbnb,不同功能的規(guī)則單獨(dú)作為一個(gè) npm 包發(fā)布,需要的時(shí)候單獨(dú)下載引入。這個(gè)可以滿足讓業(yè)務(wù)方想用哪種類型的規(guī)則就引入哪個(gè)包的期望,但是這個(gè)包管理發(fā)布的話需要一個(gè)個(gè)維護(hù),比較麻煩、也不符合我們的預(yù)期。
參考:https://github.com/airbnb/javascript/blob/master/packages/eslint-config-airbnb-base/README.md
extends:?airbnb/base
extends:?airbnb/vue
一個(gè)項(xiàng)目中根據(jù)不同分類發(fā)布不同的包,如?airbnb?中項(xiàng)目?base?集模塊單獨(dú)發(fā)包?eslint-config-airbnb-base,
需要?base?的單獨(dú)引入這個(gè)包即可
復(fù)制代碼
2.3 只發(fā)布一個(gè)包,組合使用
類似于上述 alloy,將通過(guò)不同文件夾將功能劃分清楚,例如 base 只裝基礎(chǔ)規(guī)則、vue 只裝 vue 規(guī)則,如果兩者規(guī)則都需要的就組合使用,但是由于我們希望使用的業(yè)務(wù)方可以直接使用一個(gè)集中的規(guī)則集而不需要手動(dòng)組合,所以這個(gè)方案有點(diǎn)不符合我們的預(yù)期。
參考:https://github.com/AlloyTeam/eslint-config-alloy/blob/HEAD/README.zh-CN.md
extends:?alloy/base,
extends:?alloy/vue,
extends:?alloy/typescript
需要組合使用的話就?extends:?[alloy/base,?alloy/vue]
復(fù)制代碼
2.4 只發(fā)一個(gè)包,無(wú)組合使用
綜合考慮前面兩種發(fā)包設(shè)計(jì)的優(yōu)劣,再結(jié)合我們的需求,于是選擇第三種方案,所有規(guī)則都在一個(gè)包里面管理,base 集作為基礎(chǔ)規(guī)則集會(huì)在每個(gè)其他規(guī)則集中引入,其他類似 vue 集會(huì)在引入 base 集后疊加 vue 的規(guī)則,業(yè)務(wù)方需要使用的話直接引入 vue 集時(shí)基礎(chǔ)規(guī)則和 vue 規(guī)則都會(huì)生效。
extends:?zly/vue
extends:?zly/react
extends:?zly/tina
extends:?zly/node
vue?的項(xiàng)目就單獨(dú)引入?zly?包的?vue?集,?node的項(xiàng)目就單獨(dú)引入?zly?包的?node?集
復(fù)制代碼
3. 規(guī)則集
定好大概的項(xiàng)目架構(gòu)之后相當(dāng)于架子搭建好,下一步就是內(nèi)容填充了。主要是針對(duì)于不同的框架語(yǔ)法要用不同的規(guī)則集進(jìn)行檢測(cè),結(jié)合公司現(xiàn)有項(xiàng)目制定了一下規(guī)則集:
-?'best-practices.js'
-?'custom.js'
-?'errors.js'
-?'es6.js'
-?'import.js'
-?'tyle.js'
-?'variables.js'
復(fù)制代碼
最后在 _base 文件下面 index.js 將上述文件與 airbnb-base 包一起作為 extends 引入:
Tips:在整理 node 集的時(shí)候發(fā)現(xiàn) node 項(xiàng)目需要在嚴(yán)格模式下開發(fā),ecmaFeatures 中 impliedStrict 設(shè)置為 true 即項(xiàng)目都處在嚴(yán)格模式下,無(wú)需寫上 'use strict',剛開始只想把這個(gè)配置寫在 node 集中,最后商討后還是放在 base 集中,確定所有用到的項(xiàng)目都嚴(yán)格模式下。
module.exports?=?{
??parserOptions:?{
????ecmaFeatures:?{
??????impliedStrict:?true,
????}
??},
??extends:?[
????'airbnb-base',
????join(__dirname,?'./rules/best-practices'),
????join(__dirname,?'./rules/errors'),
????join(__dirname,?'./rules/es6'),
????join(__dirname,?'./rules/import'),
????join(__dirname,?'./rules/style'),
????join(__dirname,?'./rules/variables'),
????join(__dirname,?'./rules/custom'),
??],
}
復(fù)制代碼
3.2 vue集
vue 規(guī)則集中主要是引入了 eslint-plugin-vue 中 vue3-recommended,_base集,再針對(duì)特定的規(guī)則進(jìn)行配置:
module.exports?=?{
??env:?{
????browser:?true,
??},
??extends:?[
????'plugin:vue/vue3-recommended',
????join(__dirname,?'../_base/index'),
????join(__dirname,?'./rules/vue'),
??],
??parserOptions:?{
????parser:?'babel-eslint',
??},
??plugins:?['vue'],
}
復(fù)制代碼
3.3 tina集
tina 規(guī)則主要是針對(duì)小程序項(xiàng)目,用的也是 vue 語(yǔ)法,這里針對(duì)于是引入 vue 規(guī)則集還是直接引入 eslint-plugin-vue 做了一部分討論,最終還是決定直接引入 vue 集針對(duì)不必要的規(guī)則手動(dòng)在 tina 集中關(guān)閉:
module.exports?=?{
??globals:?{
????App:?true,
????Page:?true,
????wx:?true,
????getApp:?true,
????getPage:?true,
????getCurrentPages:?true,
??},
??extends:?[
????join(__dirname,?'../vue/index'),
????join(__dirname,?'./rules/tina'),
??],
??parserOptions:?{
????parser:?'babel-eslint',
??},
}
復(fù)制代碼
3.4 node集
node 集制定過(guò)程中,看了 eslint-config-node、eslint-config-egg、eslint-plugin-eggache 和 eslint-plugin-node,經(jīng)過(guò)對(duì)比發(fā)現(xiàn) eslint-plugin-node 插件中制定的node規(guī)則最多最純粹,且社區(qū)中 eslint-plugin-node 使用較多提供的解決方案也比較多,所以經(jīng)多方調(diào)研最后決定 eslint-plugin-node。node 集中用了 eslint-plugin-node 中 recommended,再引入 _base 集,最后針對(duì)特定的 node 規(guī)則在 rules/node/index.js 中做特殊處理。
module.exports?=?{
??env:?{
????node:?true,
??},
??extends:?[
????'plugin:node/recommended',
????join(__dirname,?'../_base/index'),
????join(__dirname,?'./rules/node')
??],
??plugins:?['node'],
}
復(fù)制代碼
針對(duì)上述除 _base 以外的規(guī)則集中,其他 extends 的順序都是先引入第三方插件規(guī)則,再引入 _base 規(guī)則,然后最后引入自己針對(duì)性規(guī)則修改過(guò)的文件。
四. 自定義插件
ELSint自定義規(guī)則及源碼解析juejin.cn/editor/draf…[4]
定好項(xiàng)目架構(gòu)、發(fā)包設(shè)計(jì)、規(guī)則集之后,想要整合浩哥的自定義規(guī)則 "_.get禁止第三參數(shù) " 的時(shí)候,發(fā)現(xiàn) eslint 自定義配置包中不能集成自定義規(guī)則,只有自定義插件才可以集成自定義規(guī)則,于是整體項(xiàng)目從 config 過(guò)渡到 plugin。
1. 使用模版初始化配置
Yeoman generator-eslint:yeoman.io/authoring/[5]
ESLint 官方為了方便開發(fā)者開發(fā)插件有提供 Yeoman 模版 generator-eslint,基于官方提供的模板可以快速創(chuàng)建 ESLint Plugin 項(xiàng)目, 按照如下步驟進(jìn)行初始化:
npm?i?-g?yo
npm?i?-g?generator-eslint
//?創(chuàng)建一個(gè)plugin
yo?eslint:plugin
//?創(chuàng)建一個(gè)規(guī)則
yo?eslint:rule
復(fù)制代碼
初始化的項(xiàng)目目錄結(jié)構(gòu)如下:
rules 文件夾存放的是各個(gè)規(guī)則文件 tests 文件夾存放的是單元測(cè)試文件 package.json 是 ESLint 插件 npm 包的說(shuō)明文件,其中的 name 屬性就是 ESLint 插件的名稱,其命名規(guī)則為:eslint-plugin-*

2. 整合自定義規(guī)則
lib 文件夾中的 config 下放之前定義好的自定義規(guī)則集分類,rules 文件夾下放相關(guān)的自定義規(guī)則分類,把下篇文中針對(duì)性寫的 _.get() 禁止第三參數(shù)的自定義規(guī)則放在 rules 中的 lodash-get.js 文件下且在 index.js 中引入:

3. 本地測(cè)試
如果你的 npm 包還沒發(fā)布,需要進(jìn)行本地調(diào)試,可使用 npm link 本地調(diào)試。在 eslint-plugin 項(xiàng)目中執(zhí)行 npm link 命令,就可以全局使用 eslint-plugin 命令了,然后可以在你的測(cè)試項(xiàng)目中執(zhí)行 npm link eslint-plugin,引入測(cè)試即可。在 npm 包文件夾下執(zhí)行 npm link 命令,會(huì)創(chuàng)建一個(gè)符號(hào)鏈接,鏈接全局文件夾 {prefix}/lib/node_modules/ 和你執(zhí)行 npm link 的包文件夾。
注意:package-name 是 package.json 中的 name, 而不是文件夾名。
五. 代碼提交檢測(cè)優(yōu)化
1. git hooks
git hooks 即 git 在執(zhí)行過(guò)程中的鉤子,其可以觸發(fā)自定義腳本。每個(gè)含有 repository 的項(xiàng)目下都存在一個(gè).git 文件、每個(gè).git 文件都包含一個(gè) hooks 文件,你可以在此文件夾根據(jù)鉤子的名稱來(lái)創(chuàng)建相應(yīng)的鉤子文件,當(dāng) git 執(zhí)行某些操作時(shí)相應(yīng)的鉤子文件就會(huì)被執(zhí)行。
下圖中就是各個(gè)鉤子的案例腳本,可以把 sample 去掉,直接編寫 shell 腳本執(zhí)行:

通常情況下我們會(huì)使用的鉤子有:
pre-commit: 執(zhí)行 git commit 的時(shí)候調(diào)用,在 commit 提交之前執(zhí)行,可以檢查即將提交代碼,如果結(jié)果非 0 退出時(shí)就會(huì)中斷此次提交。 commit-msg: 由 git commit 或 git merge 調(diào)用,以非 0 狀態(tài)碼退出時(shí),會(huì)中斷此次操作,主要是檢測(cè)提交的代碼注釋格式。
Q:.git 文件下相應(yīng)的 hook 文件配置可以直接上傳到遠(yuǎn)程倉(cāng)庫(kù),以備其他人拉取達(dá)到配置共享的效果嗎?
A:查詢了相關(guān)資料,在 .git 文件下的 hook 文件配置不能提交到遠(yuǎn)程倉(cāng)庫(kù),但是可以通過(guò)復(fù)制配置給其他同事,只是這樣并不是我們想要的結(jié)果。
2. husky
husky 是前端工程化時(shí)一個(gè)必不可少的工具,由于直接修改 .git/hooks 文件不方便,使用 husky 可以讓我們向項(xiàng)目中方便添加 git hooks。
2.1 pre-commit 庫(kù)
項(xiàng)目最開始的時(shí)候普遍是通過(guò)配置 pre-commit 庫(kù)起到提交前檢測(cè)的作用,npm install pre-commit \--save-dev 下載安裝之后在項(xiàng)目中按下方配置,當(dāng)我們?cè)?commit 提交之前就會(huì)執(zhí)行。
{?
??"scripts":?{?
????"lint":?"eslint?.?--fix",?
??},?
??"pre-commit"?:?["lint"],?
}
復(fù)制代碼
2.2 husky + pre-commit 鉤子
除了只使用 pre-commit 庫(kù)進(jìn)行提交前代碼檢測(cè)以外,目前最常用的就是使用 husky,npm install husky \--save-dev 安裝之后在 package.json 中 配置,可以搭配使用 pre-commit 鉤子,這樣當(dāng)提交 commit 的時(shí)候就會(huì)執(zhí)行腳本 npm run lint 進(jìn)行全項(xiàng)目文件檢測(cè)。如果只是目前配置的話。
注意:如果某個(gè)項(xiàng)目之前是通過(guò) pre-commit 庫(kù)進(jìn)行預(yù)檢測(cè)的話,后面想換成 husky + pre-commit 鉤子 的模式檢測(cè)的話,需要?jiǎng)h除 node_modules 之后再 install 才正常生效,因?yàn)楸镜?node_modules 中的 pre-commit 庫(kù)還在,兩者會(huì)產(chǎn)生沖突導(dǎo)致執(zhí)行 husky 失效。
{?
??"husky":?{?
????"hooks":?{?
??????"pre-commit":?"npm?run?lint",?//?在commit之前先執(zhí)行npm?run?lint命令?
????}?
??}?
??"scripts":?{?
????"lint":?"eslint?.?--ext?.js,.vue",?
??}?
}
復(fù)制代碼
3. lint-staged
husky 配置已經(jīng)可以輕松的實(shí)現(xiàn)提交前代碼檢測(cè),但是直接使用檢測(cè)也會(huì)帶來(lái)了很多弊端,比如每次提交都會(huì)檢測(cè)項(xiàng)目所有文件。如果想要只檢測(cè)提交過(guò)的代碼的話我們就可以使用 lint-staged,lint-staged 只讀取暫存區(qū)的文件、并運(yùn)行配置好的腳本,避免了對(duì)未提交到暫存區(qū)的代碼造成影響。lint-staged 過(guò)濾文件采用 glob 語(yǔ)法,它可以配合 husky 使用,由 husky 觸發(fā) lint-staged,再由 lint-staged 執(zhí)行腳本,配置好之后既可以減少 eslint 全量檢測(cè)時(shí)消耗時(shí)間過(guò)長(zhǎng)的問(wèn)題、又可以減少修復(fù)問(wèn)題占用的時(shí)間!
//?package.json?文件
??"husky":?{
????"hooks":?{
??????"pre-commit":?"lint-staged",
????}
??},
??"lint-staged":?{
????"*.vue":?[
??????"eslint?--cache?--fix"
????],
????"*.js":?[
??????"eslint?--cache?--fix"
????]
??},
復(fù)制代碼
3.1 兩份 .eslintrc.js 配置
有一些項(xiàng)目他不是單獨(dú)的 vue 項(xiàng)目或 node 項(xiàng)目,類似xx系統(tǒng)有用 node 做中間層,這時(shí)候一個(gè)項(xiàng)目中就會(huì)有兩個(gè)環(huán)境,為了針對(duì)不同環(huán)境的文件夾下用不同的規(guī)則集進(jìn)行檢查,我們這里準(zhǔn)備配置兩份 .eslintrc 文件,主要配置如下:
//?package.json?文件
"scripts":?{
??"lint":?"npm?run?lint-web?&&?npm?run?lint-node",
??"lint-web":?"eslint?.?-c?./.eslintrc-web.js?--ext?.js,.vue?--ignore-pattern?server/?--cache?--fix",
??"lint-node":?"eslint?server/?-c?./.eslintrc-node.js?--ext?.js?--cache?--fix",
}????
復(fù)制代碼
//?.eslintrc-web.js?文件
module.exports?=?{
??root:?true,
??plugins:?[
????'@zly'
??],
??extends:?[
????'plugin:@zly/vue',
??],
}
復(fù)制代碼
//?.eslintrc-node.js?文件
module.exports?=?{
??root:?true,
??plugins:?[
????'@zly'
??],
??extends:?[
????'plugin:@zly/node',
??],
}
復(fù)制代碼
配置完之后運(yùn)行可以正常拋錯(cuò) error,但是自動(dòng) autofix 的功能全部失效了。后續(xù)發(fā)現(xiàn)可能是因?yàn)槲覀儼?.eslintrc.js 改成了 .eslintrc-node.js、.eslintrc-web.js 導(dǎo)致的問(wèn)題,更改之后 ide 無(wú)法動(dòng)態(tài)識(shí)別 eslint 的配置。不過(guò)在編輯器中可以手動(dòng)配置,但是每個(gè)人的編譯器都要手動(dòng)配置真的比較麻煩,于是我們就想到了之前的參數(shù) root。
3.2 root 測(cè)試
前面介紹 root 參數(shù)的時(shí)候,有對(duì)著官方文檔及網(wǎng)上看相關(guān)的翻譯,總覺得好像不同的翻譯都不一樣,而且我自己?jiǎn)为?dú)對(duì)這個(gè)概念也模棱兩可的,所以就做了一個(gè) root 的一個(gè)測(cè)試實(shí)驗(yàn)。主要是項(xiàng)目 eslint-root-test 根目錄下新建一個(gè).eslintrc.js文件、在根目錄的 server 文件夾下也新建一個(gè).eslintrc.js文件,針對(duì)兩個(gè).eslintrc.js 中 root 配置不通過(guò)的情況進(jìn)行測(cè)試,測(cè)試結(jié)果如下:

根據(jù)以上測(cè)試結(jié)果發(fā)現(xiàn),root 恰好能解決我們想要兩份 rc 的需求,于是針對(duì)智能系統(tǒng)在跟目錄下設(shè)置了 .eslintrc.js 文件,在 server 文件下設(shè)置了 .eslintrc.js 文件,兩份 rc 文件 root 都設(shè)置為 true,最終配置為:
//?package.json?文件
??"husky":?{
????"hooks":?{
??????"pre-commit":?"lint-staged",
??????"commit-msg":?"commitlint?-e?$HUSKY_GIT_PARAMS"
????}
??},
??"lint-staged":?{
????"*.vue":?[
??????"stylelint?--config?/.stylelintrc.short.js",
??????"stylelint?--config?/.stylelintrc.js?--fix",
??????"eslint?--cache?--fix"
????],
????"*.css":?[
??????"stylelint?--config?/.stylelintrc.short.js",
??????"stylelint?--config?/.stylelintrc.js?--fix"
????],
????"server/**/*.js":?[
??????"eslint?-c?./server/.eslintrc.js?--cache?--fix"
????],
????"!(server)/**/*.js":?[
??????"eslint?--cache?--fix"
????]
??},
????
??"scripts":?{
????"lint":?"npm?run?lint-web?&&?npm?run?lint-node",
????"lint-web":?"eslint?.?--ext?.js,.vue?--ignore-pattern?server/?--cache?--fix",
????"lint-node":?"eslint?server/?-c?./server/.eslintrc.js?--ext?.js?--cache?--fix",
??},
復(fù)制代碼
執(zhí)行 npm run lint 可以全量跑所有文件的檢測(cè),如果單獨(dú)檢測(cè)可以單獨(dú)執(zhí)行 npm run lint-web 和 npm run lint-node,lint-staged 中配置也針對(duì) server 文件及非 server 文件進(jìn)行暫存區(qū)文件檢測(cè)。
4. package.json 與 node_modules 中依賴版本不同時(shí)處理
check-dependencies庫(kù):www.npmjs.com/package/che…[6]
我們常常會(huì)碰到這樣的問(wèn)題,當(dāng) pull 拉的最新的代碼有個(gè)依賴升版了、但 node_modules 本地安裝的依賴版本還是舊的時(shí)候,這時(shí)候運(yùn)行是沒有問(wèn)題的但是兩邊依賴能夠保持同步一致才是最好的。
問(wèn)題一:
Q:如果需要每次 package.json 中我們自己的私有 npm 包依賴更新了,但是本地還是舊版本,怎么讓針對(duì)私有 npm 遠(yuǎn)程和本地版本不一致的問(wèn)題給出提示呢?
解決一:
A:找了一下沒有找到特別合適的解決辦法。
解決二:
A:針對(duì)自己發(fā)布的包做這種提示的方法沒有找到很合適的,但是在尋找的過(guò)程中發(fā)現(xiàn)了
check-dependencies這個(gè)庫(kù),這個(gè)庫(kù)不會(huì)檢測(cè)某個(gè)依賴,它是針對(duì) package.json 中所有的依賴進(jìn)行遠(yuǎn)程和本地的比對(duì),但凡有一個(gè)不同步就會(huì)給出提示。
4.1 npm script 鉤子
npm script 是記錄在 package.json 中的 scripts 字段中的一些自定義腳本,使用自定義腳本,用戶可以將一些項(xiàng)目中常用的命令行記錄在 package.json 而不需要每次都要敲一遍。npm 腳本中有提供 pre 和 post 兩個(gè)鉤子,自定義的腳本命令也可以加上 pre 和 post 鉤子。例如 dev 腳本命令的鉤子就是 predev 和 postdev,用戶在執(zhí)行 npm run dev 的時(shí)候,會(huì)自動(dòng)按照下面的順序執(zhí)行:
npm?run?predev?&&?npm?run?dev?&&?npm?run?postdev
復(fù)制代碼
4.2 check-dependencies
下載安裝 check-dependencies 之后,于是打算在 predev 鉤子中執(zhí)行 node check.js 腳本,當(dāng)執(zhí)行 npm run dev 的時(shí)候會(huì)預(yù)先執(zhí)行 predev 中的命令,如果發(fā)現(xiàn)兩邊依賴不同時(shí)就進(jìn)行拋錯(cuò)中斷,當(dāng)兩邊一致之后才能正常運(yùn)行:
//?package.json?文件
"scripts":?{
??"predev":?"node?check.js"
}
復(fù)制代碼
const?output?=?require('check-dependencies')
??.sync({
????verbose:?true,?
??})
if?(output.status?===?1)?{
??throw?new?Error('本地依賴與package.json中不同步,請(qǐng)先npm?install再執(zhí)行')
}
復(fù)制代碼

由于每個(gè)項(xiàng)目需要額外放一個(gè) js 文件這樣不是很友好,后面嘗試看看能不能直接一個(gè)命令執(zhí)行檢測(cè)報(bào)錯(cuò)就可以,發(fā)現(xiàn) check-dependencies 庫(kù)本身其實(shí)已經(jīng)支持直接執(zhí)行報(bào)錯(cuò):
//?package.json?文件
"scripts":?{
??"predev":?"check-dependencies"
}
復(fù)制代碼

六. 后續(xù)發(fā)展
1. 日常維護(hù)
目前已經(jīng)替換了大部分項(xiàng)目且測(cè)試正常,對(duì)新項(xiàng)目接入檢測(cè)工具只需要安裝 npm 包,在 .eslintrc.js 中配置即可生效、簡(jiǎn)單又方便,后續(xù)也只需要針對(duì)其中某些規(guī)則進(jìn)行統(tǒng)一就可以了。
2. 待解決項(xiàng)
測(cè)試過(guò)程中其實(shí)還是發(fā)現(xiàn)了很多問(wèn)題的,比如剛開始我自己但是比較習(xí)慣用 webstrom 的,但是它測(cè)試的時(shí)候真的問(wèn)題太多,遵循單一變量原則就使用 vscode來(lái)進(jìn)行測(cè)試,其中問(wèn)題搜了很多地方都沒有解決辦法,問(wèn)題主要如下:
同一份配置,vscode 可以正常自動(dòng) fix,但是在 webstrom 中 eslint 自動(dòng) fix 失效 sourcetree 中提交代碼的時(shí)候會(huì)繞過(guò) eslint 檢測(cè),原因不詳
3. 本文參考
命令行工具調(diào)試:segmentfault.com/a/119000001…[7]
ESLint 在中大型團(tuán)隊(duì)的應(yīng)用實(shí)踐:tech.meituan.com/2019/08/01/…[8]
作者:跑路磚家
https://juejin.cn/post/7035614700700368910

回復(fù)“加群”與大佬們一起交流學(xué)習(xí)~
點(diǎn)擊“閱讀原文”查看 130+ 篇原創(chuàng)文章
