從零構建前端 Lint 工作流(2020手把手版)

周振超發表於2020-06-09

從零構建前端 Lint 工作流

  • eslint prettier stylelint husky lint-staged typescript eslint-config-alloy 為構建專案程式碼質量保駕護航。
  • 傳承 ESLint 推崇的外掛化、配置化的理念,滿足個性化需求(即讓專業的工具做擅長的事)因此,各位可以選擇自己需要的功能進行整合。
  • 文章只發表于思否(不喜歡到處copy)各位有緣的老鐵,點贊收藏支援一波哈(看到點贊收藏關注,有一種小鹿亂撞的美好)

選擇性閱讀

什麼是程式碼檢查

  • 程式碼檢查主要是用來發現程式碼錯誤、統一程式碼風格。
  • 在 JavaScript 專案中,我們一般使用 ESLint 來進行程式碼檢查,它通過外掛化的特性極大的豐富了適用範圍,搭配 typescript-eslint 之後,甚至可以用來檢查 TypeScript 程式碼。

配置 ESLint

小試牛刀

1、新建一個資料夾,開啟命令列,npm init -y建立package.json
2、安裝依賴npm install --save-dev eslint babel-eslint eslint-config-alloy
3、在專案根目錄下建立一個.eslintrc.js.eslintrc.json的配置檔案:

// .eslintrc.js
module.exports = {
    extends: [
        'alloy',
    ],
};

4、在專案根目錄下建立一個index.js,複製下面內容:

var myName = 'Tom';
console.log(`My name is ${myNane}`);

5、在命令列輸入npx eslint index.js

// eslint 報錯資訊:
✖ 2 problems (2 errors, 0 warnings)
error  Unexpected var, use let or const instead  no-var
error  'myNane' is not defined                   no-undef

6、使用npx eslint index.js --fix自動修復某些規則

// 這時 var 變成了 let
// 還剩下一個無法自動修復的錯誤 
✖ 1 problem (1 error, 0 warnings)
error  'myNane' is not defined  no-undef

配合 TypeScript

1、由於 ESLint 預設使用 Espree 進行語法解析,無法識別 TypeScript 的一些語法,故我們需要安裝 @typescript-eslint/parser,替代掉預設的解析器,別忘了同時安裝 typescript:

npm install --save-dev typescript @typescript-eslint/parser

2、接下來需要安裝對應的外掛 @typescript-eslint/eslint-plugin 它作為 eslint 預設規則的補充,提供了一些額外的適用於 ts 語法的規則。

npm install --save-dev @typescript-eslint/eslint-plugin

3、修改配置檔案

module.exports = {
    extends: [
        'alloy',
    ],
    parser: '@typescript-eslint/parser',
    plugins: ['@typescript-eslint'],
    rules: {
        // 禁止使用 var
        'no-var': "error",
        // 優先使用 interface 而不是 type
        '@typescript-eslint/consistent-type-definitions': [
            "error",
            "interface"
        ]
    }
}
  • 以上配置中,我們自定義了兩個規則,其中 no-var是 ESLint 原生的規則(我們剛剛已經用到了這個規則,它被包含在alloy中,此處會覆蓋),@typescript-eslint/consistent-type-definitions 是 @typescript-eslint/eslint-plugin 新增的規則
  • 規則的取值一般是一個陣列(上例中的 @typescript-eslint/consistent-type-definitions),其中第一項是 off、warn 或 error 中的一個,表示關閉、警告和報錯。後面的項都是該規則的其他配置。
  • 如果沒有其他配置的話,則可以將規則的取值簡寫為陣列中的第一項(上例中的 no-var)。
關閉、警告和報錯的含義如下:
  • 關閉:禁用此規則
  • 警告:程式碼檢查時輸出錯誤資訊,但是不會影響到 exit code
  • 報錯:發現錯誤時,不僅會輸出錯誤資訊,而且 exit code 將被設為 1(一般 exit code 不為 0 則表示執行出現錯誤)

