前言

团队开发中,每个人的编码习惯不同,代码格式不同。这就会导致代码难看,难以维护。统一代码风格可以:

  1. 增强代码的可读性,降低维护成本。
  2. 有利于代码审查。
  3. 养成规范代码的习惯,有利于自身成长。

下面将使用 ESLint + Prettier + husky + lint-staged 对代码进行规范及检查。
使用的项目为 vue3 + ts + vite 项目。

ESLint 和 Prettier

区别

ESLint(包括其他一些 lint 工具)的主要功能包含代码格式和代码质量的校验,而 Prettier 只是代码格式的校验,不会对代码质量进行校验。代码格式问题通常指的是:单行代码长度、tab 长度、空格、逗号表达式等问题。代码质量问题指的是:未使用变量、三等号、全局变量声明等问题。

配合

为什么要两者配合使用?

  • 第一,ESLint 推出 –fix 参数前,ESLint 并没有自动格式化代码的功能,而 Prettier 可以自动格式化代码。

  • 第二,虽然 ESLint 也可以校验代码格式,但 Prettier 更擅长,同时 Prettier 也支持其他语言。

相关依赖

nodejs 版本为 16.19.0。

1
2
3
4
5
6
"eslint": "^8.57.0"
"eslint-config-prettier": "^9.1.0"
"eslint-plugin-prettier": "^5.2.1"
"husky": "^9.1.5"
"lint-staged": "^15.2.2"
"prettier": "^3.3.3"

一、ESlint

1、安装 eslint

1
npm install eslint --save-dev

2、配置 eslint

1
2
npm init @eslint/config

二、Prettier

1、安装 prettier

1
2
# nodejs 版本在16及以下时,请安装 2.x 版本 prettier
npm install prettier --save-dev

2、配置 prettier

  1. 根目录创建 .prettierrc.json文件,并定义想要的代码样式。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"printWidth": 120,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": false,
"quoteProps": "as-needed",
"jsxSingleQuote": false,
"trailingComma": "all",
"bracketSpacing": true,
"arrowParens": "always",
"requirePragma": false,
"insertPragma": false,
"proseWrap": "preserve",
"htmlWhitespaceSensitivity": "css"
}

配置说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
module.exports = {
// 一行最多 120 字符
printWidth: 120,
// 使用 2 个空格缩进
tabWidth: 2,
// 不使用 tab 缩进,而使用空格
useTabs: false,
// 行尾需要有分号
semi: true,
// 使用单引号代替双引号
singleQuote: false,
// 对象的 key 仅在必要时用引号
quoteProps: "as-needed",
// jsx 不使用单引号,而使用双引号
jsxSingleQuote: false,
// 末尾使用逗号
trailingComma: "all",
// 大括号内的首尾需要空格 { foo: bar }
bracketSpacing: true,
// 箭头函数,只有一个参数的时候,也需要括号
arrowParens: "always",
// 不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用默认的折行标准
proseWrap: "preserve",
// 根据显示样式决定 html 要不要折行
htmlWhitespaceSensitivity: "css",
};
  1. 最好再加上 .prettierignore 文件,避免把不必要的文件也进行格式化。
1
2
3
4
5
6
#ignore
node_modules
yarn*
*-lock*
dist*
public/
  1. 完善 .eslintrc.cjs 文件
1
2
3
4
5
6
7
8
9
10
module.exports = {
// ...其他配置
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:vue/vue3-essential",
// 新增下面这个配置代码
"plugin:prettier/recommended",
],
};

三、lint-staged

lint-staged 可以让你在 Git 暂存(staged)区域中的文件上运行脚本,通常用于在提交前对代码进行格式化、静态检查等操作。

1、安装 lint-staged

1
npm install lint-staged --save-dev

2、配置 lint-staged

在 package.json 文件中添加以下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
//... 其他配置
"scripts": {
//... 其他配置
"lint-staged": "lint-staged"
},
"lint-staged": {
// *.{js,vue} 校验暂存区的文件类型
// 校验命令,执行 eslint 、prettier
// prettier --write、 eslint --fix 两个命令,在 git 提交时会对代码进行校验,并对不符合要求的代码自动进行格式化修复。
"*.{js,vue,ts,tsx}": ["prettier --write", "eslint --fix"]
}
}

四、Husky

husky 是一个 Git 钩子(Git hooks)工具,它可以让你在 Git 事件发生时执行脚本,进行代码格式化、测试等操作。

常见的钩子:

  • pre-commit:在执行 Git commit 命令之前触发,用于在提交代码前进行代码检查、格式化、测试等操作。
  • commit-msg:在提交消息(commit message)被创建后,但提交操作尚未完成之前触发,用于校验提交消息的格式和内容。
  • pre-push:在执行 Git push 命令之前触发,用于在推送代码前进行额外检查、测试等操作。

1、安装 Husky

项目根目录下执行:

1
npm install husky --save-dev

2、配置 Husky

1
npx husky init

在 package.json 文件中添加配置:

