环境准备

    • node v20.13.0
    • pnpm v9.1.3
    • vite v5.2.0
    • vscode 插件
      • ESLint
      • Stylelint
      • Prettier - Code formatter

    使用vite创建一个react-ts项目

    1. 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的版本有一定的区别

    • 终端输入命令行
    1. npx eslint --init
    • 依次选中
    1. How would you like to use ESLint? · problems
    2. What type of modules does your project use? · esm
    3. Which framework does your project use? · react
    4. The React plugin doesn't officially support ESLint v9 yet. What would you like to do? · 8.x
    5. √ Does your project use TypeScript? · typescript
    6. √ Where does your code run? · browser
    7. The config that you've selected requires the following dependencies:
    8. eslint@8.x, globals, @eslint/js, typescript-eslint, eslint-plugin-react
    9. Would you like to install them now? · No / Yes
    10. Which package manager do you want to use? · pnpm
    • 安装成功后生成eslint.config.js文件
    • 配置规则发现不一样,还是用回之前.eslintrc.cjs的吧
    • 配置写入 - 保存的时候会自动修复
    1. {
    2. "editor.codeActionsOnSave": {
    3. "source.fixAll": "explicit",
    4. "source.fixAll.eslint": "explicit"
    5. }
    6. }
    • 根目录下添加.eslintignore文件
    1. node_modules
    2. build
    3. dist
    • 添加脚本命令
    1. // ...
    2. "scripts": {
    3. // ...
    4. "lint": "eslint . --ext .js,.ts,.jsx,.tsx --report-unused-disable-directives --max-warnings 0",
    5. "lint:fix": "eslint . --ext .js,.ts,.jsx,.tsx --ignore-path .gitignore --fix",
    6. }
    7. // ...
    • 新增插件排序
    1. pnpm add eslint-plugin-import eslint-import-resolver-alias eslint-import-resolver-typescript -D
    • 在.eslintrc.js 文件中增加配置
    1. module.export = {
    2. // ...
    3. extends: [
    4. // ...
    5. "plugin:import/recommended",
    6. ],
    7. settings: {
    8. 'import/resolver': {
    9. // eslint-import-resolver-typescript 可以解决别名报错的问题
    10. typescript: {
    11. alwaysTryTypes: true,
    12. },
    13. // eslint-import-resolver-alias 可以解决绝对路径的问题
    14. alias: {
    15. map: [
    16. ['', './public'], // <-- this line
    17. ],
    18. extensions: ['.js', '.jsx', '.ts', '.tsx', '.svg'],
    19. },
    20. },
    21. },
    22. rules: {
    23. //...
    24. "import/order": [
    25. "error",
    26. {
    27. // 对导入模块进行分组,分组排序规则如下
    28. groups: [
    29. "builtin", // 内置模块
    30. "external", // 外部模块
    31. "parent", //父节点依赖
    32. "sibling", //兄弟依赖
    33. "internal", //内部引用
    34. "index", // index文件
    35. "type", //类型文件
    36. "unknown",
    37. ],
    38. //通过路径自定义分组
    39. pathGroups: [
    40. {
    41. pattern: "@/**", // 把@开头的应用放在external分组后面
    42. group: "external",
    43. position: "after",
    44. },
    45. ],
    46. // 是否开启独特组,用于区分自定义规则分组和其他规则分组
    47. distinctGroup: true,
    48. // 每个分组之间换行
    49. "newlines-between": "always",
    50. // 相同分组排列规则 按字母升序排序
    51. alphabetize: { order: "asc" },
    52. },
    53. ],
    54. },
    55. };

    Prettier

    • 安装依赖
    1. pnpm add prettier eslint-config-prettier eslint-plugin-prettier -D
    • 添加.prettierrc.cjs文件
    1. module.exports = {
    2. // 一行最多多少个字符
    3. printWidth: 90,
    4. // 指定每个缩进级别的空格数
    5. tabWidth: 2,
    6. // 使用制表符而不是空格缩进行
    7. useTabs: false,
    8. // 在语句末尾是否需要分号
    9. semi: true,
    10. // 是否使用单引号
    11. singleQuote: true,
    12. // 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
    13. quoteProps: 'as-needed',
    14. // 在JSX中使用单引号而不是双引号
    15. jsxSingleQuote: false,
    16. // 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
    17. trailingComma: 'es5',
    18. // 在对象文字中的括号之间打印空格
    19. bracketSpacing: true,
    20. // jsx 标签的反尖括号需要换行
    21. jsxBracketSameLine: false,
    22. // 在单独的箭头函数参数周围包括括号 always:(x) => x \ avoid:x => x
    23. arrowParens: 'always',
    24. // 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
    25. rangeStart: 0,
    26. rangeEnd: Infinity,
    27. // 指定要使用的解析器,不需要写文件开头的 @prettier
    28. requirePragma: false,
    29. // 不需要自动在文件开头插入 @prettier
    30. insertPragma: false,
    31. // 使用默认的折行标准 always\never\preserve
    32. proseWrap: 'preserve',
    33. // 指定HTML文件的全局空格敏感度 css\strict\ignore
    34. htmlWhitespaceSensitivity: 'css',
    35. // Vue文件脚本和样式标签缩进
    36. vueIndentScriptAndStyle: false,
    37. //在 windows 操作系统中换行符通常是回车 (CR) 加换行分隔符 (LF),也就是回车换行(CRLF),
    38. //然而在 Linux 和 Unix 中只使用简单的换行分隔符 (LF)。
    39. //对应的控制字符为 "\n" (LF) 和 "\r\n"(CRLF)。auto意为保持现有的行尾
    40. // 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
    41. endOfLine: 'crlf',
    42. };
    • 修改.eslintrc.cjs文件,注入prettier相关插件和配置
    1. module.exports = {
    2. root: true,
    3. env: { browser: true, es2020: true },
    4. extends: [
    5. 'eslint:recommended',
    6. 'plugin:@typescript-eslint/recommended',
    7. 'plugin:react-hooks/recommended',
    8. 'plugin:prettier/recommended',
    9. ],
    10. ignorePatterns: ['dist', '.eslintrc.cjs'],
    11. parser: '@typescript-eslint/parser',
    12. plugins: ['react-refresh'],
    13. rules: {
    14. 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
    15. '@typescript-eslint/no-unused-vars': ['warn'],
    16. },
    17. };
    • 配置好可以看到App.tsx文件里的内容标红了
        1. 使用配置好的脚本 pnpm run lint:fix进行修复
        1. .vscode/settings.json中配置自动保存格式化,然后使用保存快捷键
    1. "editor.formatOnSave": false,
    2. "editor.defaultFormatter": "esbenp.prettier-vscode",
    • Tip:.prettierrc.cjs修改了相关格式化配置规则,ESLint代码不会立即去检查报红,Ctrl+Shift+P 打开命令面板,输入Restart Extension Host重启扩展宿主,等待加载一会儿,就能同步更新了。也可以暴力直接重启打开vscode软件。

    Stylelint

    • 安装相关依赖(使用Scss样式预处理器)
    1. pnpm add stylelint stylelint-scss stylelint-config-recommended-scss stylelint-config-standard stylelint-config-standard-scss stylelint-order -D
    • 根目录新增文件 .stylelintignore.stylelintrc.cjs
    1. # .stylelintignore
    2. # 旧的不需打包的样式库
    3. *.min.css
    4. # 其他类型文件
    5. *.js
    6. *.jpg
    7. *.png
    8. *.eot
    9. *.ttf
    10. *.woff
    11. *.json
    12. # 测试和打包目录
    13. /test/
    14. /dist/
    15. /node_modules/
    16. /lib/
    1. module.exports = {
    2. extends: [
    3. 'stylelint-config-standard',
    4. 'stylelint-config-standard-scss',
    5. 'stylelint-config-recommended-scss',
    6. ],
    7. plugins: ['stylelint-scss', 'stylelint-order'],
    8. rules: {
    9. 'no-irregular-whitespace': true,
    10. // 指定样式的排序
    11. 'order/properties-order': [
    12. [
    13. 'content',
    14. 'position',
    15. 'top',
    16. 'right',
    17. 'bottom',
    18. 'left',
    19. 'z-index',
    20. 'display',
    21. 'vertical-align',
    22. 'flex',
    23. 'flex-grow',
    24. 'flex-shrink',
    25. 'flex-basis',
    26. 'flex-direction',
    27. 'flex-flow',
    28. 'flex-wrap',
    29. 'grid',
    30. 'grid-area',
    31. 'grid-template',
    32. 'grid-template-areas',
    33. 'grid-template-rows',
    34. 'grid-template-columns',
    35. 'grid-row',
    36. 'grid-row-start',
    37. 'grid-row-end',
    38. 'grid-column',
    39. 'grid-column-start',
    40. 'grid-column-end',
    41. 'grid-auto-rows',
    42. 'grid-auto-columns',
    43. 'grid-auto-flow',
    44. 'grid-gap',
    45. 'grid-row-gap',
    46. 'grid-column-gap',
    47. 'gap',
    48. 'row-gap',
    49. 'column-gap',
    50. 'align-content',
    51. 'align-items',
    52. 'align-self',
    53. 'justify-content',
    54. 'justify-items',
    55. 'justify-self',
    56. 'order',
    57. 'float',
    58. 'clear',
    59. 'object-fit',
    60. 'overflow',
    61. 'overflow-x',
    62. 'overflow-y',
    63. 'overflow-scrolling',
    64. 'clip',
    65. //
    66. 'box-sizing',
    67. 'width',
    68. 'min-width',
    69. 'max-width',
    70. 'height',
    71. 'min-height',
    72. 'max-height',
    73. 'margin',
    74. 'margin-top',
    75. 'margin-right',
    76. 'margin-bottom',
    77. 'margin-left',
    78. 'padding',
    79. 'padding-top',
    80. 'padding-right',
    81. 'padding-bottom',
    82. 'padding-left',
    83. 'border',
    84. 'border-spacing',
    85. 'border-collapse',
    86. 'border-width',
    87. 'border-style',
    88. 'border-color',
    89. 'border-top',
    90. 'border-top-width',
    91. 'border-top-style',
    92. 'border-top-color',
    93. 'border-right',
    94. 'border-right-width',
    95. 'border-right-style',
    96. 'border-right-color',
    97. 'border-bottom',
    98. 'border-bottom-width',
    99. 'border-bottom-style',
    100. 'border-bottom-color',
    101. 'border-left',
    102. 'border-left-width',
    103. 'border-left-style',
    104. 'border-left-color',
    105. 'border-radius',
    106. 'border-top-left-radius',
    107. 'border-top-right-radius',
    108. 'border-bottom-right-radius',
    109. 'border-bottom-left-radius',
    110. 'border-image',
    111. 'border-image-source',
    112. 'border-image-slice',
    113. 'border-image-width',
    114. 'border-image-outset',
    115. 'border-image-repeat',
    116. 'border-top-image',
    117. 'border-right-image',
    118. 'border-bottom-image',
    119. 'border-left-image',
    120. 'border-corner-image',
    121. 'border-top-left-image',
    122. 'border-top-right-image',
    123. 'border-bottom-right-image',
    124. 'border-bottom-left-image',
    125. //
    126. 'background',
    127. 'background-color',
    128. 'background-image',
    129. 'background-attachment',
    130. 'background-position',
    131. 'background-position-x',
    132. 'background-position-y',
    133. 'background-clip',
    134. 'background-origin',
    135. 'background-size',
    136. 'background-repeat',
    137. 'color',
    138. 'box-decoration-break',
    139. 'box-shadow',
    140. 'outline',
    141. 'outline-width',
    142. 'outline-style',
    143. 'outline-color',
    144. 'outline-offset',
    145. 'table-layout',
    146. 'caption-side',
    147. 'empty-cells',
    148. 'list-style',
    149. 'list-style-position',
    150. 'list-style-type',
    151. 'list-style-image',
    152. //
    153. 'font',
    154. 'font-weight',
    155. 'font-style',
    156. 'font-variant',
    157. 'font-size-adjust',
    158. 'font-stretch',
    159. 'font-size',
    160. 'font-family',
    161. 'src',
    162. 'line-height',
    163. 'letter-spacing',
    164. 'quotes',
    165. 'counter-increment',
    166. 'counter-reset',
    167. '-ms-writing-mode',
    168. 'text-align',
    169. 'text-align-last',
    170. 'text-decoration',
    171. 'text-emphasis',
    172. 'text-emphasis-position',
    173. 'text-emphasis-style',
    174. 'text-emphasis-color',
    175. 'text-indent',
    176. 'text-justify',
    177. 'text-outline',
    178. 'text-transform',
    179. 'text-wrap',
    180. 'text-overflow',
    181. 'text-overflow-ellipsis',
    182. 'text-overflow-mode',
    183. 'text-shadow',
    184. 'white-space',
    185. 'word-spacing',
    186. 'word-wrap',
    187. 'word-break',
    188. 'overflow-wrap',
    189. 'tab-size',
    190. 'hyphens',
    191. 'interpolation-mode',
    192. //
    193. 'opacity',
    194. 'visibility',
    195. 'filter',
    196. 'resize',
    197. 'cursor',
    198. 'pointer-events',
    199. 'user-select',
    200. //
    201. 'unicode-bidi',
    202. 'direction',
    203. 'columns',
    204. 'column-span',
    205. 'column-width',
    206. 'column-count',
    207. 'column-fill',
    208. 'column-gap',
    209. 'column-rule',
    210. 'column-rule-width',
    211. 'column-rule-style',
    212. 'column-rule-color',
    213. 'break-before',
    214. 'break-inside',
    215. 'break-after',
    216. 'page-break-before',
    217. 'page-break-inside',
    218. 'page-break-after',
    219. 'orphans',
    220. 'widows',
    221. 'zoom',
    222. 'max-zoom',
    223. 'min-zoom',
    224. 'user-zoom',
    225. 'orientation',
    226. 'fill',
    227. 'stroke',
    228. //
    229. 'transition',
    230. 'transition-delay',
    231. 'transition-timing-function',
    232. 'transition-duration',
    233. 'transition-property',
    234. 'transform',
    235. 'transform-origin',
    236. 'animation',
    237. 'animation-name',
    238. 'animation-duration',
    239. 'animation-play-state',
    240. 'animation-timing-function',
    241. 'animation-delay',
    242. 'animation-iteration-count',
    243. 'animation-direction',
    244. 'animation-fill-mode',
    245. ],
    246. {
    247. unspecified: 'bottom',
    248. severity: 'error',
    249. },
    250. ],
    251. },
    252. }
    • 打开命令面板重启一下扩展宿主,可以看到App.css文件报红
    • 添加脚本命令行
    1. // ...
    2. "scripts": {
    3. // ...
    4. "stylelint": "stylelint ./**/*.{css,scss}",
    5. "stylelint:fix": "stylelint ./**/*.{css,scss} --fix",
    6. },
    7. // ...
    • 使用pnpm run stylelint:fix进行样式格式化修复
    • .vscode/settings.json中配置自动保存格式化,然后使用保存快捷键
    1. {
    2. "editor.codeActionsOnSave": {
    3. "source.fixAll": "explicit",
    4. "source.fixAll.eslint": "explicit",
    5. "source.fixAll.stylelint": "explicit"
    6. },
    7. "editor.formatOnSave": true,
    8. "editor.defaultFormatter": "esbenp.prettier-vscode",
    9. "stylelint.validate": ["css", "scss", "sass"],
    10. "stylelint.enable": true,
    11. "css.validate": false,
    12. "scss.validate": false,
    13. "[scss]": {
    14. "editor.defaultFormatter": "esbenp.prettier-vscode"
    15. },
    16. "[css]": {
    17. "editor.defaultFormatter": "esbenp.prettier-vscode"
    18. }
    19. }

    Husky

    • 安装依赖
    1. pnpm add husky@8.0.3 -D
    • 添加脚本命令
    1. // ...
    2. "scripts": {
    3. // ...
    4. "prepare": "husky install"
    5. },
    6. // ...
    • 执行pnpm run prepare命令,根目录会多一个.husky的目录
    • .husky文件下添加pre-commit文件

    就可以当我们在使用 git commit 代码提交的时候会先触发以下命令进行代码格式化修复

    1. pnpm husky add .husky/pre-commit "pnpm run lint:fix && pnpm run stylelint:fix"

    Commitlint

    • 安装依赖
    1. pnpm add @commitlint/config-conventional @commitlint/cli commitizen@4.2.4 cz-customizable -D
    1. module.exports = {
    2. extends: ['@commitlint/config-conventional'],
    3. rules: {
    4. 'type-enum': [
    5. // type枚举
    6. 2,
    7. 'always',
    8. [
    9. ':sparkles:',
    10. ':bug:',
    11. ':construction:',
    12. ':memo:',
    13. ':lipstick:',
    14. ':zap:',
    15. ':hammer:',
    16. ':white_check_mark:',
    17. ':rewind:',
    18. ':package:',
    19. ':rocket:',
    20. ':construction_worker:',
    21. ],
    22. ],
    23. // 'type-empty': [2, 'never'], // 提交类型不能为空
    24. // 'type-case': [0, 'always', 'lower-case'], // 提交类型的大小写(0表示不检查)
    25. // 'scope-empty': [0], // 提交范围(scope)是否为空(0表示不检查)
    26. // 'scope-case': [0], // 提交范围的大小写(0表示不检查)
    27. // 'subject-empty': [2, 'never'], // 提交说明(subject)不能为空
    28. // 'subject-case': [0], // 提交说明的大小写(0表示不检查)
    29. // 'subject-full-stop': [0, 'never', '.'], // 提交说明的结尾不能有句号
    30. // 'header-max-length': [2, 'always', 72], // 提交信息的头部最长72个字符
    31. // 'body-leading-blank': [0], // 提交信息的主体开头不强制为空行
    32. // 'footer-leading-blank': [0, 'always'], // 提交信息的脚注开头需要有空行
    33. },
    34. parserPreset: {
    35. parserOpts: {
    36. headerPattern: /^(:\w*:)(?:\((.*?)\))?\s((?:.*(?=\())|.*)(?:\(#(\d*)\))?/,
    37. headerCorrespondence: ['type', 'scope', 'subject', 'ticket'],
    38. },
    39. },
    40. }
    1. module.exports = {
    2. types: [
    3. {
    4. value: ':sparkles: feat',
    5. name: '✨ feat: 新功能',
    6. },
    7. {
    8. value: ':bug: fix',
    9. name: '🐛 fix: 修复bug',
    10. },
    11. {
    12. value: ':construction: WIP',
    13. name: '🚧 WIP: 工作进行中',
    14. },
    15. {
    16. value: ':memo: docs',
    17. name: '📝 docs: 文档变更',
    18. },
    19. {
    20. value: ':lipstick: style',
    21. name: '💄 style: 代码风格变更',
    22. },
    23. {
    24. value: ':zap: perf',
    25. name: '⚡ perf: 性能优化',
    26. },
    27. {
    28. value: ':hammer: refactor',
    29. name: '🔨 refactor: 重构',
    30. },
    31. {
    32. value: ':white_check_mark: test',
    33. name: '✅ test: 测试',
    34. },
    35. {
    36. value: ':rewind: revert',
    37. name: '⏪️ revert: 代码回退',
    38. },
    39. {
    40. value: ':package: build',
    41. name: '📦 build: 打包构建',
    42. },
    43. {
    44. value: ':rocket: chore',
    45. name: '🚀 chore: 构建/工程依赖/工具',
    46. },
    47. {
    48. value: ':construction_worker: ci',
    49. name: '👷 CI 配置变更',
    50. },
    51. ],
    52. scopes: [],
    53. scopeOverrides: {},
    54. // override the messages, defaults are as follows
    55. messages: {
    56. type: '请选择提交类型(必填):',
    57. scope: '请输入文件修改范围(可选):',
    58. // used if allowCustomScopes is true
    59. customScope: '请输入文件修改范围(可选):',
    60. subject: '请简要描述提交(必填):\n',
    61. body: '请输入详细描述,使用 "|" 实现换行输入(可选):\n',
    62. breaking: '列出所有BREAKING CHANGES(可选):\n',
    63. footer: '请输入要关联的 YouTrack Issue ID,例如: #5, #30 (可选):\n',
    64. confirmCommit: '确定提交此说明吗?',
    65. },
    66. // 跳过空的 scope
    67. skipEmptyScopes: false,
    68. skipQuestions: ['breaking', 'body'],
    69. // 设置为 true,在 scope 选择的时候,会有 empty 和 custom 可以选择
    70. // 顾名思义,选择 empty 表示 scope 缺省,如果选择 custom,则可以自己输入信息
    71. allowCustomScopes: true,
    72. // 只有我们 type 选择了 feat 或者是 fix,才会询问我们 breaking message.
    73. allowBreakingChanges: ['feat', 'fix'],
    74. }
    • package.json
    1. "config": {
    2. "commitizen": {
    3. "path": "./node_modules/cz-customizable"
    4. },
    5. "cz-customizable": {
    6. "config": ".cz-config.cjs"
    7. }
    8. }
    • 安装依赖
    1. pnpm add @commitlint/config-conventional @commitlint/cli -D
    • 添加commit-msg钩子
    1. pnpm husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'

    lint-staged

    • 安装依赖
    1. pnpm add lint-staged -D
    • 在根目录新增文件.lintstagedrc
    1. {
    2. "*.{js,jsx,ts,tsx}": [
    3. "pnpm run lint:fix"
    4. ],
    5. "*.{css,scss}": [
    6. "pnpm run stylelint:fix",
    7. "git add"
    8. ]
    9. }
    • 或者package.json下新增
    1. "lint-staged": {
    2. "*.{js,jsx,ts,tsx}": [
    3. "pnpm run lint:fix"
    4. ],
    5. "*.{css,scss}": [
    6. "pnpm run stylelint:fix",
    7. "git add"
    8. ]
    9. }
    • 修改.husky/pre-commit文件
    1. #!/usr/bin/env sh
    2. . "$(dirname -- "$0")/_/husky.sh"
    3. npx lint-staged

    参考