4、新建index.ts檔案:

var myName = 'Tom';
console.log(`My name is ${myNane}`);
console.log(`My name is ${myName.toStrng()}`);
type Foo = {};

5、在命令列輸入npx eslint index.ts,如下可以看到報錯資訊以及可修復項

  1:1   error  Unexpected var, use let or const instead  no-var
  2:27  error  'myNane' is not defined                   no-undef
  4:6   error  Use an `interface` instead of a `type`    @typescript-eslint/consistent-type-definitions

✖ 3 problems (3 errors, 0 warnings)
  2 errors and 0 warnings potentially fixable with the `--fix` option.

指令碼命令檢查整個專案

1、根目錄新建一個src資料夾,將我們的index.jsindex.ts放進去
2、在package.json中的scripts新增:

{
    "scripts": {
        // 因為eslint不是全域性安裝的,所以要使用npx
        "lint": "npx eslint src --ext .js,.ts,tsx"
        // eslint 預設不會檢查 .ts 字尾的檔案,所以需要加上引數 --ext .ts
    }
}

3、然後npm run lint就可以看到src下所有指定字尾檔案的報錯資訊

推薦使用 AlloyTeam 的配置

  • 上面手把手完成了ESLint的配置過程
  • 有一定經驗的推薦直接使用AlloyTeam實現可自定義擴充的ESLint規則
  • AlloyTeam/eslint-config-alloy已經幫我們整合了各種技術棧

1、安裝技術棧相關依賴

// Eslint
npm install --save-dev eslint babel-eslint eslint-config-alloy
// React
npm install --save-dev eslint babel-eslint eslint-plugin-react eslint-config-alloy
// Vue
npm install --save-dev eslint babel-eslint vue-eslint-parser eslint-plugin-vue eslint-config-alloy
// TypeScript
npm install --save-dev eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-config-alloy
// TypeScript React
npm install --save-dev eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react eslint-config-alloy

2、配置.eslintrc.js檔案

/* .eslintrc.js */
module.exports = {
    extends: [
        'alloy', // 都需要
        'alloy/vue', //vue專案需要
        'alloy/react', //react專案需要
        'alloy/typescript', //ts專案需要
    ],
    env: {
        // 你的環境變數(包含多個預定義的全域性變數)
        //
        // browser: true,
        // node: true,
        // mocha: true,
        // jest: true,
        // jquery: true
    },
    globals: {
        // 你的全域性變數(設定為 false 表示它不允許被重新賦值)
        //
        // myGlobal: false
    },
    rules: {
        // 自定義你的規則
    }
};

3、接下來就可以直接用eslint命令檢查檔案了
4、這樣就引入了alloy團隊的lint規則了,然後可以用rules覆蓋你不爽的規則,直接採用開源規則是為了避免重複造輪子,你也可以選擇別的團隊,或者自己定義一套

VSCode 整合 ESLint 檢查

在編輯器中整合 ESLint 檢查,可以在開發過程中就發現錯誤,甚至可以在儲存時自動修復錯誤,極大的增加了開發效率

1、先安裝 ESLint 外掛,開啟 VSCode 點選「擴充套件」按鈕,搜尋 ESLint,然後安裝即可
2、在「檔案 => 首選項 => 設定 => 工作區」中(也可以在專案根目錄下建立一個配置檔案 .vscode/settings.json),新增以下配置:

{
    // VSCode 中的 ESLint 外掛預設是不會檢查 `.vue`、`.ts` 或 `.tsx` 字尾的
    "eslint.validate": [
        "javascript",
        "javascriptreact",
        "vue",
        "typescript",
        "typescriptreact"
    ],
    // 開啟儲存時自動修復
    "editor.codeActionsOnSave": {
        "source.fixAll.eslint": true
    },
    // 指定VSCode用於IntelliSense(智慧感知)的ts版本,將內建版本更換為工作區版本
    "typescript.tsdk": "node_modules/typescript/lib"
}

