前端代码风格因人而异,一个项目参与的人多了,不加强控制可能就是一个大杂烩,对开发人员来讲就是一个噩梦。
如何解决这种困境?
通过使用 ESLint +Prettier+ Husky + Lint-staged+Commitlint + Commitizen 这套方案,它能够在提升代码质量,保障团队协作效率,统一项目代码风格,强化代码审查流程,支持自动化运维 和促进项目管理等方面发挥重要作用,同时为项目的长期稳健发展打下坚实基础。
吴冬林| 前端开发工程师
目录
一、背景
二、代码书写规范
(一)代码检查工具:ESLint
(二)代码格式化工具:prettier
(三)Vscode插件 和设置:
三、代码提交规范
(一)husky
(二)commitlint
四、总结
一、背景
为何要打造这样一套前端现代化规范工程?
1、代码风格一致性:
- ESLint:作为静态代码检查工具,它定义了一套代码质量规则,用于检测潜在的错误、不符合最佳实践的模式以及不一致的编码风格。通过统一的 ESLint 配置,团队成员能够遵循一致的编程规范,增强代码的可读性和可维护性。
- Prettier:作为代码格式化工具,Prettier 自动按照预设的样式规则对代码进行格式化,消除了因个人偏好引起的代码风格差异,确保了代码在空格、缩进、行长度、引号使用等方面的一致性。
2、自动化代码质量保障:
- Husky:通过 Git 钩子机制,在关键的 Git 操作(如提交)前自动触发预定义的任务。在前端规范工程中,Husky 可以确保在代码提交前执行代码检查和格式化,防止不符合规范的代码进入版本库。
- Lint-staged:配合 Husky 使用,仅针对暂存区中的改动文件运行指定的检查与格式化任务,既节省了资源,又确保只有符合规范的更改才会被提交。
3、规范化提交信息:
- Commitlint:强制执行提交消息的格式约定(如 Conventional Commits),确保提交信息清晰、一致,便于通过提交历史快速理解代码变更的目的、类型和影响范围。这对于生成 changelog、自动化版本管理、识别影响范围(如构建语义化版本)等 DevOps 流程至关重要。
4、标准化提交流程:
- commitizen:提供交互式的命令行工具,引导开发者按照预定义的提问模板编写符合 Commitlint 规范的提交消息。它简化了遵循提交规范的过程,尤其对新加入团队的成员或不熟悉特定提交格式的开发者非常友好。
二、代码书写规范
(一)代码检查工具:ESLint
认识:
ESLint 是一个开源的、强大的静态代码分析工具,主要用于检测 JavaScript 和相关编程语言(如 TypeScript、Vue、React、Angular 等)的代码中潜在的错误、不符合最佳实践的模式以及不一致的编码风格。
网址:
·官网地址:https://eslint.org/
·中文网地址:https://eslint.nodejs.cn/
·eslint-vue配置规则:https://eslint.vuejs.org/rules/
使用方法:
1、需要安装相关依赖如下:
- eslint
- eslint-plugin-import
- eslint-plugin-prettier
- eslint-config-prettier
- eslint-plugin-vue
- vue-eslint-parser
- @typescript-eslint/parser
- @typescript-eslint/eslint-plugin
2、安装方法:
pnpm i -D eslint eslint-plugin-importeslint-plugin-prettier eslint-config-prettier eslint-plugin-vue vue-eslint-parser @typescript-eslint/parser @typescript-eslint/eslint-plugin
3、配置,eslintrc.js
在根目录下新建 .eslintrc.js 文件, rules 里面的内容根据实际需求自行添加,编辑内容如下:
module.exports = {
root: true,
env: {
browser: true,
node: true,
es6: true
},
parser: 'vue-eslint-parser',
extends: [
'eslint:recommended', // 推荐使用eslint基础配置规则
'plugin:vue/vue3-recommended', // 对于Vue3项目,识别vue3语法规则
'plugin:@typescript-eslint/recommended', // 使用推荐的TypeScript 专用 lint 规则
'prettier', // 引入eslint-config-prettier,应用 Prettier 的格式化规则,禁用冲突规则
'plugin:prettier/recommended'// 引入eslint-plugin-prettier,推荐的 prettier 专用 lint 规则
],
parserOptions: {
parser: '@typescript-eslint/parser',
ecmaVersion: 2020,
sourceType: 'module',
jsxPragma: 'React',
ecmaFeatures: {
jsx: true
}
},
/**
* 自定义或覆盖特定规则 https://www.wenjiangs.com/docs/eslint,vue规则:https://eslint.vuejs.org/rules/
* 主要有如下的设置规则,可以设置字符串也可以设置数字,两者效果一致
* 'off' 或 0 - 关闭规则
* 'warn' 或 1 - 开启警告规则,使用警告级别的错误:warn (不会导致程序退出),
* 'error' 或 2 - 开启错误规则,使用错误级别的错误:error (当被触发的时候,程序会退出)
*/
rules: {
'no-debugger': 'off',
'no-case-declarations': 'off', // 防止在 switch 语句的 case 子句中声明变量
'no-console': 'off',
'no-undef': 'off',
'no-useless-escape': 'off',
// 'no-unused-vars': 'off', // 防止出现未使用的变量、函数参数或常量
'no-restricted-imports': 'off',
'no-use-before-define': 'off',
'no-sparse-arrays': 'off',
'compat/compat': 'off',
'no-constant-condition': 'off',
'prefer-const': ['warn', { destructuring: 'all', ignoreReadBeforeAssign: true}],
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
// 检查未使用的变量、函数参数、类属性
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'@typescript-eslint/no-redeclare': 'off',
'@typescript-eslint/no-this-alias': 'off',
'vue/no-setup-props-destructure': 'off',
'vue/script-setup-uses-vars': 'error',
'vue/no-reserved-component-names': 'off',
'vue/custom-event-name-casing': 'off',
'vue/attributes-order': 'off',
'vue/one-component-per-file': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/max-attributes-per-line': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/attribute-hyphenation': 'off',
'vue/require-default-prop': 'off',
'vue/require-explicit-emits': 'off',
'vue/require-toggle-inside-transition': 'off',
'vue/html-self-closing': 'off',
'vue/multi-word-component-names': 'off',
'vue/no-v-html': 'off',
'vue/no-dupe-keys': 'off',
'vue/no-deprecated-v-on-native-modifier': 'off',
'vue/html-indent': 'off',
'vue/no-template-shadow': 'off',
"prettier/prettier":'off'
}
};
4、配置.eslintignore
在根目录下新建.eslintignore文件, 用来让eslint不用检查这些文件
// .eslintignore
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
public
# Editor directories andfiles
.vscode/*
!.vscode/extensions.json
.husky
src/assets/
plop-templates/
package.json
README.md
(二)代码格式化工具:prettier
认识:
Prettier 是一个自动化的代码格式化器,支持多种编程语言,包括 JavaScript、TypeScript、HTML、CSS、JSON、Markdown 等。它的目标是提供一种统一、可配置但非协商式的代码风格,旨在消除团队成员之间因代码风格偏好产生的分歧,提高代码可读性和维护性。
网址:
·官方网址:https://prettier.io/
·prettier中文网:https://www.prettier.cn/
使用方法:
1、安装:
pnpmi -D prettier
2、配置.prettierrc.js
在根目录下新建 .prettierrc.js 文件,里面的内容根据实际需求自行添加,编辑内容如下:
module.exports= {
// 每行最多字符数量,超出换行(默认80)
printWidth: 80,
// 超出打印宽度 (always | never | preserve )
proseWrap: "preserve",
// (x)=>{},单个参数箭头函数是否显示小括号。(always:始终显示;avoid:省略括号。默认:always)
arrowParens: "always",
// 开始标签的右尖括号是否跟随在最后一行属性末尾,默认false
bracketSameLine: false,
// 对象字面量的括号之间打印空格 (true - Example: { foo: bar } ; false - Example: {foo:bar})
bracketSpacing: true,
// 是否格式化一些文件中被嵌入的代码片段的风格(auto|off;默认auto)
embeddedLanguageFormatting: "auto",
// 指定 HTML 文件的空格敏感度 (css|strict|ignore;默认css)
htmlWhitespaceSensitivity: "css",
// 当文件已经被 Prettier 格式化之后,是否会在文件顶部插入一个特殊的 @format 标记,默认false
insertPragma: false,
// 在 JSX 中使用单引号替代双引号,默认false
jsxSingleQuote: false,
// 对象属性是否使用引号(as-needed | consistent | preserve;默认as-needed:对象的属性需要加引号才添加;)
quoteProps: "as-needed",
// 是否只格式化在文件顶部包含特定注释(@prettier| @format)的文件,默认false
requirePragma: false,
// 结尾添加分号
semi: true,
// 使用单引号 (true:单引号;false:双引号)
singleQuote: true,
// 缩进空格数,默认2个空格
tabWidth: 2,
// 元素末尾是否加逗号,默认es5: ES5中的 objects, arrays 等会添加逗号,TypeScript 中的 type 后不加逗号
trailingComma: "es5",
// 指定缩进方式,空格或tab,默认false,即使用空格
useTabs: false,
// vue 文件中是否缩进 <style> 和 <script> 标签,默认 false
vueIndentScriptAndStyle: false,
// 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
endOfLine: "auto",
};
3、配置.prettierignore
在根目录下新建.prettierignore文件, 用来让prettier不用格式化这些文件
/dist/*
/node_modules/**
/public/*
src/assets/
.prettierrc.js
.eslintrc.js
commitlint.config.js
README.md
package.json
vite.config.ts
# Editor directories andfiles
.vscode/*
!.vscode/extensions.json
.husky/*
(三)Vscode插件 和设置:
Vscode插件:
在vscode插件栏,搜索并安装如下插件;
1、eslint:提供eslint的校验,保存自动修复功能
2、prettier:提供prettier 保存自动格式化的功能
扩展:
项目自身配置eslint、prettier插件与vscode安装插件关系:
相辅相成,共同作用于项目格式化校验和修复;项目自身配置eslint、prettier插件的格式化规范,其优先级要高于vscode安装的eslint、prettier插件;
Vscode设置:
在项目根目录下新建.vscode/setting.json文件,写入以下内容,即可在保存代码的时候自动按照eslint和prettier的规范进行代码格式化
{
// 设置npm包管理器为pnpm
"npm.packageManager": "pnpm",
// 设置编辑器的Tab大小为2个空格
"editor.tabSize": 2,
// 配置编辑器在每次保存时自动格式化代码
"editor.formatOnSave": true,
// 设置默认的代码格式化工具为prettier
"editor.defaultFormatter": "esbenp.prettier-vscode",
// 配置编辑器在保存时执行的代码动作,包括应用所有明确的修复
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit"
},
// 自定义拼写检查词典,包含一些特定的技术词汇
"cSpell.words": [
"lint-staged",
"lintstagedrc",
"persistedstate",
"pinia",
"pnpm",
"stylelint",
],
}
三、代码提交规范
前提:
需要安装如下依赖:
- husky
- lint-staged
- commitizen
- cz-git
- @commitlint/cli
- @commitlint/config-conventional
(一)husky
认识:
Husky 是一个 Git 钩子管理工具,允许您在 Git 的各个生命周期事件(如 pre-commit、pre-push、post-commit 等)中轻松添加自定义脚本。通过 Husky,您可以确保在提交代码之前执行必要的代码检查(如 ESLint、Prettier、单元测试等),防止不符合规范或存在错误的代码进入版本库。这有助于提高代码质量和维护项目的整洁性。
网址:https://typicode.github.io/husky/
使用方法:
1、配置husky
执行下面两行代码
npm pkg setscripts.prepare="husky install"// 在 package.json 中添加脚本
npm run prepare // 初始化 husky,将 git hooks 钩子交由 husky 执行
执行完这两行代码以后,发生了两件事情:
第一个是package.json中新增了一个脚本
"scripts": {
"prepare": "husky install"
},
第二个是根目下新增了 .husky 文件夹;
2、配置pre-commit
2.1、新建pre-commit文件并添加内容
在.husky文件夹下新建pre-commit 文件,然后添加以下内容:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
pnpm execlint-staged
作用:是在执行git commit时,先加载husky的内部脚本,然后使用pnpm执行lint-staged命令,对暂存区中的代码进行代码风格检查和格式化,确保符合项目规范后再提交。
2.2、配置lint-staged
在package.json中,添加如下配置,表示通过eslint来自动修复各类文件格式;
"lint-staged": {
"*.{vue,js,ts,jsx,tsx,md,json}": "eslint --fix"
}
扩展:
Lint-Staged:是一个在 Git暂存区(staging area)中运行 linters 的工具。当您准备提交代码时,Lint-Staged 仅针对已暂存的文件运行指定的 linting 工具(如 ESLint、Prettier、stylelint 等)。这样可以确保只有即将被提交的改动经过了严格的质量检查,避免了对整个项目进行 linting 所带来的性能开销,同时也确保每次提交都是干净且符合规范的。
配置只校验暂存区文件:
在package.json中,添加如下命令,表示只校验暂存区代码格式;
"scripts": {
"lint:lint-staged": "lint-staged"
}
3、效果展示
到这里为止,代码提交时,代码自动检测功能已经实现了,来测试一下效果。
如图:有三处问题:
- 12行,错误提示“已声明“name”,但从未读取其值” eslint@typescript-eslint/no-unused-vars
- 13行,错误提示:Unexpected constant condition.eslintno-constant-condition
- 13~14行"{}",错误提示:Empty block statement.eslint no-empty
提交一下,看一看结果会怎么样?
修改代码后,进行第二次测试;
重新提交,这次提交成功了;结果如下:
结论:通过配置husky和eslint,提交代码的时候会自动进行格式化,如果有格式化解决不了的错误,就会报错。
(二)commitlint
认识:
Commitlint 是一个用于验证 Git 提交消息格式的工具,基于社区广泛接受的 Conventional Commits 规范。它确保提交消息遵循一定的结构和约定,如明确的类型(fix、feat、docs、chore 等)、简洁的描述、可选的正文和 footer 等。通过规范化提交消息,Commitlint 帮助生成清晰、一致的 changelog,便于追踪项目变更历史,辅助自动化版本发布流程(如通过 semantic-release)。
网址:https://commitlint.js.org/
使用方法:
1、配置commit-msg
新建commit-msg文件并添加内容
在.husky文件夹下新建commit-msg 文件,然后添加以下内容:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
pnpm execcommitlint --edit "${1}"
作用:使用 pnpm 执行 commitlint 命令,根据 commitlint.config.js 中的配置检查提交消息格式是否符合项目规范。如果提交消息不符合规范,commitlint 尝试自动修正消息内容,以便用户在继续提交时获得符合规范的消息。
2、配置commitlint.config.js
在项目跟目录下新建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: " "}
],
}
};
作用:commitlint.config.js 是 commitlint 工具的核心配置文件,它用于定义项目中 Git 提交消息(commit messages)应遵循的格式规则和规范。
3、配置commit提交命令
在package.json中,添加如下配置,用于自动提交全部文件并执行 git-cz
// package.json
"scripts": {
"commit": "git pull && git add -A && git-cz && git push",
},
"config": {
"commitizen": {
"path": "node_modules/cz-git"
}
}
扩展:
Commitizen:是一个辅助创建符合 Conventional Commits 规范的 Git 提交消息的命令行工具。它提供了一个交互式的提交流程,引导开发者按照规定的格式输入提交信息,包括类型、scope(可选)、描述、正文(可选)和 footer(可选)。
cz-git:这个包名表明它可能是一个与Git结合使用的Commitizen插件或适配器。一般来说,Commitizen通过cz命令行工具与用户交互,引导他们按照预定义的格式填写提交信息。
4、效果展示:
到这里为止,所有的配置都完成了,来测试一下效果;
使用pnpm run commit来执行代码默认自动提交;
代码提交完成且没有报错提示。
四、总结
1、通过使用 ESLint +Prettier+ Husky + Lint-staged+Commitlint + Commitizen这套方案,它能够在提升代码质量,保障团队协作效率,统一项目代码风格,强化代码审查流程,支持自动化运维 和促进项目管理等方面发挥重要作用,同时为项目的长期稳健发展打下坚实基础。
2、需要安装的依赖包及配置如下:
{
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build:dev": "vite build --mode development",
"build:test": "vite build --mode test",
"build:prod": "vite build --mode production",
"lint:lint-staged": "lint-staged",
"commit": "git pull && git add -A && git-cz && git push",
"prepare": "husky install"
},
"dependencies": {
"vue": "^3.4.23",
},
"devDependencies": {
"@commitlint/cli": "^17.3.0",
"@commitlint/config-conventional": "^17.8.1",
"@typescript-eslint/eslint-plugin": "^5.32.0",
"@typescript-eslint/parser": "^5.32.0",
"commitizen": "^4.3.0",
"cz-git": "^1.3.12",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-vue": "^9.25.0",
"husky": "^9.0.11",
"lint-staged": "^15.2.2",
"prettier": "^3.2.5",
"typescript": "^4.6.4",
"vue-eslint-parser": "^9.4.2",
},
"config": {
"commitizen": {
"path": "node_modules/cz-git"
}
},
"lint-staged": {
"*.{vue,js,ts,jsx,tsx,md,json}": "eslint --fix"
}
}
版权声明:本文由神州数码云基地团队整理撰写,若转载请注明出处。
公众号搜索神州数码云基地,了解更多技术干货。