前端开发规范(三)-Git规范化提交

前言

在开发团队协作中,“开发规范” 是经常被讨论的话题。当然,除了代码上的规范,还有一个很重要的规范就是“提交规范”。

规范化提交的目的:

  • 提交统一的、有规则的信息;而不是混乱的、看不懂是什么意思的信息
  • 可以提供更加明朗的历史信息,便于后续快速定位问题、代码回滚等的操作
  • 可以自动化生成changelog

husky

husky 是一个 Git-Hooks 工具. 那么 hooks 是什么呢 ?

“hooks” 直译是 “钩子”,它并不仅是 react,甚至不仅是前端界的专用术语,而是整个行业所熟知的用语。通常指:系统运行到某一时期时,会调用被注册到该时机的回调函数。

规范化提交第一步就是要在 git commit之前先做一次Lint校验,限制不规范代码的提交,那 husky 这个工具就能做到。husky 继承了Git下所有的钩子,在触发pre-commit钩子的时候,阻止不合法的 commit、push 等等。

husky 配置

1、初始化 git 目录

如果没有先初始化 git, 需要重新装husky。

git init -y
2、安装 husky
# pnpm 安装
pnpm add husky  -D -w

# or npm 安装
npm install husky -D
3、添加 husky 脚本

package.json 文件 scripts 中手动添加

"prepare": "husky install"

或者直接执行命令添加

npm set-script prepare "husky install"

prepare 脚本会在npm install(不带参数)之后自动执行。也就是说当我们执行npm install安装完项目依赖后会执行 husky install命令,该命令会创建.husky/目录并指定该目录为git hooks所在的目录。

4、执行 npm run prepare

执行之后会发现项目根目录多了个.husky的目录及文件。

在该目录下添加 pre-commitcommit-msg 文件。

lint-staged

如果直接在 pre-commit 里执行 eslint --fix ,可能只是修改了一个文件,但依然会检查项目中所有需要检验的文件,体验非常的不友好。导致的问题就是:每次提交代码,无论改动多少,都会检查整个项目下的文件,当项目大了之后,检查速度也会变得越来越慢。

解决这个问题就需要用到lint-staged,lint-staged能够让lint只检测暂存区的文件

# pnpm 安装
pnpm add lint-staged  -D -w

# or npm 安装
npm install lint-staged -D

1、在 package.json 文件添加配置

"lint-staged": {
    "*.{js,jsx,ts,tsx,vue}": "eslint --fix"
}

2、在 pre-commit 添加脚本

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

pnpm exec lint-staged

# 当然也可以配合 prettier pretty-quick 这个两个插件来做代码格式化的修复,不需要可不用
# pnpm exec pretty-quick --staged

这样在使用 git commit 之前,会先执行 ./.husky/pre-commit 下的脚本,实现提交前的拦截修复

commitlint

在多人协作的项目中,每个人的 Commit message 可能都会不同,没有很明确的限定哪些是新增功能,哪些修复bug,哪些优化代码等等,那这时候就需要装一些工具来规范Commit message

需要使用的插件:

  • @commitlint/cli
  • @commitlint/config-conventional
  • czg
# pnpm 安装
pnpm add @commitlint/cli @commitlint/config-conventional czg  -D -w

# or npm 安装
npm install @commitlint/cli @commitlint/config-conventional czg -D

1、在 package.json 文件添加配置

"config": {
    "commitizen": {
      "path": "./node_modules/cz-git"
    }
}

2、在 package.json 文件添加脚本

 "cz": "git add . && czg",

3、在根目录加上 commitlint.config.js

module.exports = {
    extends: ['@commitlint/config-conventional'],
    rules: {
        // 'scope-enum': [2, 'always', scopes],
        'body-leading-blank': [1, 'always'],
        'footer-leading-blank': [1, 'always'],
        'header-max-length': [2, 'always', 72],
        'scope-case': [2, 'always', 'lower-case'],
        'subject-case': [
            1,
            'never',
            ['sentence-case', 'start-case', 'pascal-case', 'upper-case'],
        ],
        'subject-empty': [2, 'never'],
        'subject-full-stop': [2, 'never', '.'],
        'type-case': [2, 'always', 'lower-case'],
        'type-empty': [2, 'never'],
        'type-enum': [
            2,
            'always',
            [
                'feat',
                'fix',
                'style',
                'improvement',
                'perf',
                'build',
                'chore',
                'ci',
                'docs',
                'test',
                'refactor',
                'revert',
            ],
        ],
        'subject-full-stop': [0, 'never'],
        'subject-case': [0, 'never'],
    }
}

4、在 commit-msg添加脚本

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

pnpm exec commitlint --config commitlint.config.js --edit "${1}"

5、执行 npm run cz 就能看到效果了

自动生成 CHANGELOG

1、全局安装 conventional-changelog-cli 插件

npm install conventional-changelog-cli -g

2、在 package.json 文件添加脚本

"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0 -n ./changelog-option.js"

参数说明:

  • -p angular,表示changelog标准为angular,现在有angular, atom, codemirror, ember, eslint, express, jquery 等项目的标准可供选择
  • -i CHANGELOG.md,表示指定输出的文件名称
  • -s 输出到infile,这样就不需要指定与outfile相同的文件
  • -r 从最新的版本的生成,默认值为1。如果为0,则将重新生成整个更新日志并覆盖输出文件
  • -n ./changelog-option.js 表示指定自定义配置文件

3、执行 npm run changelog 就可以在根目录下看到 CHANGELOG.md 文件了

4、如果想自定义输出模板,可以在配置 changelog-option.js 文件