結合 Prettier 使用

Prettier 是一個程式碼格式化工具,相比於 ESLint 中的程式碼格式規則,它提供了更少的選項,但是卻更加專業。
AlloyTeam 推薦用 Prettier 管理格式化相關的規則,用 ESLint 來檢查它更擅長的邏輯錯誤。

配置 Prettier

1、安裝 Prettier

npm install --save-dev prettier

2、配置 .prettierrc.js 僅供參考:

// .prettierrc.js
module.exports = {
    // 一行最多 100 字元
    printWidth: 100,
    // 使用 4 個空格縮排
    tabWidth: 4,
    // 不使用縮排符,而使用空格
    useTabs: false,
    // 行尾需要有分號
    semi: true,
    // 使用單引號
    singleQuote: true,
    // 物件的 key 僅在必要時用引號
    quoteProps: 'as-needed',
    // jsx 不使用單引號,而使用雙引號
    jsxSingleQuote: false,
    // 末尾不需要逗號
    trailingComma: 'none',
    // 大括號內的首尾需要空格
    bracketSpacing: true,
    // jsx 標籤的反尖括號需要換行
    jsxBracketSameLine: false,
    // 箭頭函式,只有一個引數的時候,也需要括號
    arrowParens: 'always',
    // 每個檔案格式化的範圍是檔案的全部內容
    rangeStart: 0,
    rangeEnd: Infinity,
    // 不需要寫檔案開頭的 @prettier
    requirePragma: false,
    // 不需要自動在檔案開頭插入 @prettier
    insertPragma: false,
    // 使用預設的折行標準
    proseWrap: 'preserve',
    // 根據顯示樣式決定 html 要不要折行
    htmlWhitespaceSensitivity: 'css',
    // 換行符使用 lf
    endOfLine: 'lf'
};

VSCode 整合 Prettier

1、在.vscode/settings.json中新增配置:

{
    // 儲存時自動格式化所有支援檔案 javascript/javascriptreact/typescript/typescriptreact/json/graphql
    "editor.formatOnSave": true,
    "editor.defaultFormatter": "esbenp.prettier-vscode",
}

2、這時我們儲存檔案的時候,已經可以自動格式化了
3、也可以指定格式化檔案型別:

{
    // Set the default
    "editor.formatOnSave": false,
    // Enable per-language
    "[javascript]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode",
        "editor.formatOnSave": true
    }
}

繼續整合 Stylelint

顧名思義,Stylelint幫我們解決類css檔案樣式問題

Stylelint 規則分為三個類別

  • Possible errors: 可以使用stylelint-config-recommended啟用這些規則
  • Stylistic issues: stylelint-config-standard擴充了Possible errors,並啟用此類的規則
  • Limit language features: 其他規則,如果有需要,可以在rules裡面配置
  • 詳盡的配置規則

Stylelint 配置

1、安裝依賴

npm install --save-dev stylelint stylelint-config-standard stylelint-order

2、在專案根目錄中建立一個.stylelintrc.js配置檔案:

module.exports = {
    extends: [
        'stylelint-config-standard',
    ],
    plugins: ['stylelint-order'],
    rules: {
        // ...
    }
};
  • stylelint-config-standardstylelint的推薦配置
  • stylelint-order是 css 屬性排序外掛

3、npx stylelint "**/*.css" 嘗試檢查 css 檔案

支援 SCSS

1、安裝依賴

npm install --save-dev stylelint-config-sass-guidelines stylelint-scss

2、調整.stylelintrc.js配置檔案:

module.exports = {
    extends: [
        'stylelint-config-standard',
        'stylelint-config-sass-guidelines',
    ],
    plugins: ['stylelint-order', 'stylelint-scss'],
    rules: {
        // ...
    }
};

3、更多配置及外掛

VSCode 整合 Stylelint

1、在.vscode/settings.json中新增配置:

