繼 從零開始配置 react + typescript(一):dotfiles 介紹了一些最早配置的 dotfiles,本篇將繼續介紹 lint 工具 eslint
,stylelint
,代碼格式化工具 prettier
,用 husky
+ lint-staged
來實現每次 commit 時只 lint 修改過的代碼,以及使用 commitlint
來規範化 commit message。javascript
項目地址:react-typescript-boilerplatecss
Find and fix problems in your JavaScript codehtml
其實社區有不少的 lint 工具,例如 eslint
, stylelint
, tslint
, htmllint
, markdownlint
等。lint 工具一方面能夠幫助維護團隊成員保持統一,良好的代碼風格,另外一面能夠幫助咱們檢測出代碼的壞味道,下降 bug 的產生的可能性,提升代碼質量。須要指出的是:lint 工具備必定的格式化能力,可是主要功能不是負責格式化代碼,格式化代碼應該交給專門的格式化工具。 咱們這個項目就將準備使用 prettier
進行代碼格式化。前端
由於是打算使用 TypeScript 來編寫 react,因此要選擇一款支持 TypeScript 的 lint 工具,最流行的支持 TypeScript 的 lint 工具備倆,tslint
和 eslint
。去年 2019 年 2 月份 tslint
團隊就宣佈了廢棄 tslint,轉而將維護一系列將 TypeScript 集成到 ESLint
的工具。具體能夠看這個 issue 和這篇博客:TSLint in 2019。vue
2020 年我以爲新項目沒有任何理由還去選擇 tslint
,eslint
的 TypeScript 插件已經算是比較成熟了,雖然仍是有挺多的 bug。java
其實前端絕大多數構建工具都是用 node 編寫模塊來提供 API,有些也會提供命令行工具,本質上就是解析用戶輸入調用 node API,而後還能夠經過配置文件來配置選項,集成插件,而且配置還能夠經過 npm 包來共享。node
eslint
也不例外,配置 eslint
建議使用 eslint
命令行工具提供的交互式配置生成器。不少包既能夠全局安裝,也能夠本地安裝,咱們選擇本地安裝,由於你沒辦法確保別人開發這個項目的時候也全局安裝了,並且這樣還能夠保證都是使用同一版本。react
安裝 eslint
:webpack
# -D 參數表示開發依賴 yarn add eslint -D 複製代碼
調用 eslint
自帶的配置生成器:git
npx eslint --init
複製代碼
npx
是 npm 5.2 自帶的一個命令,x
就是和文件類型描述符的那個 x
同樣表示 execute
執行嘛。若是本地安裝了就會用本地的 eslint
,沒安裝就去找全局的,全局再沒有就在臨時目錄下載 eslint
,用完就刪。用起來比 npm scripts 還方便,傳參數不用像 npm scripts 同樣要在參數前加 --
。執行上面的 eslint
初始化命令後會詢問你一系列的問題,關於每個問題的詳細說明能夠看一下這篇文章 Setting up ESLINT in your JavaScript Project with VS Code,這篇文章說的很細。
How would you like to use ESLint?
咱們選擇第三條:To check syntax, find problems, and enforce code style
,選擇其它幾條就不會問咱們是否選擇 Google,Airbnb 仍是 Standard 風格了
What type of modules does your project use?
咱們選擇 JavaScript modules (import/export)
,包括 webpack 配置等 node 腳本咱們都將使用 ts 來編寫,因此選擇 esm
Which framework does your project use?
顯然選擇 react
Does your project use TypeScript?
這一步必定要選 Y,只有告訴初始化器咱們使用 TypeScript,它纔會幫助咱們配置好 TypeScript 的 ESLint parser,相關的 plugins, 以及其它配置
Where does your code run?
這裏咱們 browser 和 node 兩個都選上,由於咱們還要編寫一些 node 代碼
How would you like to define a style for your project?
咱們選第一個 Use a popular style guide
Which style guide do you want to follow?
選擇 Airbnb(愛彼迎)的代碼風格
What format do you want your config file to be in?
咱們選擇最靈活的配置方式:javascript,雖然 js 格式的配置文件比 json 格式的更靈活,可是 js 格式無法使用 VSCode 提供的 JSON validate 功能。
Would you like to install them now with npm?
選擇 Y,當即安裝依賴。雖然咱們用的是 yarn,不該該使用 npm 安裝依賴,用 npm 安裝依賴還會生成對咱們沒有用 package-lock.json
。package.lock.json
和 yarn.lock
同樣都是用來鎖定依賴版本的。之因此這裏選擇當即安裝依賴是由於你若是不當即安裝依賴,後面你想再用 yarn 安裝依賴的時還要去查一下安裝哪幾個依賴,我以爲很麻煩。
安裝完以後,把 node_modules
, package-lock.json
, yarn.lock
都刪掉,使用 yarn 從新安裝依賴,再升級到最新版本:
# 安裝依賴 yarn # 升級到最新版本 yarn upgrade --latest 複製代碼
經過 eslint 自帶的配置生成器咱們生成了 .eslintrc.js
:
// 格式化後的 .eslintrc.js module.exports = { env: { browser: true, es6: true, node: true, }, extends: ['plugin:react/recommended', 'airbnb'], globals: { Atomics: 'readonly', SharedArrayBuffer: 'readonly', }, parser: '@typescript-eslint/parser', parserOptions: { ecmaFeatures: { jsx: true, }, ecmaVersion: 2018, sourceType: 'module', }, plugins: ['react', '@typescript-eslint'], rules: {}, }; 複製代碼
能夠看到相對於非 TypeScript 項目,使用 @typescript-eslint/parser
替換掉了默認的 parser,並添加了 @typescript-eslint
插件。
咱們先作如下修改:
查看 eslint-config-airbnb 的說明,裏面提到,若是要開啓 react hooks 檢查,須要添加 "extends": "airbnb/hooks"
到 .eslintrc.js
修改 parserOptions.ecmaVersion
爲 2020,爭作新時代的弄潮兒 😂
查看 @typescript-eslint/eslint-plugin 文檔,裏面提到咱們能夠經過添加 extends: 'plugin:@typescript-eslint/recommended'
來開啓它推薦的一些 rules。
爲了讓 eslint-plugin-import
可以正確解析 ts
, tsx
, json
後綴名,咱們還需指定容許的後綴名,添加 setttings
字段,加入如下配置:
// .eslintrc.js { settings: { 'import/resolver': { node: { // 指定 eslint-plugin-import 解析的後綴名 extensions: ['.ts', '.tsx', '.js', '.json'], }, }, }, } 複製代碼
爲了讓 eslint-plugin-import
可以正確解析 tsconfig.json
中的 paths
映射,咱們須要安裝 eslint-import-resolver-typescript
:
yarn add eslint-import-resolver-typescript -D
複製代碼
修改 settings
字段:
// .eslintrc.js { settings: { 'import/resolver': { typescript: { // 配置 eslint-import-resolver-typescript 讀取 tsconfig.json 的路徑 // 目前用不着,先註釋掉 // directory: [resolve('./src/tsconfig.json'), resolve('./scripts/tsconfig.json')], }, }, }, } 複製代碼
添加一些社區中優秀的 eslint 插件:
yarn add eslint-plugin-eslint-comments eslint-plugin-promise eslint-plugin-unicorn -D
複製代碼
eslint-plugin-eslint-comments 是用於 lint eslint 指令註釋,例如檢測出無用的 eslint-disable 註釋。eslint-plugin-promise 按照最佳實踐 lint 你的 promise 代碼,eslint-plugin-unicorn 是 sindresorhus 大佬開發的一個 eslint 插件,提供了循環依賴檢測,文件名大小寫風格約束等很是實用的規則集合。
在個人使用中我發現,目前 eslint-plugin-import
和 TypeScript
搭配仍是存在不少的 bug,其中的一個不能忍的 bug 就是import/extensions
這個規則不能正確處理文件後綴名:
去 eslint-plugin-import github issue 搜索關鍵字 import/extensions typescript
能夠搜到不少相關的 issues。目前我採用的解決方案是修改 import/extension
的規則配置:
'import/extensions': [
'2,
'ignorePackages',
{
ts: 'never',
tsx: 'never',
json: 'never',
js: 'never'
},
],
複製代碼
另一個要提的 bug 就是這個 issue: no-useless-constructor: Cannot read property 'body' of null,簡單來講就是目前在 eslint 搭配 typescript 相關插件時,若是 .d.ts
聲明文件中若是使用了 constructor
就會報這個錯。例如:
declare module 'size-plugin' { import { Plugin } from 'webpack'; interface SizePluginOptions { writeFile?: boolean; } class SizePlugin extends Plugin { // 使用了 constructor 就報錯:no-useless-constructor: Cannot read property 'body' of null constructor(options?: SizePluginOptions); } export = SizePlugin; } 複製代碼
目前我採用的解決辦法時是添加下面兩個規則:
rules: { 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', }, 複製代碼
針對 .d.ts
文件咱們還須要要禁用一些規則,咱們後續會在 script
文件夾中實現和 webpack 相關的 node 腳本,針對這個文件夾也調整一些規則:
// .eslintrc.js { overrides: [ { files: ['**/*.d.ts'], rules: { 'import/no-duplicates': OFF, }, }, { files: ['scripts/**/*.ts'], rules: { 'import/no-extraneous-dependencies': OFF, }, }, ], } 複製代碼
其它一些我的習慣的規則調整我就不提了,讀者能夠直接去看最終的配置:.eslintrc.js。
目前這個配置還存在一些問題,例如不少 rules 會和 prettier
衝突,後面咱們會一一解決這些問題。
A mighty, modern linter that helps you avoid errors and enforce conventions in your styles
對於 stylelint,我通常都是直接參考 ant design 的 stylint 配置。添加 .stylelintrc.json
到項目根路徑,copy 過來簡單修改一下,:
// .stylelintrc.json { "extends": [ "stylelint-config-standard", "stylelint-config-rational-order", "stylelint-config-prettier" ], "plugins": [ "stylelint-order", "stylelint-declaration-block-no-ignored-properties", "stylelint-scss" ], "rules": { "comment-empty-line-before": null, "declaration-empty-line-before": null, "function-name-case": "lower", "no-descending-specificity": null, "no-invalid-double-slash-comments": null }, // 加 "**/typings/**/*" 的緣由:https://github.com/stylelint/vscode-stylelint/issues/72 "ignoreFiles": ["node_modules/**/*", "src/assets/**/*", "dist/**/*", "**/typings/**/*"] } 複製代碼
src/assets
文件夾準備用來保存一些資源文件,例如第三方的 css 庫,並不須要 lint。VSCode 的 stylelint 插件目前有個 bug,默認竟然會 lint .d.ts
文件而後報錯,因此我也添加了 "**/typings/**/*"
來忽略 .d.ts
文件:
根據上面的配置文件,咱們須要安裝對應的 npm 包:
yarn add stylelint stylelint-config-standard stylelint-config-rational-order stylelint-config-prettier stylelint-order stylelint-declaration-block-no-ignored-properties stylelint-scss -D
複製代碼
和 eslint 同樣,會與 prettier 存在衝突。
An opinionated code formatter
opinionated
能夠理解爲 專斷專行
,自覺得是
,其實就是說這個格式化器(formatter)不給用戶作選擇,就按照一套社區共識,最佳實踐,最好看的的代碼風格來格式化。具體表現就是提供的選項不多,我數了一下總共恰好 20 個選項。
首先咱們得安裝 prettier
:
yarn add prettier -D
複製代碼
添加 .prettierrc
到項目根路徑:
{ "trailingComma": "all", "tabWidth": 4, "semi": true, "singleQuote": true, "endOfLine": "auto", "printWidth": 100, "overrides": [ { "files": "*.md", "options": { "tabWidth": 2 } } ] } 複製代碼
簡單說明下一些選項這樣配置的緣由:
"trailingComma": "all"
,支持在函數參數中也插入逗號
"semi": true
,我的習慣
"singleQuote": true,
,我的習慣,少敲一下 shift 難道很差嗎?
"endOfLine": "auto"
,和 editorconfig
同樣,按照操做系統默認的換行符來就好了
"printWidth": 100
,我以爲默認的最大行寬 80 過短了,浪費編輯器空間
之因此設置 markdown 文件格式化 "tabWidth": 2
,是目前 prettier 在格式化 markdown 文件時,會在無序列表中插入多餘的空格
正常的無序列表應該格式化成:
- 1 - 2 - 3 複製代碼
可是不配置 tabWidth 的話, prettier 會格式化成:
- 1
- 2
- 3
複製代碼
巨醜 😤
這部份內容強烈建議先閱讀 prettier
官方文檔 Integrating with Linters 部分,官方文檔每每是更新最及時,也是最權威的。
咱們知道 lint 工具是用來檢查代碼風格的, prettier 是用來格式化代碼的。想一想看,若是 prettier 設置縮進爲 4 個空格,而咱們配置的 eslint 是要求縮進爲 2 個空格,這確定會致使咱們格式化代碼以後,eslint 會報縮進錯誤。
這部份內容就是爲了解決 linters 規則和 prettier 的衝突問題,其實,原理很簡單,就是禁用掉那些會和 prettier 格式化起衝突的規則。
安裝 eslint 插件 eslint-config-prettier
,這個插件會禁用全部會和 prettier 起衝突的規則。
yarn add eslint-config-prettier -D
複製代碼
添加 'prettier'
,'prettier/react'
,'prettier/@typescript-eslint'
到extends
配置:
// .eslintrc.js { extends: [ 'airbnb', 'airbnb/hooks', 'plugin:eslint-comments/recommended', 'plugin:import/typescript', 'plugin:react/recommended', 'plugin:@typescript-eslint/recommended', 'plugin:unicorn/recommended', 'prettier', // 專門支持了 eslint-plugin-react 'prettier/react', // 專門支持了 @typescript-eslint/eslint-plugin 'prettier/@typescript-eslint', ], } 複製代碼
這裏注意要把 prettier
放最後面,由於這樣才能讓 prettier
有機會禁用前面全部的 extends
中配置的會起衝突的規則。
stylelint 也是同樣,先安裝插件 stylelint-config-prettier
:
yarn add stylelint-config-prettier -D
複製代碼
再將 "stylelint-config-prettier"
添加到 extends
數組最後面:
// .stylelintrc.json { "extends": [ "stylelint-config-standard", "stylelint-config-rational-order", "stylelint-config-prettier" ], } 複製代碼
Run linters on git staged files
咱們每次提交代碼都要對代碼先進行 lint 和格式化,確保團隊的代碼風格統一。爲了達到每次 lint 和格式化時只處理咱們修改了的代碼,也就是保存在 git stage 區(暫存區)的代碼。社區比較流行的方案有倆:
咱們選擇使用 lint-staged
,由於 pretty-quick
功能單一,只是提供了 prettier 格式化 stage 區代碼的功能,無法配 eslint 和 stylelint 使用,還不能經過配置文件來配置。lint-satged 更靈活,經過它咱們能夠同時配置 eslint
,stylelint
,prettier
。
爲了達到在咱們每次 commit 的時候,都自動 lint 和格式化,咱們須要給 git commit 掛個鉤子,使用 husky 能夠很輕鬆的給 git 配置鉤子。
先安裝 husky 和 lint-staged:
yarn add husky lint-staged -D
複製代碼
在 package.json 配置 git commit 時的鉤子操做:
// package.json { "husky": { "hooks": { // 在執行 git commit 調用 lint-staged 命令,lint-staged 會讀取 package.json 中 lint-staged 的配置 "pre-commit": "lint-staged" } }, } 複製代碼
再在 package.json 中 "ling-staged"
字段配置 lint-staged:
// package.json { "lint-staged": { // 對於 ts,tsx,js 文件調用 eslint "*.{ts,tsx,js}": [ "eslint -c .eslintrc.js" ], // 對於 css,less,scss 文件調用 stylelint "*.{css,less,scss}": [ "stylelint --config .stylelintrc.json" ], // prettier 支持不少類型文件的格式化 "*.{ts,tsx,js,json,html,yml,css,less,scss,md}": [ "prettier --write" ] }, } 複製代碼
prettier 的 --write 參數是幹嗎用的呢?舉個 🌰 來講,命令行調用 prettier a.js
默認只會輸出格式化後的代碼到控制檯,不會修改原文件,加上 --write
纔會將格式化後的代碼寫到 a.js
。須要注意的一點是,可能大家看別人的教程或者一些項目中他們配置 lint-staged
還加了一個 git add
步驟,而後控制檯會有警告:
⚠ Some of your tasks use
git add
command.
緣由很簡單:lint-staged 從 V10 版本開始,任何被修改了的原 staged 區的文件都會被自動 git add,因此咱們不須要本身添加 git add 。
commitlint
helps your team adhering to a commit convention. By supporting npm-installed configurations it makes sharing of commit conventions easy.
commitlint 是一個用來 lint commit message 的工具。看官網的例子:
我知道有些人提交代碼喜歡直接來三個點 ...
,這是很很差的習慣,這樣你就徹底沒有利用到 commit message,很不利於項目管理。規範化的編寫 commit message 有不少好處,能夠方便咱們檢索提交歷史,配合 conventional-changelog 直接生成 changelog,關聯 github issue 等。
咱們能夠經過 husky
+ commlint
實如今 commit 的時候先檢查 commit message 的規範性,若是不符合規範直接終止 commit。
安裝須要的依賴:
yarn add @commitlint/cli @commitlint/config-conventional -D
複製代碼
@commitlint/config-conventional 是 commitlint 官方推薦的一個 angular 風格的 commitlint 配置,提供了少許的 lint 規則,相似於 eslint 的 extend。
它默認支持的提交類型爲:
["build", "ci", "chore", "docs", "feat", "fix", "perf", "refactor", "revert", "style", "test"] 複製代碼
添加 commlint 的配置到項目根目錄的 .commitlintrc.js
:
// .commitlintrc.js module.exports = { extends: ['@commitlint/config-conventional'], rules: { 'type-enum': [ 2, 'always', // 比默認值多了個 deps,用於表示依賴升級,降級,新增等提交 ['build', 'ci', 'chore', 'deps', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'style', 'test'], ], }, }; 複製代碼
添加 git commit-msg
鉤子:
// package.json { "husky": { "hooks": { "pre-commit": "lint-staged", "commit-msg": "commitlint -c .commitlintrc.js -E HUSKY_GIT_PARAMS" } }, } 複製代碼
當調用 commit-msg
鉤子的時候,環境變量 HUSKY_GIT_PARAMS
會被臨時設置爲保存 commit messsge 的文件的路徑,而後 commitlint
就會去 lint 這個文件中的 commit message。
若是你想在命令行中交互式的編輯 commit message,能夠了解一下 commitizen ,咱們這個項目就不配了,主要仍是以爲要配置的話就要根據具體的業務去配,咱們這個通用目的的模板項目就算了。我看了一下 angular
和 vue-next
lint commit message 的作法,它們 commitlint 和 commitizen 倆都沒配,只是在 git commit-msg
時調用了下 node 腳本校驗 commit message 。
咱們接着再配置自動生成 changelog,本地安裝 conventional-changelog-cli
:
yarn add conventional-changelog-cli -D
複製代碼
添加一個 npm script:
// package.json "scripts": { "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s" } 複製代碼
這樣咱們就能夠經過 npm run changelog
生成 angular 風格的 changelog 了,conventional-changelog
會讀取提交歷史中 fix, feat 等 type 的 commit message 自動生成 changelog。
咱們接着討論一個使用了 commilint 後如何插入 emoji 的問題,咱們知道 commit message 的格式是這樣的:
// 整行叫 header <type>(<scope>): <subject> // 空一行 <body> // 空一行 <footer> // 舉個例子,某次提交的 commit message 是:feat(component): add component Navbar // feat 是 type // component 是 scope // 'add component Navbar' 是 subject // 這裏沒有 body 和 footer 複製代碼
咱們知道 git emoji 的格式是:
:emoji_string:
複製代碼
若是你使用下面的帶 emoji 的 commit message 提交:
git commit -m ':bug: fix: xxx' 複製代碼
commitlint 等工具在解析的時候應該是將第一個冒號以前的內容解析爲 type,也就是說會把 emoji 左邊冒號以前的內容解析爲 type,那這樣解析的話 type 就是空字符串了,因此使用上面的 commit message 提交會報錯說你沒有填寫 type。
若是不修改 commilint 的 type 配置是沒法經過 commitlint 的,解決辦法之一是添加一個 type :bug: fix
,可是這樣的話 conventional-changelog-cli
不會將 commit mesage 提取到 changelog,它只認 fix: xxx
不認 :bug: fix: xxx
。所以,在當前配置下,咱們若是要插入 emoji,建議使用下圖的方式,雖然我以爲這樣很差看,但目前來講是比較折中的方案。
git commit -m "chore: :memo: improve docs and config json" 複製代碼
添加幾個經常使用用於 lint 的 npm scripts:
{ "scripts": { "lint": "yarn run lint-eslint && yarn run lint-stylelint", "lint-eslint": "eslint -c .eslintrc.js --ext .ts,.tsx,.js {src,scripts}/**/*.{ts,tsx,js}", "lint-stylelint": "stylelint --config .stylelintrc.json src/**/*.scss --syntax scss", } } 複製代碼
能夠看到我配置 eslint 和 stylelint 的 script 是用 前綴-參數
的形式,有些項目配置帶參數的 script 名是用 前綴:參數
的形式,也就是用冒號作分隔符。我以爲那樣很差,由於有些工具支持 yarn:scriptName
的形式來執行 npm scripts,例如 concurrently。
假設你有多個 npm scripts,分別是:yarn:watch-node
,yarn:watch-node
,yarn:watch-css
,這個工具支持一條命令來並行執行它們:
concurrently yarn:watch-node yarn:watch-js yarn:watch-css
複製代碼
那你說若是用冒號來作分隔符,那要寫就是:
concurrently yarn:watch:node yarn:watch:js yarn:watch:css
複製代碼
看起來就很迷,不瞭解的人可能還覺得後面的冒號也是 concurrently
的參數呢,因此表示帶參數的 npm script 不要用冒號作分隔符。
最後再來一發 yarn upgarde --latest
,養成天天升級依賴的好習慣,避免之後同時升級不少依賴出了都搞不清楚是哪一個依賴升級致使的。不過公司的項目千萬別這樣搞,容易致使出 bug 連續加班。
到這裏,從零開始配置 react + typescript 系列第二篇算是差很少了,再一次提交代碼:
git add -A git commit -m 'build: integrate eslint, stylelint, prettier, lint-staged, commi tlint' # 上次 push 的時候使用 -u 參數關聯了 master 分支和 github 遠程倉庫,這裏就能夠直接 push git push 複製代碼
第二篇到此結束,第三篇關於 webpack 配置的文章將是四篇中乾貨最多,估計也是最長的一篇。將介紹使用 TypeScript
來編寫 express + webpack devServer 中間件
做爲 devServer,集成一些實用和酷炫的 webpack 插件,優化 babel 配置,生產環境打包優化等內容。
要想了解更多細節,建議直接看源碼,項目地址:react-typescript-boilerplate。若是以爲本文對你有用,不妨賞顆 star 😁。對本文內容有疑問或者有什麼改進的地方歡迎經過評論和郵件交流。
本文爲原創內容,首發於我的博客,轉載請註明出處。