前端工程化一种实现方案

1,492 阅读10分钟

commit规范化方案

现阶段我们团队的项目没有严格统一的方案,为了提高效率、规范项目,急需对开发以及提交制定好规范,特有此文。

规范介绍以及制定

社区中的commit message规范众多,我们团队选择 Angular 规范 ,因为该规范配套工具齐全,且使用最为广泛。

其规范大致如下:

每次提交,message都应该包含三个部分:Header、Body、Footer。格式如下:

ItemValue
type(scope): subject# Header 必填,其中 type 和 subject 必填。
空一行
72-character wrapped.# Body 选填。对本次commit的详情描述,可以多行
空一行
BREAKING CHANGE: msg.# Footer 选填。主要用于版本回滚或绑定issue

这样写的commit类似于文档或者注释,当然如果在查看log不想全部行展示时,执行git log --oneline即可只展示一行

Angular规范的基础上,我们项目需要在message前添加JIRA编号,如“AA-0000 #comment (something)”, 所以我们最终的规范是基于Augular的规范而实现的自定义

有了规范总不能次次手敲,能借助工具还是要借助工具

commitizen

commitizen是一个格式化commit message的工具

  • 自定义配置

cz-customizable

  • 是一个可以实现自定义的工具,为了使用我们制定的message规范,我们需要自己实现插件

@undefined0_0/cz-customizable

  • 是我fork自cz-customizable的以便可以在我们项目组中使用的工具

安装

# commitizen工具建议全局安装
npm i -g commitizen
# 项目级安装
npm i -D @undefined0_0/cz-customizable
  • 在package.json添加配置项
// package.json
"config": {
  "commitizen": {
    "path": "./node_modules/@undefined0_0/cz-customizable"
  }
},

然后在项目根目录下新建.cz-config.js文件,按照官方给的实例文件 cz-config-EXAMPLE.js 如下所示:

进行定制化修改,如去掉不使用的value,定制scope,或者汉化等等

// .cz-config.js
module.exports = {
  jiraMode: true, // 是否开启 JIRA 校验,默认为false
  jiraPrefix: '', // JIRA 项目编号,会添加在需求号前,`${jiraPrefix}-0000 #comment (something)`
  types: [
    { value: 'feat', name: 'feat:     A new feature' },
    { value: 'fix', name: 'fix:      A bug fix' },
    { value: 'docs', name: 'docs:     Documentation only changes' },
    {
      value: 'style',
      name:
        'style:    Changes that do not affect the meaning of the code\n            (white-space, formatting, missing semi-colons, etc)',
    },
    {
      value: 'refactor',
      name: 'refactor: A code change that neither fixes a bug nor adds a feature',
    },
    {
      value: 'perf',
      name: 'perf:     A code change that improves performance',
    },
    { value: 'test', name: 'test:     Adding missing tests' },
    {
      value: 'chore',
      name:
        'chore:    Changes to the build process or auxiliary tools\n            and libraries such as documentation generation',
    },
    { value: 'revert', name: 'revert:   Revert to a commit' },
    { value: 'WIP', name: 'WIP:      Work in progress' },
  ],

  scopes: [{ name: 'accounts' }, { name: 'admin' }, { name: 'exampleScope' }, { name: 'changeMe' }],

  allowTicketNumber: false,
  isTicketNumberRequired: false,
  ticketNumberPrefix: 'TICKET-',
  ticketNumberRegExp: '\\d{1,5}',

  // it needs to match the value for field type. Eg.: 'fix'
  /*
  scopeOverrides: {
    fix: [
      {name: 'merge'},
      {name: 'style'},
      {name: 'e2eTest'},
      {name: 'unitTest'}
    ]
  },
  */
  // override the messages, defaults are as follows
  messages: {
    type: "Select the type of change that you're committing:",
    scope: '\nDenote the SCOPE of this change (optional):',
    // used if allowCustomScopes is true
    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):\n',
    footer: 'List any ISSUES CLOSED by this change (optional). E.g.: #31, #34:\n',
    confirmCommit: 'Are you sure you want to proceed with the commit above?',
  },

  allowCustomScopes: true,
  allowBreakingChanges: ['feat', 'fix'],
  // skip any questions you want
  skipQuestions: ['body'],

  // limit subject length
  subjectLimit: 100,
  // breaklineChar: '|', // It is supported for fields body and footer.
  // footerPrefix : 'ISSUES CLOSED:'
  // askForBreakingChangeFirst : true, // default is false
};