{
    "editor.codeActionsOnSave": {
         // 開啟儲存自動修復所有stylelint可修復的選項
        "source.fixAll.stylelint": true
    },
}

2、這時我們儲存檔案的時候,Stylelint已經可以自動修復,但是我們上面讓Prettier負責了所有檔案的格式化,因此可能會導致衝突,倒騰了好久VSCode配置項,效果並不好,比如去除了 css 等檔案的格式化,到.vue內聯<style>依然有問題:

{
    // 去除其他外掛
    "css.validate": false,
    "less.validate": false,
    "scss.validate": false,
    // 編輯器格式化全部交給 Prettiern 配置
    "editor.formatOnSave": true,
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    // 針對某種語言,配置替代編輯器設定,取消自動儲存格式化
    "[css]": {
        "editor.formatOnSave": false
    },
    "[less]": {
        "editor.formatOnSave": false
    },
    "[scss]": {
        "editor.formatOnSave": false
    },
}

3、如上還是會有問題,換個思路,讓Stylelint禁用所有與Prettiern有關的規則,很符合我們整篇文章「各司其職」的思想
4、安裝依賴npm install --save-dev stylelint-config-prettier
5、調整.stylelintrc.js配置檔案:

module.exports = {
    extends: [
        'stylelint-config-standard',
        'stylelint-config-sass-guidelines',
        'stylelint-config-prettier'
    ],
    plugins: ['stylelint-order', 'stylelint-scss'],
    rules: {
        // ...
    }
};

6、至此,我們讓Prettiern負責格式化,讓Stylelint檢查樣式,讓ESLint檢查語法邏輯,完成了整個前端 Lint 工作流

Git 程式碼預檢

  • 上面我們配置了ESLint、Prettier、Stylelint整合了VSCode外掛,實現了錯誤提示和儲存自動修復
  • 然而自動修復的只是小部分,如果團隊成員不按規範,依然可以將不規範的程式碼推送至遠端程式碼庫
  • 我們通過Git 程式碼預檢,一定程度防止不規範的程式碼被提交

實現過程

  1. 待提交的程式碼
  2. git add 新增到暫存區
  3. 執行 git commit(這時進行程式碼預檢)
  4. husky註冊在git pre-commit的鉤子調起 lint-staged
  5. lint-staged 取得所有被提交的檔案依次執行寫好的任務
  6. 如果有錯誤(沒通過ESlint檢查)則停止任務,等待下次commit,同時列印錯誤資訊
  7. 成功提交後,git push推送到遠端庫

什麼是 git hook

  • git hook就是.git資料夾的hooks下的一些鉤子函式,特定時機他們將被呼叫
  • 檢視所有 git 鉤子函式:
cd .git/hooks
ls -l
// 列印如下:
total 96
-rwxr-xr-x  1 zzc  staff   478 10 21  2019 applypatch-msg.sample
-rwxr-xr-x  1 zzc  staff   896 10 21  2019 commit-msg.sample
-rwxr-xr-x  1 zzc  staff  3327 10 21  2019 fsmonitor-watchman.sample
-rwxr-xr-x  1 zzc  staff   189 10 21  2019 post-update.sample
-rwxr-xr-x  1 zzc  staff   424 10 21  2019 pre-applypatch.sample
-rwxr-xr-x  1 zzc  staff  1638 10 21  2019 pre-commit.sample
-rwxr-xr-x  1 zzc  staff  1348 10 21  2019 pre-push.sample
-rwxr-xr-x  1 zzc  staff  4898 10 21  2019 pre-rebase.sample
-rwxr-xr-x  1 zzc  staff   544 10 21  2019 pre-receive.sample
-rwxr-xr-x  1 zzc  staff  1492 10 21  2019 prepare-commit-msg.sample
-rwxr-xr-x  1 zzc  staff  3610 10 21  2019 update.sample
  • .sample為各個鉤子的案例指令碼,可以把sample去掉,直接編寫shell指令碼來執行。
  • 而前端可以用外掛husky與pre-commit,來使鉤子生效。

