通过ESLint和Prettier统一团队代码规范.
字数 3.9k / 16 min 读完 / 阅读随着业务的需求的增加,前端项目也越来越多,项目之间的代码风格也不尽相同。如果一个项目涉及多个同学维护的话,那这个项目的代码风格就会比较糟糕了。而且有的同学本地可能用了格式化插件,所以保存之后会造成整个文件的代码被格式化,其实可能只修改了一行代码。这样在做合并代码 review 时,很容易就会被忽略,也失去了 review 的意义。
另外每个项目的创建时间不同,所以也造成了各个项目之间的 eslint 规范配置也不同。有的项目验证了结尾带“;”,有的项目验证了使用单引号,所以同一个人在开发不同项目时也很难使用一些快捷的格式化插件。还有因为有的项目 eslint 的缺失,也隐藏了很多低级错误在代码中。这无形之中给项目埋下了不定时炸弹。
其实之前也做过一次代码规范统一的事情,其实只是做了一些配置的改变,没有做到一些限制的措施,这样其实提高了开发同学的开发成本,也很难通过开发同学自觉遵守规范,而做到比较好的效果。
接下来我会从以下几个方面来讲讲:
- eslint 包之间的依赖介绍
- 怎么解决,通过 prettier 美化代码格式,通过 eslint 规范代码风格,避免低级 bug
- eslint 版本规范选择,各类项目(vue/vue cli3、rn、react)需要的 eslint 依赖和插件,eslintrc 的配置
- 如何降低各个项目配置 eslint 成本?
- vscode 配置,支持保存时自动格式化
- 避免未格式化代码被提交到代码库,提交前自动执行 fix 和校验
ESLint 生态依赖包
基础
eslint: lint 代码的主要工具,所以的一切都是基于此包
- eslint 可以安装在当前项目中,局部安装:
npm install eslint --save-dev
,使用./node_modules/.bin/eslint *.js
命令本地运行,插件安装在项目目录(推荐) - eslint 可以安装在根目录下,全局安装:
npm install eslint --global
,使用eslint *.js
命令全局运行,所有的插件都是要安装在根目录
- eslint 可以安装在当前项目中,局部安装:
解析器
- Esprima
- Babel-ESLint - 一个对 Babel 解析器的包装,使其能够与 ESLint 兼容。
- @typescript-eslint/parser - 将 TypeScript 转换成与 estree 兼容的形式,以便在 ESLint 中使用。
扩展的配置
eslint-config-airbnb: 该包提供了所有的 Airbnb 的 ESLint 配置,作为一种扩展的共享配置,你是可以修改覆盖掉某些不需要的配置的,该工具包包含了 react 的相关 Eslint 规则(eslint-plugin-react 与 eslint-plugin-jsx-a11y),所以安装此依赖包的时候还需要安装刚才提及的两个插件
eslint-config-airbnb-base: 与上一个包的区别是,此依赖包不包含 react 的规则,一般用于服务端检查。
eslint-config-prettier: 将会禁用掉所有那些非必须或者和 prettier 冲突的规则。这让您可以使用您最喜欢的 shareable 配置,而不让它的风格选择在使用 Prettier 时碍事。请注意该配置只是将规则 off 掉,所以它只有在和别的配置一起使用的时候才有意义。
eslint-config-standard: 是一个标准配置,旨在让所有开发者不需要维护 .eslintrc, .jshintrc, or .jscsrc
插件
eslint-plugin-babel: 和 babel-eslint 一起用的一款插件.babel-eslint 在将 eslint 应用于 Babel 方面做得很好,但是它不能更改内置规则来支持实验性特性。eslint-plugin-babel 重新实现了有问题的规则,因此就不会误报一些错误信息
eslint-plugin-import: 该插件想要支持对 ES2015+ (ES6+) import/export 语法的校验, 并防止一些文件路径拼错或者是导入名称错误的情况
eslint-plugin-promise:promise 规范写法检查插件,附带了一些校验规则。
eslint-plugin-jsx-a11y: 该依赖包专注于检查 JSX 元素的可访问性。
eslint-import-resolver-webpack: 可以借助 webpack 的配置来辅助 eslint 解析,最有用的就是 alias,从而避免 unresolved 的错误
eslint-import-resolver-typescript:和 eslint-import-resolver-webpack 类似,主要是为了解决 alias 的问题
eslint-plugin-react: React 专用的校验规则插件.
eslint-plugin-jest: Jest 专用的 Eslint 规则校验插件.
eslint-plugin-prettier: 该插件辅助 Eslint 可以平滑地与 Prettier 一起协作,并将 Prettier 的解析作为 Eslint 的一部分,在最后的输出可以给出修改意见。这样当 Prettier 格式化代码的时候,依然能够遵循我们的 Eslint 规则。如果你禁用掉了所有和代码格式化相关的 Eslint 规则的话,该插件可以更好得工作。所以你可以使用 eslint-config-prettier 禁用掉所有的格式化相关的规则(如果其他有效的 Eslint 规则与 prettier 在代码如何格式化的问题上不一致的时候,报错是在所难免的了)
@typescript-eslint/eslint-plugin:Typescript 辅助 Eslint 的插件。
Prettier
- prettier:是一个代码格式化工具,相比于 ESLint 中的代码格式规则,它提供了更少的选项,但是却更加专业。
几个工具之间的关系是:prettier 是最基础的,然后你需要用 eslint-config-prettier 去禁用掉所有和 prettier 冲突的规则,这样才可以使用 eslint-plugin-prettier 去以符合 eslint 规则的方式格式化代码并提示对应的修改建议。
辅助优化流程
Husky 能够帮你阻挡住不好的代码提交和推送。是一个 git 钩子工具,我们这里主要用 pre-commit 钩子。通俗点讲就是 husky 可以在你 commit 之前帮你做一些事情。
lint-staged:在你提交的文件中,执行自定义的指令。自定义指令可以是你 eslint 相关,也可以是其他命令
npm i -D husky lint-staged
注: 安装的时候要注意下版本,本人就是没有注意版本,在安装 0.14.3 的 husky,造成一直不能成功调用,配置文件信息。后来才发现,husky 之后的版本才可以用,之前的版本是使用 script 配置中的 percommit 来调用的。
- 配置 husky、lint-staged,在
package.json
文件中添加如下配置:
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"**/*": [
"eslint --fix",
"git add"
]
}
然后就可以修改一个文件,再去 commit 代码。这个时候在 commit 之前,命令行就会执行 lint-staged 指令,去 eslint –fix 刚刚修改的文件。
注:这里也要注意,只会修改你当前发生修改的文件,没有发生修改的文件是不会执行 eslint –fix 命令的。 再安装后,想首次全部 eslint –fix,那只能先手动执行./node_modules/.bin/eslint --fix --ext .js ./src
问题记录:按照上述步骤安装完之后,发现还是执行不成功。不会根据配置文件完成预定目标。
后来找了很久才发现,是因为他的原理造成的。
通过查看源码可以看到,在安装 husky 的时候,husky 会根据 package.json 里的配置,在.git/hooks 目录生成所有的 hook 脚本(如果你已经自定义了一个 hook 脚本,husky 不会覆盖它)问题就出在这里,因为我之前安装过老版本的 husky,所以在 .git/hooks/ 文件夹内保留了之前的 hooks 配置。
这里有两个办法解决:
- 通过
npm uninstall -D husky
卸载后,重新安装npm i -D husky
- 手动删除 .git/hooks/* 目录下的所有文件,在重新安装
npm i -D husky
ESLint 配置说明
使用 JavaScript、JSON 或者 YAML 文件为整个目录(处理你的主目录)和它的子目录指定配置信息。可以配置一个独立的 .eslintrc.js 文件,或者直接在 package.json 文件里的 eslintConfig 字段指定配置,ESLint 会查找和自动读取它们,再者,你可以在命令行运行时指定一个任意的配置文件。
env
指定脚本的运行环境。每种环境都有一组特定的预定义全局变量。
- browser - 浏览器环境中的全局变量。
- node - Node.js 全局变量和 Node.js 作用域。
- commonjs - CommonJS 全局变量和 CommonJS 作用域 (用于 Browserify/WebPack 打包的只在浏览器中运行的代码)。
- shared-node-browser - Node.js 和 Browser 通用全局变量。
- es6 - 启用除了 modules 以外的所有 ECMAScript 6 特性(该选项会自动设置 ecmaVersion 解析器选项为 6)。
extends
用于指定需要配置文件的路径、可配置模块的名称、 eslint:recommended 或 eslint:all
ESLint 递归地扩展配置,因此基本配置也可以具有 extends 属性。extends 属性值可以省略包名的前缀 eslint-config-
。
可配置模块 是一个 npm 包,它输出一个配置对象。要确保这个包安装在 ESLint 能请求到的目录下。
plugins
插件 是一个 npm 包,通常输出规则。一些插件也可以输出一个或多个命名的 配置。要确保这个包安装在 ESLint 能请求到的目录下。
plugins 属性值 可以省略包名的前缀 eslint-plugin-
。
parser
默认 ESlint 使用 Espree 作为解析器,但是一旦我们使用 babel 的话,我们需要用 babel-eslint。
parserOptions
ESLint 允许你指定你想要支持的 JavaScript 语言选项。默认情况下,ESLint 支持 ECMAScript 5 语法。
可用的选项有:
- ecmaVersion - 默认设置为 3,5(默认), 你可以使用 6、7、8、9 或 10 来指定你想要使用的 ECMAScript 版本。你也可以用使用年份命名的版本号指定为 2015(同 6),2016(同 7),或 2017(同 8)或 2018(同 9)或 2019 (same as 10)
- sourceType - 设置为 “script” (默认) 或 “module”(如果你的代码是 ECMAScript 模块)。
- ecmaFeatures - 这是个对象,表示你想使用的额外的语言特性:
- globalReturn - 允许在全局作用域下使用 return 语句
- impliedStrict - 启用全局 strict mode (如果 ecmaVersion 是 5 或更高)
- jsx - 启用 JSX
- experimentalObjectRestSpread - 启用实验性的 object rest/spread properties 支持。(重要:这是一个实验性的功能,在未来可能会有明显改变。 建议你写的规则 不要 依赖该功能,除非当它发生改变时你愿意承担维护成本。)
rules
自定义规则,可以覆盖掉 extends 的配置。
- “off” 或 0 - 关闭规则
- “warn” 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出)
- “error” 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)
globals
当访问当前源文件内未定义的变量时,no-undef 规则将发出警告。如果你想在一个源文件里使用全局变量,推荐你在 ESLint 中定义这些全局变量,这样 ESLint 就不会发出警告了。你可以使用注释或在配置文件中定义全局变量。
不同项目配置
基础配置
因为我们使用 prettier 作为默认格式化工具,所以每种项目都需要安装 prettier 相关依赖:
npm i -D -E prettier eslint-plugin-prettier eslint-config-prettier
- 推荐配置项,新建
.prettierrc.js
文件
module.exports = {
// 一行最多 200 字符
printWidth: 200,
// 使用 2 个空格缩进
tabWidth: 2,
// 不使用缩进符,而使用空格
useTabs: false,
// 行尾不需要分号
semi: false,
// 使用单引号
singleQuote: true,
// 对象的 key 仅在必要时用引号
quoteProps: "as-needed",
// jsx 不使用单引号,而使用双引号
jsxSingleQuote: false,
// 末尾不需要逗号
trailingComma: "none",
// 大括号内的首尾需要空格
bracketSpacing: true,
// jsx 标签的反尖括号不需要换行
jsxBracketSameLine: true,
// 箭头函数,只有一个参数的时候,也需要括号
arrowParens: "always",
// 每个文件格式化的范围是文件的全部内容
rangeStart: 0,
rangeEnd: Infinity,
// 不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用默认的折行标准
proseWrap: "preserve",
// 根据显示样式决定 html 要不要折行
htmlWhitespaceSensitivity: "css",
// 换行符使用 lf
endOfLine: "lf",
};
执行 npx prettier --write src/index.js
测试是否可用
因为这里我们选择统一为 standard 规范,所以需要安装 standard 相关依赖
- 安装 eslint standard 相关
npm i -D eslint babel-eslint eslint-config-standard eslint-plugin-standard eslint-plugin-import eslint-plugin-node eslint-plugin-promise
react-native 项目
- 安装 eslint 相关
npm i -D eslint-plugin-jsx-a11y eslint-plugin-react eslint-plugin-react-native
- eslint 配置
module.exports = {
"env": {
"browser": true,
"es6": true,
"amd": true
},
"extends": ["eslint:recommended", "standard", "plugin:prettier/recommended", "plugin:react-native/all"],
"parser": "babel-eslint",
"parserOptions": {
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
"jsx": true
},
"sourceType": "module"
},
"plugins": [
"react",
"react-native"
],
"rules": {
'no-prototype-builtins': 0,
'prefer-promise-reject-errors': 0,
'no-async-promise-executor': 0,
'no-misleading-character-class': 0,
'no-useless-catch': 0,
"no-console": 0,
"react/jsx-uses-react": 0,
"react/jsx-uses-vars": 0,
"react-native/no-inline-styles": 0,
"react-native/sort-styles": 0
}
};
Vue 项目
- 安装 eslint 相关
npm i -D eslint-plugin-vue
- eslint 配置
"eslintConfig": {
"root": true,
"env": {
"node": true,
"browser": true,
"es6": true,
},
"extends": [
// 顺序有关系,层层覆盖
"eslint:recommended",
"plugin:vue/strongly-recommended",
"standard",
"plugin:prettier/recommended"
],
"rules": {
"vue/max-attributes-per-line": 0,
"vue/singleline-html-element-content-newline": 0,
"vue/html-self-closing": 0,
"no-console": 0,
"no-undef": 0
},
"parserOptions": {
"parser": "babel-eslint"
}
}
配置 VSCode
安装插件 Prettier - Code formatter
修改配置项
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.codeActionsOnSave": null,
"editor.formatOnSave": true
- 这样就可以在保存代码时,进行自动格式化了
排除特定文件
有时候我们还是会存在一些文件是不需要格式化和 eslint 验证的,比如外部 js 文件,图片文件,其他静态资源文件。
这样大家可以在文件目录下添加 ignore 文件:
.prettierignore
src/assets/*
.eslintignore
src/assets/*
Vue 项目 统一配置方式
npm i -D -E prettier eslint-plugin-prettier eslint-config-prettier
npm i -D eslint babel-eslint eslint-config-standard eslint-plugin-standard eslint-plugin-import eslint-plugin-node eslint-plugin-promise eslint-plugin-vue husky lint-staged
"eslintConfig": {
"root": true,
"env": {
"node": true,
"browser": true,
"es6": true,
},
"extends": [
"eslint:recommended",
"plugin:vue/strongly-recommended",
"standard",
"plugin:prettier/recommended"
],
"rules": {
"vue/max-attributes-per-line": 0,
"vue/singleline-html-element-content-newline": 0,
"vue/html-self-closing": 0,
"no-console": 0,
"no-undef": 0
},
"parserOptions": {
"parser": "babel-eslint"
}
},
"prettier": {
"printWidth": 200,
"tabWidth": 2,
"useTabs": false,
"semi": false,
"singleQuote": true,
"quoteProps": "as-needed",
"jsxSingleQuote": false,
"trailingComma": "none",
"bracketSpacing": true,
"jsxBracketSameLine": true,
"arrowParens": "always",
"requirePragma": false,
"insertPragma": false,
"proseWrap": "preserve",
"htmlWhitespaceSensitivity": "css",
"endOfLine": "lf"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"src/**/*": [
"prettier --write",
"eslint --fix",
"git add"
]
}