一般来说,到这一步其实已经够用了,如果愿意规范自己的代码和commit,但如果没有规则还是会有错误的提交,接下来就继续对commit做校验

commitlint校验

@commitlint/config-conventional

  • 是预设的配置

commitlint-config-cz

  • 自定义

commitlint-with-demand

  • 是为了格式化我们规范下的message而写的commitlint插件

commitlint-plugin-with-jira-issue

  • 是为了校验我们的message是否符合我们的规范而写的commitlintrule
npm i -D @commitlint/config-conventional @commitlint/cli commitlint-config-cz commitlint-with-demand commitlint-plugin-with-jira-issue

然后在项目根目录新建lint配置文件.commitlintrc.js,其实别的格式也可以。

// .commitlintrc.js
module.exports = {
  extends: [
    // 使用预设的配置
    '@commitlint/config-conventional',
    'cz'
  ],
  plugins: [
    'with-jira-issue'
  ],
  parserPreset: 'commitlint-with-demand',
  // 改变预设中的提交类型
  // rule由配置项和配置数组组成,数组中第一位为 level 表示规则状态,0为禁用规则,1为警告,2为错误,第二位为应用与否,可选 always|never ,第三位该rule的值
  rules: {
    'type-enum': [2, 'always', [
      // 只有包含如下type的message才被通过
      'feat',
      'update',
      'fix',
      // ...
    ]],
    // ...
    'with-jira-issue': [2, 'always'],
    // ...
    // 其余配置不再赘述,移步官方文档查看
  }
};
// .demand.js
// Your JIRA number
const JIRA_PROJECT = '';
module.exports = {
  JIRA_PROJECT
};

commitlint相关文档

简介

官方文档

配置

预设配置

代码规范

eslint

vue3用脚手架搭建项目时会自带lint工具

或者自行安装eslint

# eslint
npm i -D eslint babel-eslint eslint-plugin-vue

eslint

  • 校验代码的核心

babel-eslint

  • babel 插件,用 babel 解析 js 文件

eslint-plugin-vue

  • vue 官方的 eslint 插件
// package.json
"scripts": {
  "lint": "vue-cli-service lint src/**/*.js" // 不愿自动修复 带上(--no-fix)
  // "lint": "eslint src"
}

.eslint.js 文件

是eslint校验的规则,使用项目标准即可。extends 是基础配置,rules是自定义的设置

例如

