從 ESLint 開始,說透我如何在團隊項目中基于 Vue 做代碼校驗
最近遇到了一個老項目,比較有意思的是這個項目集前后端的代碼于一起,而后端也會去修改前端代碼,所以就出現(xiàn)了后端用 IntelliJ IDEA 來開發(fā)前端項目,而前端用 VSCode 來開發(fā)前端項目的情況。于是乎,出現(xiàn)了代碼規(guī)范的問題,所以就有了這篇文章,整理了一下前端代碼校驗以及在 Vue 項目中的實踐。
閱讀完這篇文章,你可以收獲:
- 能夠自己親手寫出一套 ESLint 配置;
- 會知道業(yè)界都有哪些著名的 JS 代碼規(guī)范,熟讀它們可以讓你寫出更規(guī)范的代碼;
- vue-cli 在初始化一個包含代碼校驗的項目時都做了什么;
- Prettier 是什么?為什么要使用它?如何與 ESLint 配合使用?
- EditorConfig 又是什么?如何使用?
- 如何在 VSCode 中通過插件來協(xié)助代碼校驗工作;
- 如何保證 push 到遠程倉庫的代碼是符合規(guī)范的;
下面開始閱讀吧,如果你對 ESLint 比較熟悉,可以直接跳過這個部分。
ESLint 是什么
ESLint 是一個集代碼審查和修復的工具,它的核心功能是通過配置一個個規(guī)則來限制代碼的合法性和風格。
配置解析器和解析參數(shù)
ESLint 的解析器,早期的時候用的是 Esprima[1],后面基于 Esprima v1.2.2 版本開發(fā)了一個新的解析器 Espree[2],并且把它當做默認解析器。
除了使用 ESLint 自帶的解析器外,還可以指定其他解析器:
- @babel/eslint-parser[3]:使 Babel 和 ESLint 兼容,對一些 Babel 語法提供支持;
- @typescript-eslint/parser[4]:TSLint 被棄用后,TypeScript 提供了此解析器用于將其與 ESTree 兼容,使 ESLint 對 TypeScript 進行支持;
為項目指定某個選擇器的原則是什么?
- 如果你的項目用到了比較新的 ES 語法,比如 ES2021 的 Promise.any(),那就可以指定 @babel/eslint-parser 為解析器;
- 如果項目是基于 TS 開發(fā)的,那就使用 @typescript-eslint/parser;
“如果你對 ES 最新標準還不熟悉,可以看看這篇文章:送你一份精心總結(jié)的3萬字ES6實用指南(下)
除了指定解析器 parser 外,還可以額外配置解析器參數(shù) parserOption:
- {
- // ESLint 默認解析器,也可以指定成別的
- parser: "espree",
- parserOption: {
- // 指定要使用的 ECMAScript 版本,默認值 5
- ecmaVersion: 5,
- // 設置為 script (默認) 或 module(如果你的代碼是 ECMAScript 模塊)
- sourceType: "script",
- // 這是個對象,表示你想使用的額外的語言特性,所有選項默認都是 false
- ecmafeatures: {
- // 是否允許在全局作用域下使用 return 語句
- globalReturn: false,
- // 是否啟用全局 strict 模式(嚴格模式)
- impliedStrict: false,
- // 是否啟用JSX
- jsx: false,
- // 是否啟用對實驗性的objectRest/spreadProperties的支持
- experimentalObjectRestSpread: false
- }
- }
- }
指定環(huán)境 env
指定不同的環(huán)境可以給對應環(huán)境下提供預設的全局變量。比如說在 browser 環(huán)境下,可以使用 window 全局變量;在 node 環(huán)境下,可以使用 process 全局變量等;
- ESLint 中可配置的環(huán)境比較多,這里有份完整的環(huán)境列表[5],下面列出幾個比較常見的:
- browser:瀏覽器全局變量;
- node:Node.js 全局變量和作用域;
- es6:es6 中除了模塊之外的其他特性,同時將自動設置 parserOptions.ecmaVersion 參數(shù)為 6;以此類推 ES2017 是 7,而 ES2021 是 12;
- es2017:parserOptions.ecmaVersion 為 8;
- es2020:parserOptions.ecmaVersion 為 11;
- es2021:parserOptions.ecmaVersion 為 12;
配置方式如下:
- {
- env: {
- browser: true,
- node: true,
- es6: true,
- commonjs: true,
- mocha: true,
- jquery: true,
- }
- }
可以指定多個環(huán)境并不意味著配置的環(huán)境越多越好,實際配置的時候還是得依據(jù)當前項目的環(huán)境來選擇。
配置全局變量 globals
ESLint 的一些核心規(guī)則依賴于對代碼在運行時可用的全局變量的了解。由于這些在不同環(huán)境之間可能會有很大差異,并且在運行時會進行修改,因此 ESLint 不會假設你的執(zhí)行環(huán)境中存在哪些全局變量。
如果你想使用這些全局變量,那就可以通過 globals 來指定。比如在 react .eslintrc.js[6] 里就把 spyOnDev、 spyOnProd 等變量掛在了 global 下作為全局變量:
- {
- globals: {
- spyOnDev: true,
- spyOnProd: true,
- }
- }
對于它的值需要特別說明下:
- false、readable、readonly 這 3 個是等價的,表示變量只可讀不可寫;
- true、writeable、writable 這 3 個是等價的,表示變量可讀可寫;
配置擴展 extends
實際項目中配置規(guī)則的時候,不可能團隊一條一條的去商議配置,太費精力了。通常的做法是使用業(yè)內(nèi)大家普通使用的、遵循的編碼規(guī)范;然后通過 extends 去引入這些規(guī)范。extends 配置的時候接受字符串或者數(shù)組:
- {
- extends: [
- 'eslint:recommended',
- 'plugin:vue/essential',
- 'eslint-config-standard', // 可以縮寫成 'standard'
- '@vue/prettier',
- './node_modules/coding-standard/.eslintrc-es6'
- ]
- }
從上面的配置,可以知道 extends 支持的配置類型可以是以下幾種
- eslint 開頭的:是 ESLint 官方的擴展;
- plugin 開頭的:是插件類型擴展,比如 plugin:vue/essential;
- eslint-config 開頭的:來自 npm 包,使用時可以省略前綴 eslint-config-,比如上面的可以直接寫成 standard;
- @開頭的:擴展和 eslint-config 一樣,只是在 npm 包上面加了一層作用域 scope;
- 一個執(zhí)行配置文件的相對路徑或絕對路徑;
那有哪些常用的、比較著名擴展可以被 extends 引入呢
- eslint:recommended:ESLint 內(nèi)置的推薦規(guī)則,即 ESLint Rules 列表中打了鉤的那些規(guī)則;
- eslint:all:ESLint 內(nèi)置的所有規(guī)則;
- eslint-config-standard[7]:standard 的 JS 規(guī)范;
- eslint-config-prettier[8]:關閉和 ESLint 中以及其他擴展中有沖突的規(guī)則;
- eslint-config-airbnb-base:airbab 的 JS 規(guī)范;
- eslint-config-alloy[9]:騰訊 AlloyTeam 前端團隊出品,可以很好的針對你項目的技術棧進行配置選擇,比如可以選 React、Vue(現(xiàn)已支持 Vue 3.0)、TypeScript 等;
使用插件 plugins
ESLint 提供插件是干嘛用的
ESLint 雖然可以定義很多的 rules,以及通過 extends 來引入更多的規(guī)則,但是說到底只是檢查 JS 語法。如果需要檢查 Vue 中的 template 或者 React 中的 jsx,就束手無策了。所以引入插件的目的就是為了增強 ESLint 的檢查能力和范圍。
如何配置插件
ESLint 相關的插件的命名形式有 2 種:不帶命名空間的和帶命名空間的,比如:
- eslint-plugin- 開頭的可以省略這部分前綴;
- @/ 開頭的;
- {
- plugins: [
- 'jquery', // 是指 eslint-plugin-jquery
- '@jquery/jquery', // 是指 @jquery/eslint-plugin-jquery
- '@foobar', // 是指 @foobar/eslint-plugin
- ]
- }
當需要基于插件進行 extends 和 rules 的配置的時候,需要加上插件的引用,比如:
- {
- plugins: [
- 'jquery', // eslint-plugin-jquery
- '@foo/foo', // @foo/eslint-plugin-foo
- '@bar, // @bar/eslint-plugin
- ],
- extends: [
- 'plugin:jquery/recommended',
- 'plugin:@foo/foo/recommended',
- 'plugin:@bar/recommended'
- ],
- rules: {
- 'jquery/a-rule': 'error',
- '@foo/foo/some-rule': 'error',
- '@bar/another-rule': 'error'
- },
- }
以上配置來自 ESLint plugins[10]
配置規(guī)則 rules
ESLint 提供了大量內(nèi)置的規(guī)則,這里是它的規(guī)則列表 ESLint Rules,除此之外你還可以通過插件來添加更多的規(guī)則。
規(guī)則的校驗說明,有 3 個報錯等級
- off 或 0:關閉對該規(guī)則的校驗;
- warn 或 1:啟用規(guī)則,不滿足時拋出警告,且不會退出編譯進程;
- error 或 2:啟用規(guī)則,不滿足時拋出錯誤,且會退出編譯進程;
通常規(guī)則只需要配置開啟還是關閉即可;但是也有些規(guī)則可以傳入屬性,比如:
- {
- rules: {
- 'quotes': ['error', 'single'], // 如果不是單引號,則報錯
- 'one-var': ['error', {
- 'var': 'always', // 每個函數(shù)作用域中,只允許 1 個 var 聲明
- 'let': 'never', // 每個塊作用域中,允許多個 let 聲明
- 'const': 'never', // 每個塊作用域中,允許多個 const 聲明
- }]
- }
- }
如何知道某個擴展有哪些規(guī)則可以配置,以及每個規(guī)則具體限制?這里直接給出業(yè)內(nèi)著名且使用比較多的規(guī)則列表的快速鏈接:
- ESLint rules,這整個列表對應 eslint:all,而打鉤 ✔️ 的是 eslint:recommenmed;
- Prettier rules
- standard rules
- airbnb rules
- AlloyTeam vue rules
規(guī)則的優(yōu)先級
- 如果 extends 配置的是一個數(shù)組,那么最終會將所有規(guī)則項進行合并,出現(xiàn)沖突的時候,后面的會覆蓋前面的;
- 通過 rules 單獨配置的規(guī)則優(yōu)先級比 extends 高;
其他配置
配置當前目錄為 root
ESLint 檢測配置文件步驟:
- 在要檢測的文件同一目錄里尋找 .eslintrc.* 和 package.json;
- 緊接著在父級目錄里尋找,一直到文件系統(tǒng)的根目錄;
- 如果在前兩步發(fā)現(xiàn)有 root:true 的配置,停止在父級目錄中尋找 .eslintrc;
- 如果以上步驟都沒有找到,則回退到用戶主目錄 ~/.eslintrc 中自定義的默認配置;
通常我們都習慣把 ESLint 配置文件放到項目根目錄,因此可以為了避免 ESLint 校驗的時候往父級目錄查找配置文件,所以需要在配置文件中加上 root: true。
- {
- root: true,
- }
添加共享數(shù)據(jù)
ESLint 支持在配置文件添加共享設置,你可以添加 settings 對象到配置文件,它將提供給每一個將被執(zhí)行的規(guī)則。如果你想添加的自定義規(guī)則而且使它們可以訪問到相同的信息,這將會很有用,并且很容易配置:
- {
- settings: {
- sharedData: 'Hello'
- },
- }
參考:ESLint配置文件.eslintrc參數(shù)說明[11]
針對個別文件設置新的檢查規(guī)則
比如 webpack 的中包含了某些運行時的 JS 文件,而這些文件是只跑在瀏覽器端的,所以需要針對這部分文件進行差異化配置:
- overrides: [
- {
- files: ["lib/**/*.runtime.js", "hot/*.js"],
- env: {
- es6: false,
- browser: true
- },
- globals: {
- Promise: false
- },
- parserOptions: {
- ecmaVersion: 5
- }
- }
- ]
以上配置來自 webpack .eslintrc.js[12]
如何校驗
上面細說了 ESLint 的各種配置項,以及針對 Vue 項目如何進行差異配置的說明。
現(xiàn)在我們知道了如何配置,但是你知道這些配置都是配置到哪里的嗎?
配置方式
ESLint 支持 3 種配置方式:
- 命令行:不推薦,不做介紹;
- 單文件內(nèi)注釋:不推薦,不做介紹;
- 配置文件:配置文件的類型可以是好幾種,比如:.js、.yml、json 等。推薦使用 .eslintrc.js;
下面通過命令來生成一個配置文件:
- # 安裝 eslint
- npm i eslint -D
- # 初始化一個配置文件
- npx eslint --init
最后會在當前目錄生成一個 .eslintrc.js 文件。這里就不把代碼貼出來了,沒參考意義。
上面我們知道了可以將配置統(tǒng)一寫到一個配置文件里,但是你知道該如何去觸發(fā)這個配置文件的校驗規(guī)則嘛?
校驗單個文件
- // 校驗 a.js 和 b.js
- npx eslint a.js b.js
- // 校驗 src 和 scripts 目錄
- npx eslint src scripts
校驗別的類型的文件
通常 ESLint 只能校驗 JS 文件。比如需要校驗 .vue 文件,光配置 vue 插件和 vue-eslint-parser 解析器是不夠的,還需要讓 ESLint 在查找文件的時候找到 .vue 文件。
可以通過 --ext 來指定具體需要校驗的文件:
- npx eslint --ext .js,.jsx,.vue src
自動修復部分校驗錯誤的代碼
rules 列表項中標識了一個扳手 🔧 圖案的規(guī)則就標識該規(guī)則是可以通過 ESLint 工具自動修復代碼的。如何自動修復呢?通過 --fix 即可。比如對于 ESLint Rules 里的這個 semi 規(guī)則,它就是帶扳手圖案的。
對于如下的 a.js 代碼:
- const num = 12
當在配置文件配置了 'semi': [2, 'always'] 后,運行命令:
- npx eslint --fix a.js
校驗直接就通過了,且會自動修復代碼,在代碼末尾自動加上分號。
把校驗命令加到 package.json
檢驗命令比較長,也難記,習慣上會把這些命名直接寫到 package.json 里:
- {
- "scripts": {
- "lint": "npx eslint --ext .js,.jsx,.vue src",
- "lint:fix": "npx eslint --fix --ext .js,.jsx,.vue src",
- }
- }
過濾一些不需要校驗的文件
對于一些公共的 JS、測試腳本或者是特定目錄下的文件習慣上是不需要校驗的,因此可以在項目根目錄通過創(chuàng)建一個 .eslintignore 文件來配置,告訴 ESLint 校驗的時候忽略它們:
- public/
- src/main.js
除了 .eslintignore 中指定的文件或目錄,ESLint 總是忽略 /node_modules/ 和 /bower_components/ 中的文件;因此對于一些目前解決不了的規(guī)則報錯,但是如果又急于打包上線,在不影響運行的情況下,我們就可以利用 .eslintignore 文件將其暫時忽略。
在 Vue 項目中的實踐
上面把 ESLint 的幾乎所有的配置參數(shù)和校驗方式都詳細的介紹了一遍,但是如果想在項目中落地,僅僅靠上面的知識還是不夠的。下面將細說如何在 Vue 中落地代碼校驗。
關于如何在 Vue 中落地代碼校驗,一般是有 2 種情況:
- 通過 vue-cli 初始化項目的時候已經(jīng)選擇了對應的校驗配置
- 對于一個空的 Vue 項目,想接入代碼校驗
其實這 2 種情況最終的校驗的核心配置都是一樣的,只是剛開始的時候安裝的包有所區(qū)別。下面通過分析 vue-cli 配置的代碼校驗,來看看它到底做了哪些事情,通過它安裝的包以及包的作用,我們就會知道如何在空項目中配置代碼校驗了。
通過 vue-cli 初始化的項目
如果你的項目最初是通過 vue-cli 新建的,那么在新建的時候會讓你選
- 是否支持 eslint;
- 是否開啟保存校驗;
- 是否開啟提交前校驗;
如果都開啟了話,會安裝如下幾個包:
- eslint:前面 2 大章節(jié)介紹的就是這玩意,ESLint 出品,是代碼校驗的基礎包,且提供了很多內(nèi)置的 Rules,比如 eslint:recommended 經(jīng)常被作為項目的 JS 檢查規(guī)范被引入;
- babel-eslint:一個對 Babel 解析器的包裝,使其能夠與 ESLint 兼容;
- lint-staged:請看后面 pre-commit 部分;
- @vue/cli-plugin-eslint
- eslint-plugin-vue
下面重點介紹 @vue/cli-plugin-eslint 和 eslint-plugin-vue,說下這 2 個包是干嘛的。
@vue/cli-plugin-eslint
這個包它主要干了 2 件事情:
第一件事
往 package.json 里注冊了一個命令:
- {
- "scripts": {
- "lint": "vue-cli-service lint"
- }
- }
執(zhí)行這個命令之后,它會去檢查和修復部分可以修復的問題。默認查找的文件是 src 和 tests 目錄下所有的 .js,.jsx,.vue 文件,以及項目根目錄下所有的 js 文件(比如,也會檢查 .eslintrc.js)。
當然你也可以自定義的傳入?yún)?shù)和校驗文件:
- vue-cli-service lint [options] [...files]
支持的參數(shù)如下:
- --no-fix: 不會修復 errors 和 warnings;
- --max-errors [limit]:指定導致出現(xiàn) npm ERR 錯誤的最大 errors 數(shù)量;
第二件事
增加了代碼保存觸發(fā)校驗的功能 lintOnSave,這個功能默認是開啟的。如果想要關閉這個功能,可以在 vue.config.js 里配置,習慣上只開啟 development 環(huán)境下的代碼保存校驗功能:
- module.exports = {
- lintOnSave: process.env.NODE_ENV === 'development',
- }
lintOnSave 參數(shù)說明:
- true 或者 warning:開啟保存校驗,會將 errors 級別的錯誤在終端中以 WARNING 的形式顯示。默認的,WARNING 將不會導致編譯失敗;
- false:不開啟保存校驗;
- error:開啟保存校驗,會將 errors 級別的錯誤在終端中以 ERROR 的形式出現(xiàn),會導致編譯失敗,同時瀏覽器頁面變黑,顯示 Failed to compile。
eslint-plugin-vue
eslint-plugin-vue 是對 .vue 文件進行代碼校驗的插件。
針對這個插件,它提供了這幾個擴展
- plugin:vue/base:基礎
- plugin:vue/essential:預防錯誤的(用于 Vue 2.x)
- plugin:vue/recommended:推薦的,最小化任意選擇和認知開銷(用于 Vue 2.x);
- plugin:vue/strongly-recommended:強烈推薦,提高可讀性(用于 Vue 2.x);
- plugin:vue/vue3-essential:(用于 Vue 3.x)
- plugin:vue/vue3-strongly-recommended:(用于 Vue 3.x)
- plugin:vue/vue3-recommended:(用于 Vue 3.x)
各擴展規(guī)則列表:vue rules
看到這么一堆的擴展,是不是都不知道選哪個了
代碼規(guī)范的東西,原則還是得由各自的團隊去磨合商議出一套適合大家的規(guī)則。不過,如果你用的是 Vue2,我這里可以推薦 2 套 extends 配置:
- {
- // Vue 官方示例上的配置
- extends: ['eslint:recommended', 'plugin:vue/recommended'],
- // 或者使用 AlloyTeam 團隊那套
- extends: ['alloy', 'alloy/vue']
- }
配置和插件對應的解析器
如果是 Vue 2.x 項目,配置了 eslint-plugin-vue 插件和 extends 后,template 校驗還是會失效,因為不管是 ESLint 默認的解析器 Espree 還是 babel-eslint 都只能解析 JS,無法解析 template 的內(nèi)容。
而 vue-eslint-parser 只能解析 template 的內(nèi)容,但是不會解析 JS,因此還需要對解析器做如下配置:
- {
- parser: 'vue-eslint-parser',
- parseOptions: {
- parser: 'babel-eslint',
- ecmaVersion: 12,
- sourceType: 'module'
- },
- extends: [
- 'eslint:recommended',
- 'plugin:vue/recommended'
- ],
- plugins: ['vue']
- }
參考:eslint-plugin-vue faq[13]
讓 Prettier 管控代碼風格
針對 Prettier 不得不提出以下疑問?
- Prettier 是什么?
- 為什么有了 ESLint,還需要引入 Prettier 呢?它兩之間有什么區(qū)別?
- 如何配置 Prettier?
- Prettier 如何和 ESLint 結(jié)合使用?
- Prettier 是什么
用它自己的話來說:我是一個自以為是的代碼格式化工具,而且我支持的文件類型很多,比如:
- JavaScript(包括實驗中的特性)
- JSX
- Vue
- TypeScript
- CSS、Less、SCSS
- HTML
- JSON
- Markdown
以及還有一些其他類型的文件。
Prettier 對比 ESLint
我們知道 ESLint 負責了對代碼的校驗功能,并且主要提供了 2 類規(guī)則:
- 檢查格式化的規(guī)則
- 檢查代碼質(zhì)量的規(guī)則
說到底 ESLint 就是通過一條條的規(guī)則去限制代碼的規(guī)范,但是這些規(guī)則畢竟是有限的,而且更重要的是這些規(guī)則的重點并不在代碼風格上,所以單憑 ESLint 并不能完全的統(tǒng)一代碼風格。
這個時候就需要引入 Prettier 了,因為它干的事就是只管代碼格式化,不管代碼質(zhì)量。
“Prettier:在代碼風格這一塊,我一直拿捏的死死的。
如何配置 Prettier
初始化操作:
- # 安裝包
- npm i prettier -D
- # 新建 .prettierrc.js
- echo module.exports = {} > .prettierrc.js
- # 新建 .prettierignore
- echo > .prettierignore
Prettier 支持可以配置參數(shù)不多,總共才 21 個,這里是所有參數(shù)的說明 prettier options[14]
所有參數(shù)都有默認值,也就是說即使你沒有配置 .prettierrc.js,當你用 Prettier 去格式化代碼的時候全部都會走默認配置。針對個別參數(shù),你不想用默認設置的話,就可以在 .prettierrc.js 配置具體想要的值。
如下,把項目中會用到的參數(shù)進行一個說明:
- module.exports = {
- printWidth: 80, //(默認值)單行代碼超出 80 個字符自動換行
- tabWidth: 2, //(默認值)一個 tab 鍵縮進相當于 2 個空格
- useTabs: true, // 行縮進使用 tab 鍵代替空格
- semi: false, //(默認值)語句的末尾加上分號
- singleQuote: true, // 使用單引號
- quoteProps: 'as-needed', //(默認值)僅僅當必須的時候才會加上雙引號
- jsxSingleQuote: true, // 在 JSX 中使用單引號
- trailingComma: 'all', // 不用在多行的逗號分隔的句法結(jié)構(gòu)的最后一行的末尾加上逗號
- bracketSpacing: true, //(默認值)在括號和對象的文字之間加上一個空格
- jsxBracketSameLine: true, // 把 > 符號放在多行的 JSX 元素的最后一行
- arrowParens: 'avoid', // 當箭頭函數(shù)中只有一個參數(shù)的時候可以忽略括弧
- vueIndentScriptAndStyle: false, //(默認值)對于 .vue 文件,不縮進 <script> 和 <style> 里的內(nèi)容
- embeddedLanguageFormatting: 'off', // 不允許格式化內(nèi)嵌的代碼塊,比如 markdown 文件里的代碼塊
- };
“擴展閱讀:關于 Trailing commas[15] 你或許想了解更多。
然后可以通過命令來格式化代碼:
- # 將格式化當前目錄及子目錄下所有文件
- npx prettier --write .
- # 檢查某個文件是否已經(jīng)格式化
- npx prettier --check src/main.js
如果有些文件不想被 Prettier 格式化,可以將其寫入到 .prettierignore 里:
- build/
- package.json
- public/
- test/*.*
Prettier 和 ESLint 一起干活更配哦
上面介紹了 Prettier 的具體配置,這里主要介紹和 ESLint 結(jié)合使用的配置和注意事項。
和 ESLint 配合使用需要用到 eslint-plugin-prettier 這個插件:
- npm i eslint-plugin-prettier -D
配置:
- {
- plugins: ['prettier'],
- rules: {
- 'prettier/prettier': 'error'
- }
- }
這個插件的工作原理是先調(diào)用 Prettier 對你的代碼進行格式化,然后會把格式化前后不一致的地方進行標記,通過配置 'prettier/prettier': 'error' 此條規(guī)則會將標記地方進行 error 級別的報錯提示,然后可以通過 ESLint 的 --fix 自動修復功能將其修復。
沖突了怎么辦
通過前面的介紹,我們知道 ESLint 也是會對代碼風格做一些限制的,而 Prettier 主要就是規(guī)范代碼風格,所以在把它們結(jié)合一起使用的時候是存會在一些問題的。對于個別規(guī)則,會使得雙方在校驗后出現(xiàn)代碼格式不一致的問題。
那么當 Prettier 和 ESLint 出現(xiàn)沖突之后,該怎么辦呢?
用 Prettier 的話來說很簡單,只要使用 eslint-config-prettier 就可以了。解決沖突的思路就是通過將這個包提供的擴展放到 extends 最后面引入,依據(jù) rules 生效的優(yōu)先級,所以它會覆蓋前面起沖突的規(guī)則,比如:
- {
- extends: [
- 'eslint:recommended',
- 'prettier', // 必須放最后
- ],
- }
除了能覆蓋和 ESLint 中起沖突的規(guī)則之外,eslint-config-prettier 還能覆蓋來自以下插件的規(guī)則(只列了部分):
- eslint-plugin-standard
- eslint-plugin-vue
那 eslint-config-prettier 到底提供了哪些覆蓋規(guī)則呢?直接看這個列表:eslint-config-prettier rules
如果想覆蓋某些插件的規(guī)則,需要引入對應插件的擴展,比如:
- {
- extends: [
- 'standard',
- 'plugin:vue/recommended',
- 'prettier/standard', // 覆蓋 eslint-config-stanard
- 'prettier/vue', // 覆蓋 eslint-plugin-vue
- ],
- }
“提示:在 eslint-config-prettier 8.0.0 版本后,extends 不再需要為單獨的插件引入對應擴展來覆蓋沖突了,統(tǒng)一引入 'prettier' 即可。
如果同時使用了 eslint-plugin-prettier 和 eslint-config-prettier 可以這么配置:
- {
- extends: ['plugin:prettier/recommended'],
- }
它其實和下面這些配置是等價的:
- {
- extends: ['prettier'], // eslint-config-prettier 提供的,用于覆蓋起沖突的規(guī)則
- plugins: ['prettier'], // 注冊 eslint-plugin-prettier 插件
- rules: {
- 'prettier/prettier': 'error',
- 'arrow-body-style': 'off',
- 'prefer-arrow-callback': 'off'
- }
- }
所以如果是在 Vue 2 項目中配置 ESLint 和 Prettier 會這么配置:
- {
- parser: 'vue-eslint-parser',
- parseOptions: {
- parser: 'babel-eslint',
- ecmaVersion: 12,
- sourceType: 'module'
- },
- extends: [
- 'eslint:recommended',
- 'plugin:vue/recommended',
- 'plugin:prettier/recommended', // 在前面 Vue 配置的基礎上加上這行
- ],
- plugins: ['vue']
- }
其實如果你的項目是用 vue-cli 初始化的,且選擇了 eslint + prettier 方案的話,生成的項目中,.eslintrc.js 配置文件中 extends 的配置是這樣的:
- {
- extends: [
- 'plugin:vue/essential',
- 'eslint:recommended',
- '@vue/prettier'
- ]
- }
它的最后一項擴展是 @vue/prettier,這個對應的是 @vue/eslint-config-prettier 這個包,讓我們看看這個包下面的 index.js 內(nèi)容:
- {
- plugins: ['prettier'],
- extends: [
- require.resolve('eslint-config-prettier'),
- require.resolve('eslint-config-prettier/vue')
- ],
- rules: {
- 'prettier/prettier': 'warn'
- }
- }
這個和我們上面配置的內(nèi)容是相差無幾的,而引入 eslint-config-prettier/vue 是因為這個 @vue/eslint-config-prettier 包依賴的 eslint-config-prettier 版本是 ^6.0.0 版本的,所以在處理沖突的時候需要特別指定和對應類型插件匹配的擴展。
讓 EditorConfig 助力多編輯器開發(fā)吧
EditorConfig[16] 是個啥玩意?它可以對多種類型的單文件進行簡單的格式化,它提供的配置參數(shù)很少:
- # 告訴 EditorConfig 插件,這是根文件,不用繼續(xù)往上查找
- root = true
- # 匹配全部文件
- [*]
- # 設置字符集
- charset = utf-8
- # 縮進風格,可選 space、tab
- indent_style = tab
- # 縮進的空格數(shù),當 indent_style = tab 將使用 tab_width
- # 否則使用 indent_size
- indent_size = 2
- tab_width = 2
- # 結(jié)尾換行符,可選 lf、cr、crlf
- end_of_line = lf
- # 在文件結(jié)尾插入新行
- insert_final_newline = true
- # 刪除一行中的前后空格
- trim_trailing_whitespace = true
- # 匹配md結(jié)尾的文件
- [*.md]
- insert_final_newline = false
- trim_trailing_whitespace = false
雖然它提供的格式化的配置參數(shù)很少,就 3 個,縮進風格、是否在文件末尾插入新行和是否刪除一行中前后空格。但是它還是非常有必要存在的,理由有 3 個:
- 能夠在不同的編輯器和 IDE 中保持一致的代碼風格;
- 配合插件打開文件即自動格式化,非常方便
- 支持格式化的文件類型很多;
如果需要讓以上的配置生效,還得在 VSCode 里安裝 EditorConfig for VS Code 這個插件配合使用。
重點來了
可以看到 EditorConfig 和 Prettier 會存在一些重復的配置,比如都提供了對縮進的配置參數(shù),所以在實際使用的時候需要避免它們,或者把他們的參數(shù)設置為一致。
在 VSCode 中支持 ESLint
前面做的配置,都需要執(zhí)行命令才能進行檢查和修復代碼,還是挺不方便的,如果我希望編輯完或者保存的時候去檢查代碼該如何做呢?可以直接在 IDE 里安裝 ESLint 插件,因為我使用的是 VSCode,所以這里只介紹在 VSCode 中的配置。
- 在使用前,需要把 ESLint 擴展安裝到 VSCode 里,這里我就不細說安裝步驟了。安裝完成后,需要在設置里寫入配置:
- 在 VSCode 左下角找到一個齒輪 ⚙ 圖標,點擊后選擇設置選項,這個時候打開了設置面板;
- 然后在 VSCode 右上角找到打開設置(json)的圖標,點擊后,會打開 settings.json 文件;
然后把以下配置貼進去即可;
- {
- "eslint.alwaysShowStatus": true, // 總是在 VSCode 顯示 ESLint 的狀態(tài)
- "eslint.quiet": true, // 忽略 warning 的錯誤
- "editor.codeActionsOnSave": { // 保存時使用 ESLint 修復可修復錯誤
- "source.fixAll": true,
- "source.fixAll.eslint": true
- }
- }
配置說明,在 ESLint 2.0.4 版本開始:
- 不需要通過 eslint.validate 來指定校驗的文件類型了,已經(jīng)自動支持了 .vue 文件;
- editor.codeActionsOnSave 開啟保存自動修復功能;
當這樣配置之后呢,每次編輯代碼 ESLint 都會實時校驗代碼,且當保存的時候會自動 fix,是不是很方便呢。不過對于有些無法自動 fix 的代碼就需要你手動去修改了,如果不想修改的話就可以配置 rules 把該條規(guī)則給關閉掉。
其實在團隊開發(fā)的時候,最好把針對 VSCode 的配置,寫一個文件跟隨著項目,一起提交到遠程倉庫,這樣的話就保證了項目成員都是用的這套配置。比如可以在項目根目錄新建 .vscode/settings.json,然后寫入上面的那串配置內(nèi)容。
在提交前做校驗 pre-commit
以上只是通過 ESLint 自動修復能夠修復的錯誤以及通過 Prettier 進行代碼的格式化,但是在實際開發(fā)的時候難免會遇到無法 fix 的錯誤,可能開發(fā)人員也忘記修改,如果這個時候把代碼提交到遠程倉庫,那就把糟糕的代碼給提交上去了。
那么如何杜絕把糟糕的代碼提交上去呢?可以通過配置 git hooks 的 pre-commit 鉤子來實現(xiàn)這個目的。主要是利用了 husky[17] 和 lint-staged[18] 這 2 個包。husky 就是用來配置 git hooks 的,而 lint-staged 則是對拿到的 staged 文件進行處理,比如執(zhí)行 npx eslint --fix 進行代碼校驗。
具體操作步驟如下:
1、執(zhí)行以下命令:
- npx mrm lint-staged
會自動安裝 lint-staged 和 husky 并且在 package.json 里寫入 lint-staged。
“注意:mrm 是一個自動化工具,它將根據(jù) package.json 依賴項中的代碼質(zhì)量工具來安裝和配置 husky 和 lint-staged,因此請確保在此之前安裝并配置所有代碼質(zhì)量工具,如 Prettier 和 ESlint。
如果上面順利會在 package.json 里寫入 lint-staged,可以自行修改讓它支持 .vue 文件的校驗:
- {
- "lint-staged": {
- "*.{js,vue}": "eslint --cache --fix"
- }
- }
2、啟動 git hooks
- npx husky install
經(jīng)過上面的命令后,v6 版本的 husky 會在項目根目錄新建一個 .husky 目錄。如果是 v4 版本的則會寫入到 package.json 里。
3、創(chuàng)建 pre-commit 鉤子
- npx husky add .husky/pre-commit "npx lint-staged"
到這里后,git commit 前自動執(zhí)行代碼校驗和修復的功能就算完成了。然后你可以試試修改文件,然后提交試試。
總結(jié)
這篇文章比較長,前前后后講了很多代碼校驗的東西,現(xiàn)在我們來梳理下。
首先用 ESLint 來做代碼校驗,它自帶的 ruels 能提供 2 種類型的校驗,分別是代碼錯誤校驗和代碼格式校驗,而 ESLint 本身的核心工作其實就是校驗和修復錯誤的代碼,而對格式化的規(guī)則提供的不多。
所以如果想要對代碼格式化進行一個更加精細的配置則需要借助 Prettier,因為它是只負責風格的管控,所以用它再適合不過了。但是如果把 ESLint 和 Prettier 結(jié)合起來一起使用的話,就可能會出現(xiàn)規(guī)則的沖突了,畢竟它們兩者都會對風格進行處理,所以這個時候就可以通過 eslint-config-prettier 這個擴展來把沖突的規(guī)則進行關閉,這個擴展不僅可以關閉和 ESLint 內(nèi)置規(guī)則的沖突,還可以關閉實際項目中引用到的擴展規(guī)則的沖突,比如和 Vue、React、TypeScript、Flow 的沖突。
在把 ESLint 和 Prettier 結(jié)合的時候,我們希望讓 ESLint 來檢查代碼錯誤,而 Prettier 校驗代碼風格,那么這個時候其實是有 2 個任務的,需要用 2 條命令來處理的。但是有了 eslint-plugin-prettier 這個插件后就可以很方便的把它們結(jié)合起來,當需要校驗代碼錯誤的時候 ESLint 自動會給你校驗,當然前提是 VSCode 里必須按照 ESLint 插件,而當需要校驗代碼風格的時候 ESLint 就會調(diào)用 Prettier 的能力進行代碼風格的檢查。
文章的后面分別又細說了 EditorConfig 和提交代碼前校驗的處理,這里就不多講了。
看到這里希望你對代碼校驗和規(guī)范有一個新的認識,不過我最希望的是你能夠自己動手為你的項目配置一套校驗規(guī)則。
參考資料
[1]Esprima:
http://esprima.org/
[2]Espree:
https://github.com/eslint/espree
[3]@babel/eslint-parser:
https://github.com/babel/babel/tree/main/eslint/babel-eslint-parser
[4]@typescript-eslint/parser:
https://github.com/typescript-eslint/typescript-eslint
[5]Specifying Environments:
https://eslint.org/docs/user-guide/configuring/language-options#specifying-environments
[6]react .eslintrc.js:
https://github.com/facebook/react/blob/master/.eslintrc.js
[7]eslint-config-standard:
https://github.com/standard/eslint-config-standard
[8]eslint-config-prettier:
https://github.com/prettier/eslint-config-prettier
[9]eslint-config-alloy:
https://github.com/AlloyTeam/eslint-config-alloy
[10]ESLint plugins:
https://eslint.org/docs/user-guide/configuring/plugins#configuring-plugins
[11]ESLint配置文件.eslintrc參數(shù)說明:
https://gist.github.com/rswanderer/29dc65efc421b3b5b0442f1bd3dcd046
[12]webpack .eslintrc.js:
https://github.com/webpack/webpack/blob/master/.eslintrc.js
[13]eslint-plugin-vue:
https://eslint.vuejs.org/user-guide/#faq
[14]prettier options:
https://prettier.io/docs/en/options.html#print-width
[15]Trailing commas:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Trailing_commas#trailing_commas_in_functions
[16]EditorConfig:
https://editorconfig.org/
[17]husky:
https://typicode.github.io/husky/#/?id=install
[18]lint-staged:
https://github.com/okonet/lint-staged