前言 Code standard 代码规范
是前端工程化落地的基石,用于约束 编码规范
和 编码风格
,它的好处是:
强制规范代码风格统一,保持一样的编码习惯
增加代码的可维护性和可接入性,即使有新成员的加入也能快速适应项目的架构与需求
保障项目的整体质量,可减少无用代码、重复代码、错误代码和 BUG 代码的产生几率
Git Commit Message commit message
是开发的日常操作, 写好 log
不仅有助于他人 review
, 还可以有效的输出 CHANGELOG
, 对项目的管理实际至关重要, 但是实际工作中却常常被大家忽略.
那如何在前端项目中统一 代码规范 ,以及 Commit Message 的规范? 我这边结合实际项目以及目前较受欢迎的相关工具,给大家分享一套解决方案。
使用到的工具:
eslint :JavaScript 代码检测工具,检测并提示错误或警告信息
prettier :代码自动格式化工具,更好的代码风格效果
husky :Git hooks 工具, 可以在执行 git 命令时,执行自定义的脚本程序
lint-staged :对暂存区 (git add) 文件执行脚本检测校验
commitlint :帮助你的团队遵守提交约定。通过支持 npm 安装的配置,它可以很容易地共享提交约定。
配置 eslint & prettier 安装 1 2 pnpm i eslint prettier @typescript-eslint/parser -D pnpm i @typescript-eslint/eslint-plugin -D
可以在 vscode 插件中安装 Prettier 和 Eslint 插件,在设置中开启保存文件则进行格式化;
也可配置 scripts 命令(这会检测你指定目录的所有文件,当然会比较耗时)。
另外可结合 lint-staged 只对 commited 后的文件进行格式化重写(见下面的 pre-commit git-hooks 触发)
1 2 3 4 5 6 7 { "scripts" : { "lint" : "eslint src --ext js,jsx,.vue,.ts,.tsx --report-unused-disable-directives --max-warnings 0" , "format" : "prettier --write src/**/*/*.{js,jsx,ts,tsx}" } }
通过配置文件,统一代码格式化的格式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 module .exports = { env : { browser : true , es2021 : true }, extends : ['eslint:recommended' , 'plugin:@typescript-eslint/recommended' ], parser : '@typescript-eslint/parser' , parserOptions : { ecmaVersion : 'latest' , sourceType : 'module' }, plugins : ['@typescript-eslint' ], rules : {} };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 module .exports = { semi : true , trailingComma : 'none' , singleQuote : true , printWidth : 120 , arrowParens : 'avoid' };
Commit Message 规范 我们在使用 Git 托管代码时,规范化的 Commit Message 可以帮助大家直观清晰地理解每次修改的内容,不仅能帮助别人 Review,还可以有效地输出 ChangeLog。那么要想前端工程化项目更易于维护,最好有一套 Git 提交说明的 规范化模板
目前最受开发人员肯定的规范是前端框架 Angular 提出的 Angular 提交信息规范 ,包含 页眉(header)、正文(body)和页脚(footer),每次提交必须包含页眉内容,每次提交的信息不超过 100 个字符,提交格式如下:
1 2 3 4 5 <type >(<scope>): <subject> <BLANK LINE> <body> <BLANK LINE> <footer>
页眉设置 页眉的格式指定为提交类型(type)、作用域(scope,可选)和主题(subject)
提交类型(type) 提交类型指定为下面其中一个:
feat:新增功能
fix:Bug 修复
docs:文档更新
ci:脚本更新,修改了 CI 配置文件或脚本
pref:性能优化,提高性能的代码更改
build:更新构建,构建系统或者外部依赖项进行了修改、更新
test:新增测试,增加确实的测试或者矫正已存在的测试
refactor:代码重构,非新增功能也非修复缺陷
chore:事务变动,改动其他不影响代码的事务
revert:代码回滚,撤销某次代码提交
merge:分支合并,合并分支代码到其他分支
style:格式变动,不影响代码逻辑
release:版本发布
作用域(scope,可选) 用于说明 commit 的影响范围(选填) (比如按下面这些划分方案填写)
按功能划分(如:数据层 - Data、视图层 - View 和 控制层 - Control)
按交互层划分(如:组件 - Component、布局 - Layout、流程 - Flow、视图 - View 和 页面 - Page)
按改动文件划分(如:某个文件 - Button.tsx,全部改动 -*)
主题(subject) 用于说明 commit 的细节描述(选填)。以精炼简洁的文字(中文还是英文就看各自规定了,一般推荐使用英文)说明提交的改动,比如可以遵循这些规则:
以动词开头(如:update,更新)
使用第一人称现在时
首字母无需大写,重点区分的(如某组件)首字母无需大写
不以句号(. / 。)结尾
commit 信息示例:
1 2 3 feat(Component): add Layout component feat(Button.tsx): change the default size of the button fix(EmitEvent): handle event on blur (closes #28)
正文设置 和主题设置类似,使用命令式、现在时态
应该包含修改的动机以及和之前行为的对比
页脚设置 Breaking changes 不兼容修改指的是本次提交修改了不兼容之前版本的 API 或者环境变量
所有不兼容修改都必须在页脚中作为中断更改块提到,以 BREAKING CHANGE:开头,后跟一个空格或者两个换行符,其余的信息就是对此次修改的描述,修改的理由和修改注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 BREAKING CHANGE: isolate scope bindings definition has changed and the inject option for the directive controller injection was removed. To migrate the code follow the example below: Before: 。。。 。。。 After: 。。。 。。。 The removed `inject` wasn't generaly useful for directives so there should be no code using it.
引用提交的问题 如果本次提交目的是修改 issue 的话,需要在页脚引用该 issue
以关键字 Closes 开头,比如
如果修改了多个 bug,以逗号隔开
回滚设置 当此次提交包含回滚(revert)操作,那么页眉以”revert:”开头,同时在正文中添加”This reverts commit hash”,其中 hash 值表示被回滚前的提交
1 2 3 4 5 6 revert:<type >(<scope>): <subject> <BLANK LINE> This reverts commit hash <other-body> <BLANK LINE> <footer>
方案实现: husky+commitlint
安装 1 2 pnpm i husky lint-staged @commitlint/cli @commitlint/config-conventional -D
初始化 husky,创建 .husky 文件夹(本质就是创建要触发的 git-hooks) 1 2 3 4 5 6 7 8 9 10 11 12 npx husky install npx husky-init
修改 .husky/pre-commit 文件: 1 2 3 4 5 6 #!/usr/bin/env sh . "$(dirname -- "$0 " ) /_/husky.sh" npx lint-staged
配置 package.json 文件内容 1 2 3 4 5 6 7 8 9 { "lint-staged" : { "*.{js,jsx,vue,ts,tsx}" : [ "prettier --write" , "eslint --fix --ext .js,.jsx,.vue,.ts,.tsx" ] } }
创建 .eslintrc.cjs 文件和 .pretterrc.cjs 文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 module .exports = { env : { browser : true , es2021 : true }, extends : ['eslint:recommended' , 'plugin:@typescript-eslint/recommended' ], parser : '@typescript-eslint/parser' , parserOptions : { ecmaVersion : 'latest' , sourceType : 'module' }, plugins : ['@typescript-eslint' ], rules : {} };
1 2 3 4 5 6 7 8 module .exports = { semi : false ; singleQuote : true ; printWidth : 80 ; trailingComma : 'none' ; arrowParens : 'avoid' ; }
在 .husky 目录下添加 commit-msg hook 1 2 npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'
1 2 3 4 5 6 7 8 . "$(dirname -- "$0 " ) /_/husky.sh" npx --no -- commitlint --edit ""
创建 commitlint.config.cjs 文件 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 31 32 33 34 35 36 37 38 39 const types = [ 'build' , 'ci' , 'chore' , 'docs' , 'feat' , 'fix' , 'pref' , 'refactor' , 'revert' , 'style' , 'test' ], typeEnum = { rules : { 'type-enum' : [2 , 'always' , types] }, value : () => { return types; } }; module .exports = { extends : ['@commitlint/config-conventional' ], rules : { 'type-enum' : typeEnum.rules ['type-enum' ], 'subject-full-stop' : [0 , 'never' ], 'subject-case' : [0 , 'never' ] } };
配置完毕,测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ➡️ git commit -m "tes: 错误示例 🙅<200d>♂️" ✔ Preparing lint-staged... ✔ Running tasks for staged files... ✔ Applying modifications from tasks... ✔ Cleaning up temporary files... Commit Msg: tes: 错误示例 🙅♂️ ERROR invalid commit message format. Proper commit message format is required for automated changelog generation. Examples: feat(compiler): add 'comments' option fix(input): handle events on blur (close See .github/commit-convention.md for more details. husky - commit-msg hook exited with code 1 (error)
当然,整体来说也只是一个推荐性的规范方案,commit 同样可以通过 -n 或者是 -no-verify 直接绕开 commit 检测
添加 ChangeLog 安装 1 2 pnpm i conventional-changelog-cli -D
配置 scripts (packages.json) 1 2 3 4 5 { "scripts" : { "changelog" : "conventional-changelog -p angular -i CHANGELOG.md -s" } }
conventional-changelog-cli 不会覆盖任何以前的变更日志。 新增的日志基于自上一个 commit 的 “Feature”, “Fix”, “Performance Improvement” 或 “Breaking Changes”
另外:结合 husky 和自定义 verifyCommits.js 来实现 主要处理逻辑是,修改.husky/commit-commit
文件中的执行语句
1 2 3 4 5 6 7 8 . "$(dirname -- "$0 " ) /_/husky.sh" node scripts/verifyCommits.js "$1 "
$1
必须在 .husky/commit-msg
文件中写上(在我们使用 git commit -m "[msg]"
时,会传递给 $1
参数),否则我们执行自定义校验 commit 文件时是无法拿到 commit 信息 的(当时花费好长时间去踩这个问题点)
创建 scripts/verifyCommits.js 文件 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 const chalk = require ('chalk' );const msgPath = process.env .GIT_PARAMS || process.env .HUSKY_GIT_PARAMS || process.argv [2 ]; const msg = require ('fs' ).readFileSync (msgPath, 'utf-8' ).trim ();console .log (); console .log (chalk.bgBlueBright .white ('Commit Msg: ' ), msg);console .log (); const commitRE = /^(revert: )?(feat|fix|docs|style|refactor|perf|test|workflow|ci|chore|types|build)(\(.+\))?: .{1,50}/ ; if (!commitRE.test (msg)) { console .error ( ` ${chalk.bgRed.white(' ERROR ' )} ${chalk.red( `提交日志不符合规范` )} \n\n${chalk.red( ` 合法的提交日志格式如下(emoji 和 模块可选填):\n\n` )} ${chalk.green(`[<emoji>] [revert: ?]<type>[(scope)?]: <message>\n` )} ${chalk.green(`💥 feat(模块): 添加了个很棒的功能` )} ${chalk.green(`🐛 fix(模块): 修复了一些 bug` )} ${chalk.green(`📝 docs(模块): 更新了一下文档` )} ${chalk.green(`🌷 UI(模块): 修改了一下样式` )} ${chalk.green(`🏰 chore(模块): 对脚手架做了些更改` )} ${chalk.green(`🌐 locale(模块): 为国际化做了微小的贡献\n` )} ${chalk.green( `其他提交类型: refactor, perf, workflow, build, CI, typos, tests, types, wip, release, dep\n` )} ${chalk.red( `See https://github.com/vuejs/core/blob/main/.github/commit-convention.md\n` )} ` ); process.exit (1 ); }