环境准备
- node v20.13.0
- pnpm v9.1.3
- vite v5.2.0
- vscode 插件
- ESLint
- Stylelint
- Prettier - Code formatter
使用vite创建一个react-ts项目
pnpm create vite react-code-lint --template react-ts
- 可以看到默认是已经帮我们生成了
.eslintrc.cjs
文件,可以先删除此文件(或者备份一个),自己通过eslint命令来创建 - 根目录下创建.vscode文件夹
- extensions.json - 用于放vscode扩展插件
- settings.json - 用于设置vscode的基本配置(项目配置会覆盖本地)
- 删除
.gitignore
里的.vscode/*
和!.vscode/extensions.json
- 目的:做到同一项目相同配置,保证风格统一
Eslint
ESLint 能够帮助开发者发现代码中的潜在错误和不良实践,确保代码符合团队或项目的编码规范,从而提高代码质量和可维护性。
这是使用的版本是eslint@8.57.0,和v9的版本有一定的区别
- 终端输入命令行
npx eslint --init
- 依次选中
√ How would you like to use ESLint? · problems
√ What type of modules does your project use? · esm
√ Which framework does your project use? · react
√ The React plugin doesn't officially support ESLint v9 yet. What would you like to do? · 8.x
√ Does your project use TypeScript? · typescript
√ Where does your code run? · browser
The config that you've selected requires the following dependencies:
eslint@8.x, globals, @eslint/js, typescript-eslint, eslint-plugin-react
√ Would you like to install them now? · No / Yes
√ Which package manager do you want to use? · pnpm
- 安装成功后生成
eslint.config.js
文件 - 配置规则发现不一样,还是用回之前
.eslintrc.cjs
的吧 - 配置写入 - 保存的时候会自动修复
{
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.fixAll.eslint": "explicit"
}
}
- 根目录下添加
.eslintignore
文件 v9不支持了
node_modules
build
dist
- 添加脚本命令
// ...
"scripts": {
// ...
"lint": "eslint .",
"lint:fix": "eslint . --ext .js,.ts,.jsx,.tsx --ignore-path .gitignore --fix",
}
// ...
- 运行脚本
pnpm lint
会出现以下警告,可以将typescript
进行降级即可"typescript": "^5.5.4",
- eslint不工作不检查的原因,需要删除 vscode 配置文件中的 eslint.options,具体可以参考#1823
- 新增插件排序
pnpm add eslint-plugin-import eslint-import-resolver-alias eslint-import-resolver-typescript -D
- 在.eslintrc.js 文件中增加配置
module.export = {
// ...
extends: [
// ...
"plugin:import/recommended",
],
settings: {
'import/resolver': {
// eslint-import-resolver-typescript 可以解决别名报错的问题
typescript: {
alwaysTryTypes: true,
},
// eslint-import-resolver-alias 可以解决绝对路径的问题
alias: {
map: [
['', './public'], // <-- this line
],
extensions: ['.js', '.jsx', '.ts', '.tsx', '.svg'],
},
},
},
rules: {
//...
"import/order": [
"error",
{
// 对导入模块进行分组,分组排序规则如下
groups: [
"builtin", // 内置模块
"external", // 外部模块
"parent", //父节点依赖
"sibling", //兄弟依赖
"internal", //内部引用
"index", // index文件
"type", //类型文件
"unknown",
],
//通过路径自定义分组
pathGroups: [
{
pattern: "@/**", // 把@开头的应用放在external分组后面
group: "external",
position: "after",
},
],
// 是否开启独特组,用于区分自定义规则分组和其他规则分组
distinctGroup: true,
// 每个分组之间换行
"newlines-between": "always",
// 相同分组排列规则 按字母升序排序
alphabetize: { order: "asc" },
},
],
},
};
Prettier
- 安装依赖
pnpm add prettier eslint-config-prettier eslint-plugin-prettier -D
- 添加
.prettierrc.cjs
文件
module.exports = {
// 一行最多多少个字符
printWidth: 90,
// 指定每个缩进级别的空格数
tabWidth: 2,
// 使用制表符而不是空格缩进行
useTabs: false,
// 在语句末尾是否需要分号
semi: true,
// 是否使用单引号
singleQuote: true,
// 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
quoteProps: 'as-needed',
// 在JSX中使用单引号而不是双引号
jsxSingleQuote: false,
// 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
trailingComma: 'es5',
// 在对象文字中的括号之间打印空格
bracketSpacing: true,
// jsx 标签的反尖括号需要换行
jsxBracketSameLine: false,
// 在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x
arrowParens: 'always',
// 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
rangeStart: 0,
rangeEnd: Infinity,
// 指定要使用的解析器,不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用默认的折行标准 always\never\preserve
proseWrap: 'preserve',
// 指定HTML文件的全局空格敏感度 css\strict\ignore
htmlWhitespaceSensitivity: 'css',
// Vue文件脚本和样式标签缩进
vueIndentScriptAndStyle: false,
//在 windows 操作系统中换行符通常是回车 (CR) 加换行分隔符 (LF),也就是回车换行(CRLF),
//然而在 Linux 和 Unix 中只使用简单的换行分隔符 (LF)。
//对应的控制字符为 "\n" (LF) 和 "\r\n"(CRLF)。auto意为保持现有的行尾
// 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
endOfLine: 'crlf',
};
- 修改
.eslintrc.cjs
文件,注入prettier
相关插件和配置
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
'plugin:prettier/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
'@typescript-eslint/no-unused-vars': ['warn'],
},
};
- 配置好可以看到
App.tsx
文件里的内容标红了- 使用配置好的脚本
pnpm run lint:fix
进行修复
- 使用配置好的脚本
- .vscode/settings.json中配置自动保存格式化,然后使用保存快捷键
"editor.formatOnSave": false,
"editor.defaultFormatter": "esbenp.prettier-vscode",
- Tip:
.prettierrc.cjs
修改了相关格式化配置规则,ESLint代码不会立即去检查报红,Ctrl+Shift+P 打开命令面板,输入Restart Extension Host
重启扩展宿主,等待加载一会儿,就能同步更新了。也可以暴力直接重启打开vscode软件。
Stylelint
- 安装相关依赖(使用Scss样式预处理器)
pnpm add stylelint stylelint-scss stylelint-config-recommended-scss stylelint-config-standard stylelint-config-standard-scss stylelint-order -D
- 根目录新增文件
.stylelintignore
、.stylelintrc.cjs
# .stylelintignore
# 旧的不需打包的样式库
*.min.css
# 其他类型文件
*.js
*.jpg
*.png
*.eot
*.ttf
*.woff
*.json
# 测试和打包目录
/test/
/dist/
/node_modules/
/lib/
module.exports = {
extends: [
'stylelint-config-standard',
'stylelint-config-standard-scss',
'stylelint-config-recommended-scss',
],
plugins: ['stylelint-scss', 'stylelint-order'],
rules: {
'no-irregular-whitespace': true,
// 指定样式的排序
'order/properties-order': [
[
'content',
'position',
'top',
'right',
'bottom',
'left',
'z-index',
'display',
'vertical-align',
'flex',
'flex-grow',
'flex-shrink',
'flex-basis',
'flex-direction',
'flex-flow',
'flex-wrap',
'grid',
'grid-area',
'grid-template',
'grid-template-areas',
'grid-template-rows',
'grid-template-columns',
'grid-row',
'grid-row-start',
'grid-row-end',
'grid-column',
'grid-column-start',
'grid-column-end',
'grid-auto-rows',
'grid-auto-columns',
'grid-auto-flow',
'grid-gap',
'grid-row-gap',
'grid-column-gap',
'gap',
'row-gap',
'column-gap',
'align-content',
'align-items',
'align-self',
'justify-content',
'justify-items',
'justify-self',
'order',
'float',
'clear',
'object-fit',
'overflow',
'overflow-x',
'overflow-y',
'overflow-scrolling',
'clip',
//
'box-sizing',
'width',
'min-width',
'max-width',
'height',
'min-height',
'max-height',
'margin',
'margin-top',
'margin-right',
'margin-bottom',
'margin-left',
'padding',
'padding-top',
'padding-right',
'padding-bottom',
'padding-left',
'border',
'border-spacing',
'border-collapse',
'border-width',
'border-style',
'border-color',
'border-top',
'border-top-width',
'border-top-style',
'border-top-color',
'border-right',
'border-right-width',
'border-right-style',
'border-right-color',
'border-bottom',
'border-bottom-width',
'border-bottom-style',
'border-bottom-color',
'border-left',
'border-left-width',
'border-left-style',
'border-left-color',
'border-radius',
'border-top-left-radius',
'border-top-right-radius',
'border-bottom-right-radius',
'border-bottom-left-radius',
'border-image',
'border-image-source',
'border-image-slice',
'border-image-width',
'border-image-outset',
'border-image-repeat',
'border-top-image',
'border-right-image',
'border-bottom-image',
'border-left-image',
'border-corner-image',
'border-top-left-image',
'border-top-right-image',
'border-bottom-right-image',
'border-bottom-left-image',
//
'background',
'background-color',
'background-image',
'background-attachment',
'background-position',
'background-position-x',
'background-position-y',
'background-clip',
'background-origin',
'background-size',
'background-repeat',
'color',
'box-decoration-break',
'box-shadow',
'outline',
'outline-width',
'outline-style',
'outline-color',
'outline-offset',
'table-layout',
'caption-side',
'empty-cells',
'list-style',
'list-style-position',
'list-style-type',
'list-style-image',
//
'font',
'font-weight',
'font-style',
'font-variant',
'font-size-adjust',
'font-stretch',
'font-size',
'font-family',
'src',
'line-height',
'letter-spacing',
'quotes',
'counter-increment',
'counter-reset',
'-ms-writing-mode',
'text-align',
'text-align-last',
'text-decoration',
'text-emphasis',
'text-emphasis-position',
'text-emphasis-style',
'text-emphasis-color',
'text-indent',
'text-justify',
'text-outline',
'text-transform',
'text-wrap',
'text-overflow',
'text-overflow-ellipsis',
'text-overflow-mode',
'text-shadow',
'white-space',
'word-spacing',
'word-wrap',
'word-break',
'overflow-wrap',
'tab-size',
'hyphens',
'interpolation-mode',
//
'opacity',
'visibility',
'filter',
'resize',
'cursor',
'pointer-events',
'user-select',
//
'unicode-bidi',
'direction',
'columns',
'column-span',
'column-width',
'column-count',
'column-fill',
'column-gap',
'column-rule',
'column-rule-width',
'column-rule-style',
'column-rule-color',
'break-before',
'break-inside',
'break-after',
'page-break-before',
'page-break-inside',
'page-break-after',
'orphans',
'widows',
'zoom',
'max-zoom',
'min-zoom',
'user-zoom',
'orientation',
'fill',
'stroke',
//
'transition',
'transition-delay',
'transition-timing-function',
'transition-duration',
'transition-property',
'transform',
'transform-origin',
'animation',
'animation-name',
'animation-duration',
'animation-play-state',
'animation-timing-function',
'animation-delay',
'animation-iteration-count',
'animation-direction',
'animation-fill-mode',
],
{
unspecified: 'bottom',
severity: 'error',
},
],
},
}
- 打开命令面板重启一下扩展宿主,可以看到
App.css
文件报红 - 添加脚本命令行
// ...
"scripts": {
// ...
"stylelint": "stylelint ./**/*.{css,scss}",
"stylelint:fix": "stylelint ./**/*.{css,scss} --fix",
},
// ...
- 使用
pnpm run stylelint:fix
进行样式格式化修复 - .vscode/settings.json中配置自动保存格式化,然后使用保存快捷键
{
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit"
},
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"stylelint.validate": ["css", "scss", "sass"],
"stylelint.enable": true,
"css.validate": false,
"scss.validate": false,
"[scss]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
Husky
- 安装依赖
pnpm add husky@8.0.3 -D
- 添加脚本命令
// ...
"scripts": {
// ...
"prepare": "husky install"
},
// ...
- 执行
pnpm run prepare
命令,根目录会多一个.husky
的目录 - 在
.husky
文件下添加pre-commit
文件
就可以当我们在使用 git commit 代码提交的时候会先触发以下命令进行代码格式化修复
pnpm husky add .husky/pre-commit "pnpm run lint:fix && pnpm run stylelint:fix"
Commitlint
- 安装依赖
pnpm add @commitlint/config-conventional @commitlint/cli commitizen@4.2.4 cz-customizable -D
- 为什么使用指定版本
- 根目录新增文件
.commitlintrc.cjs
、.cz-config.cjs
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
// type枚举
2,
'always',
[
':sparkles:',
':bug:',
':construction:',
':memo:',
':lipstick:',
':zap:',
':hammer:',
':white_check_mark:',
':rewind:',
':package:',
':rocket:',
':construction_worker:',
],
],
// 'type-empty': [2, 'never'], // 提交类型不能为空
// 'type-case': [0, 'always', 'lower-case'], // 提交类型的大小写(0表示不检查)
// 'scope-empty': [0], // 提交范围(scope)是否为空(0表示不检查)
// 'scope-case': [0], // 提交范围的大小写(0表示不检查)
// 'subject-empty': [2, 'never'], // 提交说明(subject)不能为空
// 'subject-case': [0], // 提交说明的大小写(0表示不检查)
// 'subject-full-stop': [0, 'never', '.'], // 提交说明的结尾不能有句号
// 'header-max-length': [2, 'always', 72], // 提交信息的头部最长72个字符
// 'body-leading-blank': [0], // 提交信息的主体开头不强制为空行
// 'footer-leading-blank': [0, 'always'], // 提交信息的脚注开头需要有空行
},
parserPreset: {
parserOpts: {
headerPattern: /^(:\w*:)(?:\((.*?)\))?\s((?:.*(?=\())|.*)(?:\(#(\d*)\))?/,
headerCorrespondence: ['type', 'scope', 'subject', 'ticket'],
},
},
}
module.exports = {
types: [
{
value: ':sparkles: feat',
name: '✨ feat: 新功能',
},
{
value: ':bug: fix',
name: '🐛 fix: 修复bug',
},
{
value: ':construction: WIP',
name: '🚧 WIP: 工作进行中',
},
{
value: ':memo: docs',
name: '📝 docs: 文档变更',
},
{
value: ':lipstick: style',
name: '💄 style: 代码风格变更',
},
{
value: ':zap: perf',
name: '⚡ perf: 性能优化',
},
{
value: ':hammer: refactor',
name: '🔨 refactor: 重构',
},
{
value: ':white_check_mark: test',
name: '✅ test: 测试',
},
{
value: ':rewind: revert',
name: '⏪️ revert: 代码回退',
},
{
value: ':package: build',
name: '📦 build: 打包构建',
},
{
value: ':rocket: chore',
name: '🚀 chore: 构建/工程依赖/工具',
},
{
value: ':construction_worker: ci',
name: '👷 CI 配置变更',
},
],
scopes: [],
scopeOverrides: {},
// override the messages, defaults are as follows
messages: {
type: '请选择提交类型(必填):',
scope: '请输入文件修改范围(可选):',
// used if allowCustomScopes is true
customScope: '请输入文件修改范围(可选):',
subject: '请简要描述提交(必填):\n',
body: '请输入详细描述,使用 "|" 实现换行输入(可选):\n',
breaking: '列出所有BREAKING CHANGES(可选):\n',
footer: '请输入要关联的 YouTrack Issue ID,例如: #5, #30 (可选):\n',
confirmCommit: '确定提交此说明吗?',
},
// 跳过空的 scope
skipEmptyScopes: false,
skipQuestions: ['breaking', 'body'],
// 设置为 true,在 scope 选择的时候,会有 empty 和 custom 可以选择
// 顾名思义,选择 empty 表示 scope 缺省,如果选择 custom,则可以自己输入信息
allowCustomScopes: true,
// 只有我们 type 选择了 feat 或者是 fix,才会询问我们 breaking message.
allowBreakingChanges: ['feat', 'fix'],
}
package.json
"config": {
"commitizen": {
"path": "./node_modules/cz-customizable"
},
"cz-customizable": {
"config": ".cz-config.cjs"
}
}
- 安装依赖
pnpm add @commitlint/config-conventional @commitlint/cli -D
- 添加commit-msg钩子
pnpm husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'
lint-staged
- 安装依赖
pnpm add lint-staged -D
- 在根目录新增文件
.lintstagedrc
{
"*.{js,jsx,ts,tsx}": [
"pnpm run lint:fix"
],
"*.{css,scss}": [
"pnpm run stylelint:fix",
"git add"
]
}
- 或者
package.json
下新增
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"pnpm run lint:fix"
],
"*.{css,scss}": [
"pnpm run stylelint:fix",
"git add"
]
}
- 修改.husky/pre-commit文件
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged
参考