// .eslintrc.js 
module.exports = {
  // 此项是用来配置标准的js风格
  "extends": "standard",
  // add your custom rules here
  // 下面这些rules是用来设置从插件来的规范代码的规则,使用必须去掉前缀eslint-plugin-
  // 主要有如下的设置规则,可以设置字符串也可以设置数字,两者效果一致
  // "off" -> 0 关闭规则
  // "warn" -> 1 开启警告规则
  //"error" -> 2 开启错误规则
  "rules": {
    "semi": [2, "never"],
    "no-console": 0,
    "comma-dangle": [2, "always-multiline"],
    "max-len": 0,
    "react/jsx-first-prop-new-line": 0,
    "react/jsx-filename-extension": 0,
    "space-before-function-paren": [2, "always"],
    "no-unused-expressions": [0, {
      "allowShortCircuit": true,
      "allowTernary": true
    }],
    "arrow-body-style": [0, "never"],
    "func-names": 0,
    "prefer-const": 0,
    "no-extend-native": 0,
    "no-param-reassign": 0,
    "no-restricted-syntax": 0,
    "no-eval": 0,
    "no-continue": 0,
    "react/jsx-no-bind": 0,
    "no-unused-vars": [2, { "ignoreRestSiblings": true }],
    "no-underscore-dangle": 0,
    "global-require": 0,
    "import/no-unresolved": 0,
    "import/extensions": 0,
    "jsx-a11y/href-no-hash": 0,
    "react/no-array-index-key": 0,
    "react/require-default-props": 0,
    "react/forbid-prop-types": 0,
    "react/no-string-refs": 0,
    "react/no-find-dom-node": 0,
    "import/no-extraneous-dependencies": 0,
    "import/prefer-default-export": 0,
    "react/no-danger": 0,
    "jsx-a11y/no-static-element-interactions": 0,
  },
  //此项是用来指定eslint解析器的,解析器必须符合规则,babel-eslint解析器是对babel解析器的包装使其与ESLint解析
  "parser": "babel-eslint",
  //此项是用来指定javaScript语言类型和风格,sourceType用来指定js导入的方式,默认是script,此处设置为module,指某块导入方式
  "parserOptions": {
    "sourceType": "module",
    "ecmaVersion": 8,
    "ecmaFeatures": {
      "jsx": true,
      "experimentalObjectRestSpread": true
    }
  },
  "settings": {
    "import/resolver": "node"
  }
};

具体的rules见文档

stylelint

优点

  1. 支持lesssass预处理器;
  2. 活跃、插件多;

项目中安装stylelint

npm i -D stylelint stylelint-config-standard stylelint-scss stylelint-order stylelint-config-recess-order

stylelint-scss

  • scss拓展,使stylelint支持scss语法

stylelint-config-standard

  • 官方的stylelint代码规则

stylelint-order

  • 以某个顺序编写css。例如先写定位,再写盒模型,再写内容区样式,最后写CSS3相关属性。这样可以极大的保证我们代码的可读性。

stylelint-config-recess-order

  • stylelint-order的第三方配置

配置文件.stylelintrc.js

// .stylelintrc.js
module.exports = {
  "extends": [
    "stylelint-config-standard" // 标准配置规则
  ],
  'plugins': [
    'stylelint-order', // 指定排序,比如声明的块内(插件包)属性的顺序。
    'stylelint-scss' // 执行各种各样的 SCSS语法特性检测规则(插件包)
  ],
  "rules": {
    // css书写顺序
    // ...
    // 其他规则
    'no-empty-source': null
  }
}

配置文档

中文文档(不完整)

配置文件.stylelintignore,指定忽略的文件

*.js

*.jpg

*.woff

测试和打包目录

/test/

/dist/

校验

package.json中的scripts添加指令,然后npm run lintcss即可

// package.json
{
  "scripts": {
    "lintcss": "stylelint src/**/*.css", // (--fix) 自动修复,不建议使用
  }
}

Git Hooks

yorkie:在cli安装之后,@vue/cli-service 也会安装 yorkie,它会让你在 package.json 的 gitHooks 字段中方便地指定 Git hook:

npm i -D lint-staged yorkie
// package.json
"gitHooks": {
  "pre-commit": "lint-staged", // 代码lint检测
  "commit-msg": "commitlint -E GIT_PARAMS" // commitlint检测
},

commit-msg

  • 会在提交的时候执行,通过commitlint对相关提交信息文件进行校验。当项目很大的时候,提交时不可能对所有文件做校验,所以需要lint-staged对已修改的文件进行校验

pre-commit

  • 会在提交之前执行,通过lint-staged对修改的文件进行校验

新建.lintstagedrc.js文件

// .lintstagedrc.js
module.exports = {
  "*.{js,vue}": [
    "eslint"
  ],
  "*.{html,vue,css,wxss,sass,scss}": [
    "stylelint"
  ]
}

