继 从零开始配置 react + typescript(一):dotfiles 介绍了一些最早配置的 dotfiles,本篇将继续介绍 lint 工具 eslint,stylelint,代码格式化工具 prettier,用 husky + lint-staged 来实现每次 commit 时只 lint 修改过的代码,以及使用 commitlint 来规范化 commit message。javascript
Find and fix problems in your JavaScript codecss
其实社区有不少的 lint 工具,例如 eslint, stylelint, tslint, htmllint, markdownlint 等。lint 工具一方面能够帮助维护团队成员保持统一良好的代码风格,并且能够定制本身团队的规则集合,另外一面能够帮助咱们检测出代码的坏味道,下降 bug 的产生的可能性,甚至提升代码的执行效率,提升代码质量。须要指出的是:lint 工具备必定的格式化能力,可是主要功能不是负责格式化代码,格式化代码应该交给专门的格式化工具。 咱们这个项目就将准备使用 prettier 进行代码格式化。html
由于是打算使用 TypeScript 来编写 react,TypeScript 的 lint 工具备俩,tslint 和 eslint。去年 2019 年 2 月份 tslint 团队就宣布了要废弃 tslint,转而将维护一系列将 TypeScript 集成到 ESLint 的工具。具体能够看这个 issue 和这篇博客:TSLint in 2019。前端
2020 年我以为新项目没有任何理由还去选择 tslint,eslint 的 TypeScript 插件已经算是比较成熟了,虽然仍是有挺多的 bug,后面会提到一些 bug。vue
其实前端绝大多数构建工具都是用 node 编写模块来提供 API,有些也会提供命令行工具,本质上就是解析用户输入调用 node API,而后能够经过配置文件来配置选项和集成插件。java
eslint 也不例外,配置 eslint 建议使用 eslint 命令行工具提供的交互式配置生成器。不少包既能够全局安装,也能够本地安装,咱们选择本地安装,由于你没办法确保别人开发这个项目的时候也全局安装了,并且这样还能够保证都是使用同一版本。node
安装 eslint:react
# -D 参数表示开发依赖
yarn add eslint -D
复制代码
调用 eslint 自带的配置生成器:webpack
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,这篇文章说的很细。git
How would you like to use ESLint?
咱们选择第三条,选择其它几条就不会问咱们是否选择 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
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,修改 extends
:
{
extends: ['plugin:react/recommended', 'airbnb', 'airbnb/hooks','plugin:@typescript-eslint/recommended']
}
复制代码
为了让 eslint-plugin-import
可以正确解析 ts
, tsx
, json
后缀名,咱们还需指定容许的后缀名,添加 setttings
字段,加入如下配置:
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
字段:
settings: {
'import/resolver': {
// ...
// 配置 eslint-import-resolver-typescript 读取 tsconfig.json 的路径
typescript: {
directory: [resolve(__dirname, './src/tsconfig.json'), resolve(__dirname, './scripts/tsconfig.json')],
},
},
}
复制代码
在个人使用中我发现,目前 eslint-plugin-import
和TypeScript
搭配仍是存在不少的 bug,其中的一个不能忍的 bug 就是import/extensions
这个规则不能正确处理文件后缀名:
去 eslint-plugin-import github issue 搜索关键字 import/extensions typescript
能够搜到不少相关的 issues。目前我采用的解决方案是修改 import/extension
的规则配置:
'import/extensions': [
'error',
'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 就报错
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: {
// 多个外部模板声明中导入同一个模块会报错的 bug,例如多个 webpack 插件的声明文件中都导入 webapck
'import/no-duplicates': 'off',
'max-classes-per-file': 'off',
},
},
{
files: ['scripts/**/*.ts'],
rules: {
// node 脚本是须要使用外部依赖的
'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"],
"plugins": ["stylelint-order", "stylelint-declaration-block-no-ignored-properties"],
"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
},
"ignoreFiles": ["node_modules/**/*", "src/assets/**/*", "dist/**/*", "**/typings/**/*"]
}
复制代码
我去掉了 stylelint-config-prettier
插件(后面会加上),修改了 function-name-case
规则,最后将 ignoreFiles
选项修改成 ["node_modules/**/*", "src/assets/**/*", "dist/**/*", "**/typings/**/*"]
。
src/assets
文件夹准备用来保存一些资源文件,例如第三方的 css 库,并不须要 lint。stylelint 插件目前有个 bug,若是 lint 了 .d.ts
文件在某些状况会报 TypeError: Cannot read property 'buildError' of undefine
的错误,因此我也添加了 "**/typings/**/*"
来忽略 .d.ts
文件。
根据上面的配置文件,咱们须要安装对应的 npm 包:
yarn add stylelint stylelint-config-standard stylelint-config-rational-order stylelint-order stylelint-declaration-block-no-ignored-properties -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": 120,
"overrides": [
{
"files": "*.md",
"options": {
"tabWidth": 2
}
}
]
}
复制代码
简单说明下一些选项这样配置的缘由:
"trailingComma": "all"
,支持在函数参数中也插入逗号
"semi": true
,我的习惯
"singleQuote": true,
,我的习惯,少敲一下 shift 难道很差吗?
"endOfLine": "auto"
,和 editorconfig
同样,按照操做系统默认的换行符来就好了
"printWidth": 120
,我以为默认的最大行宽 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 会报缩进错误。
这部份内容就是为了解决 linter 规则和 prettier 的冲突问题,其实,原理很简单,就是禁用掉掉那些会和 prettier 格式化起冲突的规则。
安装 eslint 插件 eslint-config-prettier
,这个插件会禁用全部会和 prettier 起冲突的规则。
yarn add eslint-config-prettier -D
复制代码
添加 'prettier'
到extends
配置:
// .eslintrc.js
{
extends: ['plugin:react/recommended', 'airbnb', 'airbnb/hooks', 'prettier']
}
复制代码
这里注意要把 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
,由于前者功能单一,只是提供了 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 的配置
"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"
],
// 对于 scss 文件调用 stylelint
"*.scss": [
"stylelint --config .stylelintrc.json"
],
// prettier 支持不少类型文件的格式化
"*.{ts,tsx,js,json,scss,md}": [
"prettier --write"
]
},
}
复制代码
prettier 的 --write 参数是干吗用的呢?举个 🌰 来讲,命令行调用 prettier a.js
默认只会输出格式化后的内容到控制台,不会修改原文件,加上 --write
才会直接格式化 a.js
。须要注意的一点是,可能大家看别人的教程或者一些项目中它们还加了一个 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 的工具。看官网的例子:
我知道有些人提交代码喜欢直接来三个点 ...
,这是很很差的习惯,到时候你想版本回退的话就傻眼了,只能看提交的时间了和 diff 了,很不利于项目管理。规范化的编写 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。
添加 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。
添加 commlint 的配置到项目根目录新建的 .commitlintrc.js
:
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
// 暂时定为默认值,根据本身的需求调整
['build', 'ci', 'chore', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'style', 'test'],
],
},
};
复制代码
若是你想在命令行中交互式的编辑 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。
为了说明一个和 git emoji 相关的问题,咱们先来看一下 ant design 的 github 首页,注意其中 emoji 的位置:
咱们知道 commit message 的格式是这样的:
// 整行叫 header。type 指的是例如: fix, feat, build 等
<type>(<scope>): <subject>
// 空一行
<body>
// 空一行
<footer>
复制代码
能够看到上面图中第二个带 emoji 的 commit message 中的 emoji 是放在 header 最前面。咱们知道 git emoji 的格式是:
:emoji_string:
复制代码
若是你使用下面的带 emoji 的 commit message 提交:
git commit -m ':bug: fix: xxx'
复制代码
commitlint 等工具在解析的时候应该是将第一个冒号以前的内容解析为 type,因此上面的 commit message 提交会报错说你没有填写 type。
若是不修改 commilint 的 type-enum 配置是没法经过 commitlint 的 lint,conventional-changelog-cli
也不会将上面举例的 commit mesage 提取到 changelog,它只认 fix: xxx
不认 :bug: fix: xxx
。所以,在当前配置下,咱们若是要插入 emoji,建议使用上图第一个带 emoji 的方式,虽然我以为这样很差看,但目前来讲是比较折中的方案。
添加几个经常使用用于 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,这个工具支持调用 concurrently yarn:watch-node yarn:watch-js yarn:watch-css
来执行多个 npm scripts。那你说若是用 冒号来作分隔符,那要写就是 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,集成一些实用和装逼的 webpack 插件,集成和优化 antd 等内容,敬请期待...