1
2
3
4
5
6
7
8
9
10
11
12
{
//... 其他配置
"scripts": {
//... 其他配置
"prepare": "husky init"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
}
}

3、创建挂钩

在 Git 提交之前做 eslint 语法校验 。

创建钩子脚本文件。

1
npx husky

执行成功后,.husky 目录多出一个 pre-commit 文件,文件修改为以下内容:

1
2
3
4
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm run lint-staged

五、vscode 保存时,进行代码格式化

.vscode/settings.json 文件(没有就自行创建)中添加一下配置:

1
2
3
4
5
6
7
8
9
10
{
// ... 其他配置
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"prettier.requireConfig": true,
"eslint.enable": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}

六、解决 eslint 和 prettier 冲突

有时,ESLint 的规则和 Prettier 的规则可能存在冲突,导致代码格式化不一致。使用 eslint-config-prettier 可以关闭 ESLint 中与 Prettier 冲突的规则。

1
npm i  eslint-config-prettier eslint-plugin-prettier --save-dev

.eslintrc.jsextends 中加入配置:

1
2
3
{
extends: ['plugin:prettier/recommended'],
}

七、注意事项

1、运行项目时,报错 Delete eslintprettier/prettier

由于历史原因,windows 下和 linux 下的文本文件的换行符不一致。

  • Windows 在换行的时候,同时使用了回车符 CR(carriage-return character)和换行符 LF(linefeed character)
  • 而 Mac 和 Linux 系统,仅仅使用了换行符 LF
  • 老版本的 Mac 系统使用的是回车符 CR

解决方案

  1. 使用 vue-cli-service lint 进行修复:
1
npm run lint --fix
  1. windows 电脑设置 git
1
git config --global core.autocrlf false

注意:git 全局配置之后,你需要重新拉取代码。

2、如果提交时报命令相关错误,大概率是依赖版本问题,具体修改可参考【注意事项】【3】。

3、项目运行时,报错 Module parse failed: Unexpected token

可能是 prettier 版本问题,编写此文章时,nodejs 版本为 16.19.0,使用的依赖版本如下:

1
2
3
4
5
6
"eslint": "^8.57.0"
"eslint-config-prettier": "^9.1.0"
"eslint-plugin-prettier": "^5.2.1"
"husky": "^9.1.5"
"lint-staged": "^15.2.2"
"prettier": "^3.3.3"

注:

  1. 如提交时报错,请检查依赖版本是否过高。
  2. 上面 husky 9.0.11 的版本 nodejs 版本应大于等于 16。
  3. lint-staged 15+的版本,nodejs 版本应大于等于 18.12.0,实测 16.19.0 版本也可使用。
  4. 如果 nodejs 不能升级到 16 版本,则需降低 husky 和 lint-staged 版本。实测 nodejs 版本为 14.21.2 时,husky@8.0.3lint-staged@13.2.0 可正常使用,husky@8.0.3 的用法和该文档用法有些区别,具体请查看官方文档

4、运行时,报错Syntax Error: TypeError: eslint.CLIEngine is not a constructor

  1. 检查 package.json 中是否存在 @vue/cli-plugin-eslint,如存在,则去掉重新运行。
  2. 如果不是【1】的原因,尝试降级 eslint。

八、项目中 .eslintrc.cjs 及 .prettier.json 的配置

.eslintrc.cjs 中的 rules

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
module.exports = {
// ... 其他配置
rules: {
// eslint(https://eslint.bootcss.com/docs/rules/)
"no-var": "error", // 要求使用 let 或 const 而不是 var
"no-multiple-empty-lines": ["warn", { max: 1 }], // 不允许多个空行
"no-console": process.env.NODE_ENV === "production" ? "error" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "error" : "off",
"no-unexpected-multiline": "error", // 禁止空余的多行
"no-useless-escape": "off", // 禁止不必要的转义字符

// typeScript (https://typescript-eslint.io/rules)
"@typescript-eslint/no-unused-vars": "error", // 禁止定义未使用的变量
"@typescript-eslint/prefer-ts-expect-error": "error", // 禁止使用 @ts-ignore
"@typescript-eslint/no-explicit-any": "off", // 禁止使用 any 类型
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-namespace": "off", // 禁止使用自定义 TypeScript 模块和命名空间。
"@typescript-eslint/semi": "off",

// eslint-plugin-vue (https://eslint.vuejs.org/rules/)
"vue/multi-word-component-names": "off", // 要求组件名称始终为 “-” 链接的单词
"vue/script-setup-uses-vars": "error", // 防止<script setup>使用的变量<template>被标记为未使用
"vue/no-mutating-props": "off", // 不允许组件 prop的改变
"vue/attribute-hyphenation": "off", // 对模板中的自定义组件强制执行属性命名样式
},
};

.prettier.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"printWidth": 120,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": false,
"quoteProps": "as-needed",
"jsxSingleQuote": false,
"trailingComma": "all",
"bracketSpacing": true,
"arrowParens": "always",
"requirePragma": false,
"insertPragma": false,
"proseWrap": "preserve",
"htmlWhitespaceSensitivity": "css"
}