生成版本号

standard-version 是一款遵循语义化版本(semver)和 commit message 标准规范 的版本和 changelog 自动化工具。

通常我们在main分支进行发布操作时,需要更新版本号,更新changelog,打tag,而 standard-version 工具会自动完成上述操作

npm i -D standard-version
// package.json
  "scripts": {
    "release": "standard-version"
  }

主版本号(major):一般改动很大,不兼容低版本 次版本号(minor):兼容同一个大版本的API和用法 修订号(patch):一般用来修复bug 有的时候在修订号后面可能还会有先行版本号,例如1.0.0-alpha.1,1.0.0-beta.4,2.0.0-rc.1等。常用的先行版本一般为alpha,beta,rc,stable,csp等。

常用的命令如下

-f 第一次Realease

-r 指定版本号

  • 默认情况下,工具会自动根据 major,minor or patch 规则生成版本号,例如如果你package.json 中的version 为 1.0.0, 那么执行后版本号则是:1.0.1。 自定义可以通过:

  • -r minor 执行后版本号则是:v1.1.0

  • -r 2.0.0 执行后版本号则是:v2.0.0

-p 用来生成预发版本,如果当期的版本号是 1.0.0

  • -p 执行后版本号则是:1.0.1-0
  • -p alpha 执行后版本号则是:v1.0.1-alpha.0

-t 版本 tag 前缀,默认有一个前缀v,如果不想有任何前缀,直接用-t 即可指定前缀。如果当期的版本号是 1.0.0

  • -t 'V-' 执行后版本号则是:V-2.0.0

命令可以结合使用,如-t 'V-' -r 2.1.0,执行后版本号则是:V-2.1.0

--dry-run 预览功能。不会修改package.json、changelog以及tag

更多命令通过 standard-version --help 查看

自定义输出

创建.versionrc.js文件

// .versionrc.js
module.exports = {
  "header": "# HEAD \n\n", // changelog顶部标题
  "types": [ // 影响生成changelog内容的配置
      {"type": "feat", "section": "Features"},
      {"type": "fix", "section": "Bug Fixes"},
      {"type": "chore", "hidden": true},
      {"type": "docs", "hidden": true},
      {"type": "style", "hidden": true},
      {"type": "refactor", "hidden": true},
      {"type": "perf", "hidden": true},
      {"type": "test", "hidden": true}
  ],
  // ...
}

更多配置见Conventional Changelog Configuration Spec (v2.1.0)

生命周期脚本

standard-version支持生命周期脚本。我们可以通过下面这些钩子在发布期间去执行自己的补充命令。钩子按照下面的顺序执行:

  • prerelease:首先进入该生命周期。当prerelease返回一个非零的退出代码,versioning将被中止,但它对进程没有影响。

  • prebump/postbump:在版本号变更之前和之后执行。如果prebump脚本返回版本#,则将使用该版本,而不是standard-version提供的版本。

  • prechangelog/postchangelog:在生成CHANGELOG之前和之后执行。

  • precommit/postcommit:在提交步骤之前和之后调用。

  • pretag/posttag:在标记tag步骤之前和之后调用。

假如项目中使用JIRA时,issue地址需要使用jira的地址,但standard-version并不支持自定义issue地址,所以可以这样操作(如果安装了replace

  // .versionrc.js
  "scripts": {
    "postchangelog": "replace 'https://github.com/myproject/issues/' 'https://myjira/browse/' CHANGELOG.md"
  }

或者

  // .versionrc.js
  "scripts": {
    "prebump": "echo 9.9.9"
  }

执行standard-version会生成v9.9.9的版本。

跳过特定流程

standard-version 若想跳过某些操作,如生成changelog、打tag等可以在 .versionrc.js 文件中增加配置来实现:

  // .versionrc.js
  "skip": {
    "changelog": true, // 自动生成changelog
    "commit": true, // 自动commit
    "tag": true // 打tag标识
  }