husky 註冊 git hook

Requires Node >= 10 and Git >= 2.13.0.
  • husky新老版本的配置方式和使用變化較大,老版本請自行升級,詳見 husky

1、安裝 husky

npm install husky --save-dev

2、編輯 package.json 檔案:

{
    "husky": {
        "hooks": {
            "pre-commit": "eslint src/**/*.js"
        }
    },
}

3、嘗試 git commit 提交,就會先執行eslint src/**/*.js,程式碼沒有問題才會被真正提交
4、這樣每次提交程式碼,eslint都會檢查所有檔案,如果報錯過多,一定會崩潰

lint-staged 只 Lint 改動程式碼

lint-staged requires Node.js version 10.13.0 or later.
  • v10.0.0 以後對原始暫存檔案的任何新修改都將自動新增到提交中。如果您的任務以前包含一個git add步驟,請刪除此步驟,同時執行多個git操作通常會導致錯誤,詳見 lint-staged

1、安裝 lint-staged

npm install lint-staged --save-dev

2、新增 package.json 配置:

{
    "lint-staged": {
        "src/**/*.js": "eslint"
    }
}

3、如此husky只負責註冊git hook,後續操作交給lint-staged,只對改動的檔案執行任務,而且可以很方便
地配置多條命令:

{
    "husky": {
        "hooks": {
            "pre-commit": "lint-staged"
        }
    },
    "lint-staged": {
        "src/**/*.js": ["eslint --fix", "prettier --write"]
    }
}

4、如上,我們提交程式碼之前,程式會自動修復eslint配置,格式化prettier配置

幾點建議

  • 建議程式碼提交只做檢查和測試,攔截問題程式碼比較好,還是在儲存時,自動修復eslint、prettier配置,而且大部分還需要手動修復才行
  • 實在緊急,也可通過git commit -m -n "跳過程式碼程式碼預檢"跳過檢查,慎用
  • 和構建有關的包建議使用--save-dev安裝在專案內部
  • 使用VSCode開啟專案,path不要巢狀過深,可能導致Lint工具失效
  • 老版本husky lint-staged配置都放在package.json中,現在eslint prettier husky lint-staged都支援多種字尾配置檔案,建議採用.js統一格式,也方便擴充:

統一配置檔案格式

// .eslintrc.js
module.exports = {
    extends: [
        'alloy',
    ],
};
// .prettierrc.js
module.exports = {
    // 一行最多 100 字元
    printWidth: 100,
    // 使用 4 個空格縮排
    tabWidth: 4,
    // ...
};
// .huskyrc.js
module.exports = {
    'hooks': {
        'pre-commit': "lint-staged"
    }
}
// .lintstagedrc.js
module.exports = {
    "src/**/*.{js,ts}": "eslint"
}
// .stylelintrc.js
module.exports = {
    extends: [
        'stylelint-config-standard',
        'stylelint-config-sass-guidelines',
        'stylelint-config-prettier'
    ],
    plugins: ['stylelint-order', 'stylelint-scss'],
    rules: {
        // ...
    }
};

擴充示例

  • .huskyrc.js
// 陣列方式配置多條命令
const tasks = arr => arr.join(' && ')
module.exports = {
  'hooks': {
    'pre-commit': tasks([
      'npm run lint',
      'npm test'
    ])
  }
}
  • .lintstagedrc.js
module.exports = {
    // 如果超過10個暫存檔案,則在整個儲存庫上執行eslint
    '**/*.js?(x)': (filenames) =>
    filenames.length > 10 ? 'eslint .' : `eslint ${filenames.join(' ')}`,
    "*.css": "stylelint",
    "*.scss": "stylelint --syntax=scss",
    // 對ts檔案執行tsc,但不傳遞任何引數
    '**/*.ts?(x)': () => 'tsc -p tsconfig.json --noEmit'
}

相關文章