flag:每月至少产出三篇高质量文章~
欢迎关注我的另外几篇文章:
可能想要玩的技术栈:
- 主技术栈:webpack5 + React18 + TS4.x + React-Router6.x
- 代码风格和规范:eslint + prettier + stylelint + husky + pre-commmit + commitlint
- 样式与处理器:
- 状态管理:TanStack Query、RTK/RTK-Query、mobx、recoil、jotai、zustand......
- UI:Antd5.x、Arco design......
- 按需加载
dark theme
I18n
- ......
- 静态资源:图片、
fonts
、Media
、数据资源(JSON
、csv
、tsv
、excel
)...... React-Admin
、可视化大屏、响应式、代码生成、低代码- 权限:
React-Admin(RBAC) + Go
、React-Admin(RBAC) + Node
- 接口
mock
- 性能优化
- 工具层面:热更新、资源压缩、代码分离(动态导入、懒加载、预加载等)、缓存……
- 代码层面:大组件拆分、全局状态管理、组件封装、
re-render
- 单测:Jest、React testing library,Chai、Mocha、Enzyme's......
CI/CD
、nginx
、jekins
、Docker 部署
- ......
上篇文章,我们做了项目的一些基础功能配置,本篇主要讲代码质量和git提交规范。
1、前言
一套完善的开发环境配置可以极大的提升开发效率,提高代码质量,方便多人合作,以及后期的项目迭代和维护。所以说,前端代码格式规范和语法检测的工具可以提高代码的质量和可读性,减少低级错误和维护成本,提高团队的协作效率和开发效率,是非常有必要的。
1.1 代码格式规范和语法检测工具
- EditorConfig:
EditorConfig
是一个用于统一编辑器和 IDE 的配置文件的工具。它可以帮助团队协作中的开发人员保持一致的编码风格,无论他们使用的是哪个编辑器或 IDE。EditorConfig
支持配置文件的语法规则、缩进、换行符、字符编码等,可以通过在项目中添加一个.editorconfig
文件来使用。 - ESLint:
ESLint
是一个广泛使用的 JavaScript 代码检查工具,它可以帮助开发人员遵循代码风格规范,并发现代码中潜在的问题。ESLint
有很多可定制的规则,可以根据团队的代码风格和项目的要求进行配置。ESLint 还支持集成到许多编辑器和 IDE 中,如 Visual Studio Code、Sublime Text、Atom、WebStorm 等,以提供实时的语法和格式错误检查。 - Prettier:
Prettier
是一个代码格式化工具,它可以自动化地将代码格式化为一致的风格。与ESLint
不同的是,Prettier
不关心代码的语义或质量,只关心代码的外观。Prettier
与ESLint
集成使用可以让代码保持一致性和规范性。 - Stylelint:
Stylelint
是一个 CSS 样式检查工具,它可以帮助开发人员遵循 CSS 代码风格规范,并发现代码中潜在的问题。Stylelint
有很多可定制的规则,可以根据团队的代码风格和项目的要求进行配置。Stylelint
也支持集成到许多编辑器和 IDE 中,如 Visual Studio Code、Sublime Text、Atom、WebStorm 等,以提供实时的语法和格式错误检查。 - Markdownlint:
Markdownlint
是一个用于检查Markdown
文件中语法和格式的工具,它可以帮助你保证Markdown
文件的一致性和可读性。Markdownlint
支持在命令行、编辑器和CI/CD
工具中使用。
这些工具都有相似的目标:
- 使代码本身和团队成员之间更加一致
- 检测可能导致潜在错误的有问题的代码
总的来说:
EditorConfig
: 跨编辑器和IDE编写代码,保持一致的简单编码风格;Prettier
: 专注于代码格式化的工具,美化代码;ESLint
:专注于代码质量检测、编码风格约束等;Stylelint
:专注于样式代码语法检查和格式错误检查;Markdownlint
:专注作为Markdown
的linter
;
它们整个的工作流程是这样的:
1.2 代码提交规范工具
- Commitizen:
Commitizen
是一个 Git 提交信息格式化工具,可以通过命令行交互式地创建符合规范的 Git 提交信息。Commitizen
支持自定义提交信息的格式,并且可以与其他规范工具(如ESLint
和Prettier
)集成使用。 - Conventional Commits:
Conventional Commits
是一个 Git 提交信息规范标准,规定了 Git 提交信息的格式和内容,包括提交类型、作用域、描述、消息体和消息页脚等部分。Conventional Commits
规范可以帮助团队协作中的开发人员更好地理解和管理代码变更历史。 - Husky 和 Commitlint:
Husky
和Commitlint
是两个工具,可以结合使用来规范 Git 提交信息。Husky
可以在 Git 提交前执行一些预定义的钩子(如pre-commit
和pre-push
),而Commitlint
可以检查 Git 提交信息是否符合规范。 - Git Hooks:
Git Hooks
是 Git 自带的一个钩子系统,可以在 Git 的各个生命周期(如pre-commit
和post-commit
)执行自定义的脚本。开发人员可以使用Git Hooks
来规范化 Git 提交信息,例如在pre-commit
钩子中执行提交信息的格式检查。
代码 Git 提交规范工具是非常有用的,可以帮助开发人员更好地管理代码变更历史,提高代码可维护性和协作效率。开发人员可以根据自己的需求和团队的规范选择合适的工具和标准来使用。
推荐文章:
2、editorconfig
在项目中引入 editorconfig
是为了在多人协作开发中保持代码的风格和一致性。不同的开发者使用不同的编辑器或IDE,可能会有不同的缩进(比如有的人喜欢4个空格,有的喜欢2个空格)、换行符、编码格式等。甚至相同的编辑器因为开发者自定义配置的不同也会导致不同风格的代码,这会导致代码的可读性降低,增加代码冲突的可能性,降低了代码的可维护性。
EditorConfig 使不同编辑器可以保持同样的配置。因此,我们得以无需在每次编写新代码时,再依靠 Prettier 来按照团队约定格式化一遍(出现保存时格式化突然改变的情况) 。当然这需要在你的 IDE 上安装了必要的 EditorConfig 插件或扩展。
2.1 安装 EditorConfig for VS Code
目前主流的编辑器或者 IDE 基本上都有对应的 EditorConfig
插件,有的是内置支持的(比如,WebStorm
不需要独立安装 EditorConfig
的插件),有的需要独立安装。
需要注意的是,不同的插件对
EditorConfig
属性的支持度不一样,笔者使用的是VS Code
。
然后在插件的介绍页上点击设置的齿轮,并且选择Add to Workspace Recommendations,就可以将其加入清单。也可以直接开启 .vscode/extensions.json
进行编辑
{ "recommendations": ["editorconfig.editorconfig"] }
为什么要做这个操作? 假如哪天项目新来一个协同开发的同学,当他拉取取项目,用 vscode
打开项目的时候,编辑器就会自动提醒他安装这个插件,并将相关的配置做设定。下面的 eslint
和 prettier
插件也是类似。
2.2 新建 .editorconfig
在根目录新建 .editorconfig
文件:
# https://editorconfig.org root = true # 设置为true表示根目录,控制配置文件 .editorconfig 是否生效的字段 [*] # 匹配全部文件,匹配除了 `/` 路径分隔符之外的任意字符串 charset = utf-8 # 设置字符编码,取值为 latin1,utf-8,utf-8-bom,utf-16be 和 utf-16le,当然 utf-8-bom 不推荐使用 end_of_line = lf # 设置使用的换行符,取值为 lf,cr 或者 crlf indent_size = 2 # 设置缩进的大小,即缩进的列数,当 indexstyle 取值 tab 时,indentsize 会使用 tab_width 的值 indent_style = space # 缩进风格,可选space|tab insert_final_newline = true # 设为true表示使文件以一个空白行结尾 trim_trailing_whitespace = true # 删除一行中的前后空格 [*.md] # 匹配全部 .md 文件 trim_trailing_whitespace = false
上面的配置可以规范本项目中文件的缩进风格,和缩进空格数等,会覆盖 vscode
的配置,来达到不同编辑器中代码默认行为一致的作用。
VS Code 的 EditorConfig
目前支持下列属性:
indent_style
indent_size
tab_width
end_of_line
(on save)insert_final_newline
(on save)trim_trailing_whitespace
(on save)
3、prettier
每个人写代码的风格习惯不一样,比如代码换行,结尾是否带分号,单双引号,缩进等,而且不能只靠口头规范来约束,项目紧急的时候可能会不太注意代码格式,这时候需要有工具来帮我们自动格式化代码,而prettier就是帮我们做这件事的。
3.1 安装 VS Code 插件和 prettier
安装 prettier
:
pnpm add prettier -D
3.2 新建 .prettier.js
在根目录下新建 .prettierrc.js 和 .prettierignore 文件:
// .prettierrc.js module.exports = { tabWidth: 2, // 一个tab代表几个空格数,默认就是2 useTabs: false, // 是否启用tab取代空格符缩进,.editorconfig设置空格缩进,所以设置为false printWidth: 100, // 一行的字符数,如果超过会进行换行 semi: false, // 行尾是否使用分号,默认为true singleQuote: true, // 字符串是否使用单引号 trailingComma: 'none', // 对象或数组末尾是否添加逗号 none| es5| all jsxSingleQuote: true, // 在jsx里是否使用单引号,你看着办 bracketSpacing: true, // 对象大括号直接是否有空格,默认为true,效果:{ foo: bar } arrowParens: 'avoid' // 箭头函数如果只有一个参数则省略括号 }
// .prettierignore node_modules dist env .gitignore pnpm-lock.yaml README.md
3.3 配置 .vscode/settings.json
配置前两步后,虽然已经配置 prettier
格式化规则,但还需要让 vscode
来支持保存后触发格式化,在项目根目录新建 .vscode
文件夹,内部新建 settings.json
文件配置文件,代码如下:
{ "search.exclude": { "/node_modules": true, "dist": true, "pnpm-lock.yaml": true }, "files.autoSave": "onFocusChange", "editor.formatOnSave": true, "editor.formatOnType": true, "editor.defaultFormatter": "esbenp.prettier-vscode", "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[javascriptreact]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[typescriptreact]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[json]": { "editor.defaultFormatter": "vscode.json-language-features" }, "[html]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[markdown]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "javascript.validate.enable": false, }
- 若配置
"prettier.requireConfig": true
则要求根目录下有Prettier
的配置文件,它会覆盖Prettier扩展
中的默认配置,否则保存时不会自动格式化。可以参考这里。 - 若配置
"prettier.requireConfig": false
则不要求根目录下有配置文件,若有的话也会被感知到并以配置文件的内容为准。
这些代码的作用是:在编辑后保存
[xxx]
文件时,自动应用Prettier
进行格式化,以确保代码风格的一致性。
先配置了忽略哪些文件不进行格式化,又添加了保存代码后触发格式化代码配置,以及各类型文件格式化使用的格式。这一步配置完成后,修改项目代码,把格式打乱,点击保存时就会看到编辑器自动把代码格式规范化了。
若设置需要配置文件,则必须要求根目录下有配置文件 .prettierrc.js
或 .editorconfig
中的一个或者两个同时存在,否则代码保存不会进行格式化。
可能你会对上面 .editorconfig
文件作为 Prettier
的配置文件感到疑惑,vscode
的 Prettier
插件中有关配置文件有这样的一段描述,如下图:
- 可以看出Prettier插件获取配置文件有一个优先级:
.prettierrc > .editorconfig > vscode默认配置
。 - 上面的前两者并不是说
.prettierrc
和.editorconfig
同时存在时,后者的配置内容就被忽略,实际的表现: .prettierrc.js
和.editorconfig
同时存在时,二者内容会进行合并,若配置项有冲突,这.prettierrc
的优先级更高。
为了保证我们的编辑器是使用 prettier
作为默认格式化的工具,最好是手动配置一下默认的格式化插件:
3.4 脚本命令检查和修复格式
在 package.json
的 "scripts"
中加入以下脚本命令:
"lint:prettier": "prettier --write --loglevel warn \"src/**/*.{js,ts,json,tsx,css,less,scss,stylus,html,md}\""
这段代码是一个脚本命令,用于运行 Prettier
工具来格式化指定目录下的文件。具体解释如下:
"--write"
: 表示将格式化后的结果直接写回原文件中,而不是输出到控制台。"--loglevel warn"
: 表示只输出警告级别的日志信息。"src/**/*.{js,ts,json,tsx,css,less,scss,stylus,html,md}"
: 是要格式化的文件的路径,这里指定了在src
目录下,所有扩展名为.js
、.ts
、.json
、.tsx
、.css
、.less
、.scss
、.stylus
、.html
、.md
的文件。
这个脚本命令的作用是:运行 Prettier 工具来格式化指定目录下的文件,并将格式化后的结果直接写回原文件中。同时,只输出警告级别的日志信息。
现在可以测试一下,将 .prettierrc.js
中的 tabWidth
值修改为 4(缩进为 4 个空格),然后运行 pnpm run lint:prettier
,随便打开一个 src
下的文件,你就会发现缩进从 2 变成 4 了。
4、Markdownlint
4.1 安装 markdownlint-cli
安装依赖:
pnpm add markdownlint-cli -D
4.2 新建 .markdownlint.js
module.exports = { default: false, // excludes all rules MD001: true, // enables rule MD001 MD003: { style: 'atx_closed' }, // set parameter style of rule MD003 to atx_closed 'first-line-heading': true, // enables alias first-line-heading atx_closed: true // enables tag atx-closed }
名称可以是default
、 rule
、 alias
或 tags
:
default
:特殊属性,设为true
会启用所有规则,反之设为false
则关闭所有规则rule
:规则名称,对应至各个规则,像是范例中的MD001
与MD003
alias
:规则别称,对应至各个规则的别名,例如范例中的first-line-heading
是规则MD041
的别名tags
:作用的标签,对应至各规则的所属标签,可以将其视为规则的群组,例如范例中的atx_closed
包括了MD020
与MD021
两个规则
在根目录新建 .markdownlintignore
,然后加入:
node_modules dist env .gitignore pnpm-lock.yaml src/assets/* .vscode public .github
设定完成后执行 Markdownlint
:
npx markdownlint --config .markdownlint.js --fix .
这个指令会依照 .markdownlint.js
所设定的规则规范项目中所有的 Markdown
文件。
最后一步,将其加入到 package.json
的 scripts
中
"scripts": { // ... "lint:md": "npx markdownlint --config .markdownlint.js --fix ." },
4.3 配置 .vscode/settings.json
{ // ... "editor.codeActionsOnSave": { "source.fixAll.markdownlint": true } }
这样就可以在保存md文件的时候,自动修复文档了。
5、stylelint
5.1 安装 stylelint 插件和依赖
pnpm add stylelint stylelint-config-css-modules stylelint-config-prettier stylelint-config-standard stylelint-order -D
5.2 新建 .stylelintrc.js 和 .stylelintignore
// @see: https://stylelint.io module.exports = { extends: [ 'stylelint-config-standard', 'stylelint-config-prettier' ], plugins: ['stylelint-order'], rules: { 'selector-class-pattern': [ // 命名规范 - '^([a-z][a-z0-9]*)(-[a-z0-9]+)*$', { message: 'Expected class selector to be kebab-case' } ], 'string-quotes': 'double', // 单引号 'at-rule-empty-line-before': null, 'at-rule-no-unknown': null, 'at-rule-name-case': 'lower', // 指定@规则名的大小写 'length-zero-no-unit': true, // 禁止零长度的单位(可自动修复) 'shorthand-property-no-redundant-values': true, // 简写属性 'number-leading-zero': 'always', // 小数不带0 'declaration-block-no-duplicate-properties': true, // 禁止声明快重复属性 'no-descending-specificity': true, // 禁止在具有较高优先级的选择器后出现被其覆盖的较低优先级的选择器。 'selector-max-id': null, // 限制一个选择器中 ID 选择器的数量 'max-nesting-depth': 10, 'declaration-block-single-line-max-declarations': 1, 'block-opening-brace-space-before': 'always', 'selector-max-type': [0, { ignore: ['child', 'descendant', 'compounded'] }], indentation: [ 2, { // 指定缩进 warning 提醒 severity: 'warning' } ], 'order/order': ['custom-properties', 'dollar-variables', 'declarations', 'rules', 'at-rules'], 'order/properties-order': [ // 规则顺序 // ... 这块太长,去我 github repo 找吧 ] } }
.stylelintignore
文件:
dist public env build .vscode .husky .DS_Store .github typings README.md node_modules
5.3 配置 .vscode/settings.json
最后记得在 .vscode/settings.json
中加入:
{ // ... "editor.codeActionsOnSave": { "source.fixAll.stylelint": true }, "stylelint.validate": [ "css", "less", "sass", "stylus", "postcss" ] }
6、eslint
ESLint
是什么呢?一个开源的 JavaScript
的 linting
工具,是一个在 JavaScript
代码中通过规则模式匹配作代码识别和报告的插件化的检测工具,它的目的是保证代码规范的一致性和及时发现代码问题、提前避免错误发生。它使用 espree 将 JavaScript
代码解析成抽象语法树 (AST),然后通过AST 来分析我们代码,从而给予我们两种提示:
- 代码质量问题:使用方式有可能有问题,eslint 可以发现代码中存在的可能错误,如使用未声明变量、声明而未使用的变量、修改 const 变量、代码中使用debugger等等
- 代码风格问题:风格不符合一定规则,eslint 也可以用来统一团队的代码风格,比如加不加分号、使用tab还是空格、字符串使用单引号 等等
ESLint
的关注点是代码质量,检查代码风格并且会提示不符合风格规范的代码。除此之外 ESLint
也具有一部分代码格式化的功能。
ESLint
最初是由Nicholas C. Zakas
( JavaScript 高级程序设计 作者)于2013年6月创建的开源项目。它的目标是提供一个插件化的javascript代码检测工具。
JavaScript发展历程中出现的Lint工具:JSLint
->JSHint
->ESLint/TSLint
;
JSLint
是最早出现的 Lint 工具,不支持灵活拓展及配置,必须接受它所有规则;JSHint
在JSLint
的基础上提供了一定的配置项,给了开发者较大的自由,但无法添加自定义规则;Zakas
创建ESLint
的初衷就是觉得当时的JSHint存在局限性,无法添加自定义规则。ES6
的出现后则让ESLint
迅速大火。
因为ES6新增了很多语法,JSHint
短期内无法提供支持,而 ESLint 只需要有合适的解析器以及拓展校验规则 就能够进行 Lint 检查。此时babel
就为兼容ESLint
开发了babel-eslint
解析器,提供支持的同时也让ESLint
成为最快支持ES6
语法的 Lint 工具。
6.1 安装 eslint 插件和包
安装 eslint
:
pnpm add eslint eslint-config-airbnb eslint-config-standard eslint-friendly-formatter eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-node eslint-plugin-promise eslint-plugin-react-hooks eslint-plugin-react @typescript-eslint/eslint-plugin @typescript-eslint/parser -D
6.2 新建 .eslintrc.js
在根目录新建一个 .eslintrc.js
文件:
module.exports = { env: { browser: true, es2021: true, node: true }, extends: [ 'airbnb-base', 'eslint:recommended', 'plugin:import/recommended', 'plugin:@typescript-eslint/recommended' ], parser: '@typescript-eslint/parser', parserOptions: { ecmaFeatures: { jsx: true }, ecmaVersion: 'latest', sourceType: 'module' }, plugins: ['react', '@typescript-eslint'], rules: { // eslint (http://eslint.cn/docs/rules) 'react/jsx-filename-extension': ['error', { extensions: ['.js', '.jsx', '.ts', '.tsx'] }], 'class-methods-use-this': 'off', 'no-param-reassign': 'off', 'no-unused-expressions': 'off', 'no-plusplus': 0, 'no-restricted-syntax': 0, 'consistent-return': 0, '@typescript-eslint/ban-types': 'off', // "import/no-extraneous-dependencies": "off", '@typescript-eslint/no-non-null-assertion': 'off', 'import/no-unresolved': 'off', 'import/prefer-default-export': 'off', // 关闭默认使用 export default 方式导出 'import/no-extraneous-dependencies': ['error', { devDependencies: true }], '@typescript-eslint/no-use-before-define': 0, 'no-use-before-define': 0, '@typescript-eslint/no-var-requires': 0, '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-namespace': 'off', // 禁止使用自定义 TypeScript 模块和命名空间。 'no-shadow': 'off', // "@typescript-eslint/no-var-requires": "off" 'import/extensions': [ 'error', 'ignorePackages', { '': 'never', js: 'never', jsx: 'never', ts: 'never', tsx: 'never' } ] // "no-var": "error", // 要求使用 let 或 const 而不是 var // "no-multiple-empty-lines": ["error", { max: 1 }], // 不允许多个空行 // "no-use-before-define": "off", // 禁止在 函数/类/变量 定义之前使用它们 // "prefer-const": "off", // 此规则旨在标记使用 let 关键字声明但在初始分配后从未重新分配的变量,要求使用 const // "no-irregular-whitespace": "off", // 禁止不规则的空白 // // typeScript (https://typescript-eslint.io/rules) // "@typescript-eslint/no-unused-vars": "error", // 禁止定义未使用的变量 // "@typescript-eslint/no-inferrable-types": "off", // 可以轻松推断的显式类型可能会增加不必要的冗长 // "@typescript-eslint/no-namespace": "off", // 禁止使用自定义 TypeScript 模块和命名空间。 // "@typescript-eslint/no-explicit-any": "off", // 禁止使用 any 类型 // "@typescript-eslint/ban-ts-ignore": "off", // 禁止使用 @ts-ignore // "@typescript-eslint/ban-types": "off", // 禁止使用特定类型 // "@typescript-eslint/explicit-function-return-type": "off", // 不允许对初始化为数字、字符串或布尔值的变量或参数进行显式类型声明 // "@typescript-eslint/no-var-requires": "off", // 不允许在 import 语句中使用 require 语句 // "@typescript-eslint/no-empty-function": "off", // 禁止空函数 // "@typescript-eslint/no-use-before-define": "off", // 禁止在变量定义之前使用它们 // "@typescript-eslint/ban-ts-comment": "off", // 禁止 @ts-<directive> 使用注释或要求在指令后进行描述 // "@typescript-eslint/no-non-null-assertion": "off", // 不允许使用后缀运算符的非空断言(!) // "@typescript-eslint/explicit-module-boundary-types": "off", // 要求导出函数和类的公共类方法的显式返回和参数类型 // // react (https://github.com/jsx-eslint/eslint-plugin-react) // "react-hooks/rules-of-hooks": "error", // "react-hooks/exhaustive-deps": "off" }, settings: { 'import/extensions': ['.js', '.jsx', '.ts', '.tsx'], 'import/parsers': { '@typescript-eslint/parser': ['.ts', '.tsx'] }, 'import/resolver': { node: { paths: ['src'], extensions: ['.js', '.jsx', '.ts', '.tsx'], moduleDirectory: ['node_modules', 'src/'] } } } }
有时候,我们需要在代码中忽略ESLint的某些规则检查,此时我们可以通过加入代码注释的方式解决:可以指定整个文件、某一行、某一区块开启/关闭 某些或全部规则检查;
/* eslint-disable */ --禁用全部规则 放在文件顶部则整个文件范围都不检查 /* eslint-disable no-alert, no-console */ --禁用某些规则 // eslint-disable-line --当前行上禁用规则 // eslint-disable-next-line --下一行上禁用规则
6.3 新建 .eslintignore
在根目录新建一个 .eslintignore
文件:
node_modules dist env .gitignore pnpm-lock.yaml README.md src/assets/*
.eslintignore
用于指定 ESLint 工具在检查代码时要忽略的文件和目录。具体来说,.eslintignore
文件中列出的文件和目录将被 ESLint 忽略,不会对其进行代码检查和报告。这个文件中的每一行都是一个文件或目录的模式,支持使用通配符(*
)和正则表达式来匹配多个文件或目录。
通常情况下,
.eslintignore
文件中会列出一些不需要检查的文件或目录,比如第三方库、测试代码、构建输出等,以减少 ESLint 的检查范围,提高代码检查的效率。
6.4 添加eslint语法检测脚本
前面的eslint报错和警告都是我们用眼睛看到的,有时候需要通过脚本执行能检测出来,在package.json
的scripts
中新增:
// --fix:此项指示 ESLint 尝试修复尽可能多的问题。这些修复是对实际文件本身进行的,只有剩余的未修复的问题才会被输出。 "lint:eslint": "eslint --fix --ext .js,.ts,.tsx ./src",
现在执行 pnpm run lint:eslint
,控制台将会爆出一系列 warning
:
除此之外再解决一个问题就是 eslint
报的警告:
React version not specified in eslint-plugin-react settings
需要告诉 eslint
使用的 react版本
,在 .eslintrc.js
和rules
平级添加 settings
配置,让 eslint
自己检测 react
版本,对应 issuse:
settings: { "react": { "version": "detect" } }
再执行pnpm run lint:eslint
就不会报这个未设置react
版本的警告了。
6.5 eslint 与 prettier 冲突
安装两个依赖:
pnpm add eslint-config-prettier eslint-plugin-prettier -D
在 .eslintrc.js
的 extends
中加入:
module.exports = { // ... extends: [ // ... 'plugin:prettier/recommended', // <==== 增加一行 ], // ... }
最后再配置一下 .vscode/settings.json
:
{ // ... "eslint.enable": true, "editor.codeActionsOnSave": { "source.fixAll.eslint": true, }, }
7、husky + lint-statged
7.1 使用lint-staged优化eslint检测速度
在上面配置的eslint
会检测src
文件下所有的 .ts, .tsx
文件,虽然功能可以实现,但是当项目文件多的时候,检测的文件会很多,需要的时间也会越来越长,但其实只需要检测提交到暂存区,就是git add
添加的文件,不在暂存区的文件不用再次检测,而lint-staged
就是来帮我们做这件事情的。
在package.json
添加lint-staged
配置
"lint-staged": { "src/**/*.{ts,tsx}": [ "pnpm run lint:eslint", "pnpm run lint:prettier" ] },
因为要检测 git
暂存区代码,所以如果你的项目还没有使用 git
来做版本控制,需要执行git init
初始化一下git
:
git init
初始化git
完成后就可以进行测试了,先提交一下没有语法问题的App.tsx
git add src/App.tsx
把src/App.tsx
提交到暂存区后,执行npx lint-staged
,会顺利通过检测。
假如我们现在把 package.json
中的 "lint:eslint"
改一下,加一个 --max-warnings=0
,表示允许最多 0 个警告,就是只要出现警告就会报错:
"lint:eslint": "eslint --fix --ext .js,.ts,.tsx ./src --max-warnings=0",
然后在 App.tsx
中加入一个未使用的变量:
// ... const a = 1 // ...
然后执行:
git add src/App.tsx npx lint-staged
你就会发现控制台出现了 warning
:
这就是 lint-staged
的作用。
7.2 使用tsc检测类型和报错
需要注意的是,执行 tsc 命令可能会生成一个编译后的产物文件,需想要避免这个问题,需要在 tsconfig.json
中加入 "noEmit": true
:
{ "compilerOptions": { "target": "es2016", // 指定ECMAScript目标版本 "esModuleInterop": true, "module": "commonjs", "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true, "resolveJsonModule": true, "allowJs": false, // 允许编辑js文件 "noEmit": true, // 不生成输出文件 "noImplicitAny": false, "experimentalDecorators": true, "baseUrl": ".", "paths": { "@/*": ["./src/*"] }, "typeRoots": ["./typings/*.d.ts", "node_modules/@types"], "jsx": "react-jsx" // react18这里改成react-jsx,就不需要在tsx文件中手动引入React了 }, "include": ["./src/*", "./src/**/*.ts", "./src/**/*.tsx", "./typings/*.d.ts"], "exclude": ["node_modules", "dist"] }
更多的配置参考:
{ "compilerOptions": { /* 基本选项 */ "target": "es5", // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT' "module": "commonjs", // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015' "lib": [], // 指定要包含在编译中的库文件 "allowJs": true, // 允许编译 javascript 文件 "checkJs": true, // 报告 javascript 文件中的错误 "jsx": "preserve", // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react' "declaration": true, // 生成相应的 '.d.ts' 文件 "sourceMap": true, // 生成相应的 '.map' 文件 "outFile": "./", // 将输出文件合并为一个文件 "outDir": "./", // 指定输出目录 "rootDir": "./", // 用来控制输出目录结构 --outDir. "removeComments": true, // 删除编译后的所有的注释 "noEmit": true, // 不生成输出文件 "importHelpers": true, // 从 tslib 导入辅助工具函数 "isolatedModules": true, // 将每个文件作为单独的模块 (与 'ts.transpileModule' 类似). /* 严格的类型检查选项 */ "strict": true, // 启用所有严格类型检查选项 "noImplicitAny": true, // 在表达式和声明上有隐含的 any类型时报错 "strictNullChecks": true, // 启用严格的 null 检查 "noImplicitThis": true, // 当 this 表达式值为 any 类型的时候,生成一个错误 "alwaysStrict": true, // 以严格模式检查每个模块,并在每个文件里加入 'use strict' /* 额外的检查 */ "noUnusedLocals": true, // 有未使用的变量时,抛出错误 "noUnusedParameters": true, // 有未使用的参数时,抛出错误 "noImplicitReturns": true, // 并不是所有函数里的代码都有返回值时,抛出错误 "noFallthroughCasesInSwitch": true, // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿) /* 模块解析选项 */ "moduleResolution": "node", // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6) "baseUrl": "./", // 用于解析非相对模块名称的基目录 "paths": {}, // 模块名到基于 baseUrl 的路径映射的列表 "rootDirs": [], // 根文件夹列表,其组合内容表示项目运行时的结构内容 "typeRoots": [], // 包含类型声明的文件列表 "types": [], // 需要包含的类型声明文件名列表 "allowSyntheticDefaultImports": true, // 允许从没有设置默认导出的模块中默认导入。 /* Source Map Options */ "sourceRoot": "./", // 指定调试器应该找到 TypeScript 文件而不是源文件的位置 "mapRoot": "./", // 指定调试器应该找到映射文件而不是生成文件的位置 "inlineSourceMap": true, // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件 "inlineSources": true, // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性 /* 其他选项 */ "experimentalDecorators": true, // 启用装饰器 "emitDecoratorMetadata": true // 为装饰器提供元数据的支持 } }
在项目中使用了ts
,但一些类型问题,现在配置的eslint
是检测不出来的,需要使用ts
提供的tsc
工具进行检测,如下示例
在index.tsx
定义了函数hello
,参数name
是string
类型,当调用传number
类型参数时,页面有了明显的ts报错,但此时提交index.tsx
文件到暂存区后执行npx lint-staged
:
发现没有检测到报错,所以需要配置下tsc
来检测类型,在package.json
添加脚本命令
"pre-check": "tsc && npx lint-staged"
执行pnpm run pre-check
,发现已经可以检测出类型报错了。
7.3 配置 husk
为了避免把不规范的代码提交到远程仓库,一般会在git提交代码时对代码语法进行检测,只有检测通过时才能被提交,git提供了一系列的githooks
,而我们需要其中的pre-commit
钩子,它会在git commit
把代码提交到本地仓库之前执行,可以在这个阶段检测代码,如果检测不通过就退出命令行进程停止commit
。
7.3.1 代码提交前husky检测语法
而husky
就是可以监听githooks
的工具,可以借助它来完成这件事情。
7.3.2 安装husky
pnpm add husky -D
7.3.3 配置husky的pre-commit钩子
生成.husky
配置文件夹(如果项目中没有初始化git
,需要先执行git init
)
npx husky install
会在项目根目录生成 .husky
文件夹,生成文件成功后,需要让husky
支持监听pre-commit
钩子,监听到后执行上面定义的pnpm run pre-check
语法检测。
npx husky add .husky/pre-commit 'pnpm run pre-check'
会在 .husky
目录下生成pre-commit
文件,里面可以看到我们设置的npm run pre-check
命令。
然后提交代码进行测试
git add . git commit -m "feat: add code validate"
会发现监听pre-commit
钩子执行了pnpm run pre-check
, 使用eslint
检测了git
暂存区的两个文件,并且发现了index.tsx
的警告,退出了命令行,没有执行git commit
把暂存区代码提交到本地仓库。
到这里代码提交语法验证就配置完成了,可以有效的保障仓库的代码质量。
8、Commit 信息的 Linter - Commitlint
Commitlint
是个 npm 包,它使用 commit conventions 规范来检查 commit
的信息是否符合我们约定好的提交规范。
通过配置 commitlint.config.js
, Commitlint
可以知道要使用哪些规则规范 commit
信息,并输出相对的提示供使用者作为修改的依据。
使用 Commitlint
规范项目的 commit
,可以让所有人的代码提交保持一致的格式,这样做会有下列好处:
- 容易检索:利用定义的关键字可以轻松地找到想要找的
commit
- 自动输出
Changelog
:固定的讯息格式可以藉由changelog 工具的帮助输出Changelog
8.1 安装Commitlint
首先安装 Commitlint
:
pnpm add @commitlint/cli -D
8.2 使用Commitlint
安装完成后,由于 Commitlint
的配置档是必要的,因此要建立配置档 commitlint.config.js
:
js
复制代码
module.exports = { rules: { 'header-min-length': [2, 'always', 10], }, };
配置属性 rules
可以设定规则,规则列表请参考 Commitlint 的官方页面。范例中设定讯息标头的最小长度要大于10。
接着执行 commitlint
:
> echo 'foo' | npx commitlint ⧗ input: foo ✖ header must not be shorter than 10 characters, current length is 3 [header-min-length] ✖ found 1 problems, 0 warnings ⓘ Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint
当 message
信息为 foo
时,由于长度只有3,因此 Commitlint
会视为违规而输出错误讯息。
8.3 配置规则包
为了节省配置规则的时间, Commitlint
可以使用预先配置的规则包来设定多项规则。使用前须要先安装:
pnpm add @commitlint/config-conventional -D
这里使用 @commitlint/config-conventional
是 Commitlint
提供的规则包。安装完成后,要在配置中设定使用规则包:
module.exports = { extends: ['@commitlint/config-conventional'], // ... };
这样一来 Commitlint
就会将 @commitlint/config-conventional所配置的规则都纳入并对讯息做相应的检查。
8.4 使用 Husky 为 Commitlint 注册 Git Hooks
到目前为止,我们都必须自己去手动调用 Commitlint
才能作用,使用起来的步骤较原本多,也不直观,容易被忽略。
接下来我们使用 Husky
将 Commitlint
融入 Git flow
中,让其更加的易用。使用 husky add
将指令加入 Git hooks
:
npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'
修改完后,要重新注册 Git hooks
:
npx husky install
这会触发相关的初始化工作。完成设定后,当你输入指令 git commit
,在完成编辑讯息后会启动 Commitlint
检查讯息。
9、Commitizen
为了避免写出不符规范的 commit message
而提交失败, Commitizen
使用问答的方式,让使用者在完成问答时就可以边写出符合规范的信息,以减少来回的次数。
Commitizen
是个指令式的工具,使用 Commitizen
来 commit
代码时会启动设定的adapter
,使用 adapter
提供的问题一一询问开发者,每个问题都会确认一部分的 commit message
,到最后将所有的回答组合起来,变成一个完整并符合规范的 commit message
。
9.1 cz-git
指定提交文字规范,一款工程性更强、高度自定义、标准输出格式的 commitizen
适配器:
pnpm add commitizen cz-git -D
配置 package.json
:
"config": { "commitizen": { "path": "node_modules/cz-git" } }
9.2 配置 commitlint.config.js
文件
// @see: https://cz-git.qbenben.com/zh/guide /** @type {import('cz-git').UserConfig} */ module.exports = { ignores: [commit => commit.includes('init')], extends: ['@commitlint/config-conventional'], rules: { // @see: https://commitlint.js.org/#/reference-rules 'body-leading-blank': [2, 'always'], 'footer-leading-blank': [1, 'always'], 'header-max-length': [2, 'always', 108], 'subject-empty': [2, 'never'], 'type-empty': [2, 'never'], 'subject-case': [0], 'type-enum': [ 2, 'always', [ 'feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'build', 'ci', 'chore', 'revert', 'wip', 'workflow', 'types', 'release' ] ] }, prompt: { messages: { type: "Select the type of change that you're committing:", scope: 'Denote the SCOPE of this change (optional):', customScope: 'Denote the SCOPE of this change:', subject: 'Write a SHORT, IMPERATIVE tense description of the change:\n', body: 'Provide a LONGER description of the change (optional). Use "|" to break new line:\n', breaking: 'List any BREAKING CHANGES (optional). Use "|" to break new line:\n', footerPrefixsSelect: 'Select the ISSUES type of changeList by this change (optional):', customFooterPrefixs: 'Input ISSUES prefix:', footer: 'List any ISSUES by this change. E.g.: #31, #34:\n', confirmCommit: 'Are you sure you want to proceed with the commit above?' // 中文版 // type: "选择你要提交的类型 :", // scope: "选择一个提交范围(可选):", // customScope: "请输入自定义的提交范围 :", // subject: "填写简短精炼的变更描述 :\n", // body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n', // breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n', // footerPrefixsSelect: "选择关联issue前缀(可选):", // customFooterPrefixs: "输入自定义issue前缀 :", // footer: "列举关联issue (可选) 例如: #31, #I3244 :\n", // confirmCommit: "是否提交或修改commit ?" }, types: [ { value: 'feat', name: 'feat: 🚀 A new feature', emoji: '🚀' }, { value: 'fix', name: 'fix: 🧩 A bug fix', emoji: '🧩' }, { value: 'docs', name: 'docs: 📚 Documentation only changes', emoji: '📚' }, { value: 'style', name: 'style: 🎨 Changes that do not affect the meaning of the code', emoji: '🎨' }, { value: 'refactor', name: 'refactor: ♻️ A code change that neither fixes a bug nor adds a feature', emoji: '♻️' }, { value: 'perf', name: 'perf: ⚡️ A code change that improves performance', emoji: '⚡️' }, { value: 'test', name: 'test: ✅ Adding missing tests or correcting existing tests', emoji: '✅' }, { value: 'build', name: 'build: 📦️ Changes that affect the build system or external dependencies', emoji: '📦️' }, { value: 'ci', name: 'ci: 🎡 Changes to our CI configuration files and scripts', emoji: '🎡' }, { value: 'chore', name: "chore: 🔨 Other changes that don't modify src or test files", emoji: '🔨' }, { value: 'revert', name: 'revert: ⏪️ Reverts a previous commit', emoji: '⏪️' } // 中文版 // { value: "特性", name: "特性: 🚀 新增功能", emoji: "🚀" }, // { value: "修复", name: "修复: 🧩 修复缺陷", emoji: "🧩" }, // { value: "文档", name: "文档: 📚 文档变更", emoji: "📚" }, // { value: "格式", name: "格式: 🎨 代码格式(不影响功能,例如空格、分号等格式修正)", emoji: "🎨" }, // { value: "重构", name: "重构: ♻️ 代码重构(不包括 bug 修复、功能新增)", emoji: "♻️" }, // { value: "性能", name: "性能: ⚡️ 性能优化", emoji: "⚡️" }, // { value: "测试", name: "测试: ✅ 添加疏漏测试或已有测试改动", emoji: "✅" }, // { value: "构建", name: "构建: 📦️ 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等)", emoji: "📦️" }, // { value: "集成", name: "集成: 🎡 修改 CI 配置、脚本", emoji: "🎡" }, // { value: "回退", name: "回退: ⏪️ 回滚 commit", emoji: "⏪️" }, // { value: "其他", name: "其他: 🔨 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)", emoji: "🔨" } ], useEmoji: true, themeColorCode: '', scopes: [], allowCustomScopes: true, allowEmptyScopes: true, customScopesAlign: 'bottom', customScopesAlias: 'custom', emptyScopesAlias: 'empty', upperCaseSubject: false, allowBreakingChanges: ['feat', 'fix'], breaklineNumber: 100, breaklineChar: '|', skipQuestions: [], issuePrefixs: [{ value: 'closed', name: 'closed: ISSUES has been processed' }], customIssuePrefixsAlign: 'top', emptyIssuePrefixsAlias: 'skip', customIssuePrefixsAlias: 'custom', allowCustomIssuePrefixs: true, allowEmptyIssuePrefixs: true, confirmColorize: true, maxHeaderLength: Infinity, maxSubjectLength: Infinity, minSubjectLength: 0, scopeOverrides: undefined, defaultBody: '', defaultIssues: '', defaultScope: '', defaultSubject: '' } }
然后测试
git add . git cz
9.3 一键提交
我们还可以通过一个script来集成之前所有的这些步骤:
"scripts": { // ... "commit": "git pull && git add -A && git-cz && git push", }
现在,我们只需要执行 pnpm run commit
即可完成代码的质量检测、style format
、代码提交规范等一系列流程了。
10、change-log
10.1 简单使用
安装 standard-version
(github地址)
pnpm add standard-version -D
自动化升级版本号、生成 changelog
及 tag
添加到 package.json
脚本命令
"scripts": { // ... "release": "standard-version" }
通过 pnpm run release
,生成日志。
10.2 高级用法
10.2.1 这个东西可以做什么?
- 自动升级版本
- 自动打
tag
- 自动生成
changelog
10.2.2 自动升级版本
1. 版本构成
版本号 major.minor.patch
2. 默认的版本更新规则
主版本号.次版本号.修订号,版本号递增规则如下:
主版本号(major)
:当你做了不兼容的API
修改,次版本号(minor)
:当你做了向下兼容的功能性新增,可以理解为Feature
版本,修订号(patch)
:当你做了向下兼容的问题修正,可以理解为Bug fix
版本。
先行版本号可以加到 “主版本号.次版本号.修订号”
的后面,作为延伸。
3. 先行版本
当即将发布大版本改动前,但是又不能保证这个版本的功能 100% 正常,这个时候可以发布先行版本:
alpha
: 内部版本beta
: 公测版本rc
: 候选版本(Release candiate)
比如:1.0.0-alpha.0
、1.0.0-alpha.1
、1.0.0-rc.0
、1.0.0-rc.1
等。
standard-verstion
生成的 CHANGELOG
只会包含 feat
、fix
、BREACK-CHANGE
类型的提交记录:
{ scripts: { "release": "standard-version", "release:alpha": "standard-version --prerelease alpha", "release:rc": "standard-version --prerelease rc", "release:major": "pnpm run release -- --release-as major", "release:minor": "pnpm run release -- --release-as minor", "release:patch": "pnpm run release -- --release-as patch" } }
10.2.3 手动控制版本更新
1. 直接升级major
"scripts": { "release-major": "standard-version --release-as major", }
2. 直接升级minor
"scripts": { "release-minor": "standard-version --release-as minor", }
3. 直接升级patch
"scripts": { "release-patch": "standard-version --release-as patch", }
4. 按默认规则升级版本号
"scripts": { "release": "standard-version", }
5. 强制打一个静态版本号
"scripts": { "release-static": "standard-version --release-as 3.3.3", }
第一个版本(该方式不会升级版本号)
# npm run script pnpm run release -- --first-release # global bin standard-version --first-release # npx npx standard-version --first-release
10.2.4 配置哪些commit消息写入changelog
hidden
属性值控制是否将该类型的 commit
消息写入 changlog
, 不填的情况下默认是: false
,在根目录下新建 .versionrc.js
:
module.exports = { types: [ { type: 'feat', section: '✨ Features | 新功能' }, { type: 'fix', section: '🐛 Bug Fixes | Bug 修复' }, { type: 'init', section: '🎉 Init | 初始化' }, { type: 'docs', section: '✏️ Documentation | 文档' }, { type: 'style', section: '💄 Styles | 风格' }, { type: 'refactor', section: '♻️ Code Refactoring | 代码重构' }, { type: 'perf', section: '⚡ Performance Improvements | 性能优化' }, { type: 'test', section: '✅ Tests | 测试' }, { type: 'revert', section: '⏪ Revert | 回退', hidden: true }, { type: 'build', section: '📦 Build System | 打包构建' }, { type: 'chore', section: '🚀 Chore | 构建/工程依赖/工具' }, { type: 'ci', section: '👷 Continuous Integration | CI 配置' } ] }
10.2.5 配置跳过生成changelog这个步骤
所有可配置跳过的有: bump, changelog, commit, tag:
{ "standard-version": { "skip": { "changelog": true } } }
至此,我们就完成了脚手架的代码质量和 git
提交规范的配置。就当前的脚手架已具备基本的 React
项目的配置,可以作为大多数项目的基础架子了~
end~