在根目录新建 changelog-option.js文件 和 templates/commit.hbs 文件

changelog-option.js文件:

const readFileSync = require('fs').readFileSync
const join = require('path').join

module.exports = {
    gitRawCommitsOpts: {
        format: '%B%n-hash-%n%H%n-gitTags-%n%d%n-committerDate-%n%ci%n-authorName-%n%an%n-authorEmail-%n%ae',
    },
    writerOpts: {
        commitPartial: readFileSync(
            join(__dirname, 'templates/commit.hbs'),
            'utf-8'
        ),
        // mainTemplate:  readFileSync(join(__dirname, 'templates/template.hbs'),'utf-8'),
        // headerPartial: readFileSync(join(__dirname, 'templates/header.hbs'),'utf-8'),
        // footerPartial: readFileSync(join(__dirname, 'templates/footer.hbs'),'utf-8'),
        ...getWriterOpts(),
    },
}

function getWriterOpts() {
    return {
        transform: (commit, context) => {
            let discard = true
            const issues = []

            commit.notes.forEach((note) => {
                note.title = 'BREAKING CHANGES'
                discard = false
            })
            if (commit.type === 'feat') {
                commit.type = '✨ Features | 新功能'
            } else if (commit.type === 'fix') {
                commit.type = '🐛 Bug Fixes | Bug 修复'
            } else if (commit.type === 'perf') {
                commit.type = '⚡ Performance Improvements | 性能优化'
            } else if (commit.type === 'revert' || commit.revert) {
                commit.type = '⏪ Reverts | 回退'
            } else if (commit.type === 'improvement') {
                commit.type = '💩 Improvement | 优化改进'
            } else if (commit.type === 'style') {
                commit.type = '💄 Styles | 风格'
            } else if (discard) {
                return
            } else if (commit.type === 'docs') {
                commit.type = '📝 Documentation | 文档'
            } else if (commit.type === 'refactor') {
                commit.type = '♻ Code Refactoring | 代码重构'
            } else if (commit.type === 'test') {
                commit.type = '✅ Tests | 测试'
            } else if (commit.type === 'build') {
                commit.type = '👷‍ Build System | 构建'
            } else if (commit.type === 'ci') {
                commit.type = '🔧 Continuous Integration | CI 配置'
            } else if (commit.type === 'chore') {
                commit.type = '🎫 Chores | 其他更新'
            }

            if (commit.scope === '*') {
                commit.scope = ''
            }

            if (typeof commit.hash === 'string') {
                commit.hash = commit.hash.substring(0, 7)
            }

            if (typeof commit.subject === 'string') {
                let url = context.repository
                    ? `${context.host}/${context.owner}/${context.repository}`
                    : context.repoUrl

                if (url) {
                    url = `${url}/issues/`
                    // Issue URLs.
                    commit.subject = commit.subject.replace(
                        /#([0-9]+)/g,
                        (_, issue) => {
                            issues.push(issue)
                            return `[#${issue}](${url}${issue})`
                        }
                    )
                }

                if (context.host) {
                    // User URLs.
                    commit.subject = commit.subject.replace(
                        /\B@([a-z0-9](?:-?[a-z0-9/]){0,38})/g,
                        (_, username) => {
                            if (username.includes('/')) {
                                return `@${username}`
                            }

                            return `[@${username}](${context.host}/${username})`
                        }
                    )
                }
            }

            // remove references that already appear in the subject
            commit.references = commit.references.filter((reference) => {
                if (issues.indexOf(reference.issue) === -1) {
                    return true
                }
                return false
            })
            return commit
        },
        groupBy: 'type',
        commitGroupsSort: 'title',
        commitsSort: ['scope', 'subject'],
        noteGroupsSort: 'title',
    }
}

templates/commit.hbs文件:

- {{header}}

{{~!-- commit link --}} {{#if @root.linkReferences~}}
  ([#{{hash}}](
  {{~#if @root.repository}}
    {{~#if @root.host}}
      {{~@root.host}}/
    {{~/if}}
    {{~#if @root.owner}}
      {{~@root.owner}}/
    {{~/if}}
    {{~@root.repository}}
  {{~else}}
    {{~@root.repoUrl}}
  {{~/if}}/
  {{~@root.commit}}/{{hash}}) by @{{authorName}})
{{~else}}
  {{~hash}}
{{~/if}}

{{~!-- commit references --}}
{{~#if references~}}
  , closes
  {{~#each references}} {{#if @root.linkReferences~}}
    [
    {{~#if this.owner}}
      {{~this.owner}}/
    {{~/if}}
    {{~this.repository}}#{{this.issue}}](
    {{~#if @root.repository}}
      {{~#if @root.host}}
        {{~@root.host}}/
      {{~/if}}
      {{~#if this.repository}}
        {{~#if this.owner}}
          {{~this.owner}}/
        {{~/if}}
        {{~this.repository}}
      {{~else}}
        {{~#if @root.owner}}
          {{~@root.owner}}/
        {{~/if}}
          {{~@root.repository}}
        {{~/if}}
    {{~else}}
      {{~@root.repoUrl}}
    {{~/if}}/
    {{~@root.issue}}/{{this.issue}})
  {{~else}}
    {{~#if this.owner}}
      {{~this.owner}}/
    {{~/if}}
    {{~this.repository}}#{{this.issue}}
  {{~/if}}{{/each}}
{{~/if}}

5、生成效果如下

changelog.jpg

流程说明

  • commit(规范化提交)
  • release(发布版本)
  • tag(创建tag,提交代码到远程仓库)
  • npm run changelog (生成 CHANGELOG)
  • 提交 CHANGELOG.MD

参考文档

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值