使用 husky、commitlint 和 lint-staged 来构建你的前端工作流(vue、react、dva)

16,605 阅读4分钟

引言

发现每次 commit 的时候总是提交完了才发现少了一个分号,或者多了一个 console.log,想起以前看过的项目里使用了 husky 这个库,可以在 commit 之前做代码校验,如果代码有格式问题,就会禁止提交。

作用

在我们提交代码时,先自动帮我们格式化代码,然后使用eslint检查代码,并自动修复错误,在修复不了的时候,报错给我们。并且报错后此次的commit不会提交。

关于 commitlint, husky, eslint 的具体信息可以见官网。

工具

  1. prettier。 一个很流行的代码格式化工具,你很容易在编辑器找到实现它的各种插件,像vscode,atom,webstom都可以找到。这里用它在代码提交前做代码格式化。
  2. eslint。 代码检查工具。eslint也可以负责一部分代码格式检查的工作,但是prettier已经做的很好了,所以我便没用eslint的代码格式检查,只让其负责代码错误检查。
  3. lint-staged。在你提交的文件中,执行自定义的指令。don’t let 💩 slip into your codebase. — lint-staged

安装

安装eslint

npm i -D eslint babel-eslint eslint-config-airbnb eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react

安装prettier

npm install --save-dev prettier eslint-plugin-prettier eslint-config-prettier

安装 husky 和 lint-stage

yarn add husky@next  # 安装最新版,就不用配置 scripts 脚本了
yarn add lint-stage

配置

配置 commitlint

commitlint 搭配 husky 的 commit message 钩子后,每次提交 git 版本信息的时候,会根据配置的规则进行校验,若不符合规则会 commit 失败,并提示相应信息。

安装 commitlint 依赖

yarn add @commitlint/{cli,config-conventional}

新建 commitlint.config.js 文件

module.exports = {
    extends: ['@commitlint/config-conventional']
};

commitlint.config.js 配置文件可以添加自己的规则,这里 @commitlint/config-conventional 提供了官方的规则扩展:

build:主要目的是修改项目构建系统(例如 glup,webpack,rollup 的配置等)的提交
ci:主要目的是修改项目继续集成流程(例如 Travis,Jenkins,GitLab CI,Circle等)的提交
docs:文档更新
feat:新增功能
merge:分支合并 Merge branch ? of ?
fix:bug 修复
perf:性能, 体验优化
refactor:重构代码(既没有新增功能,也没有修复 bug)
style:不影响程序逻辑的代码修改(修改空白字符,格式缩进,补全缺失的分号等,没有改变代码逻辑)
test:新增测试用例或是更新现有测试
revert:回滚某个更早之前的提交
chore:不属于以上类型的其他类型

配置 package.json 文件

添加 husky 字段

"husky": {
    "hooks": {
      "commit-msg": "commitlint -e $HUSKY_GIT_PARAMS"
    }
  },

测试

git add .
git commit -m "foo: this will fail"

image.png

prettier配置

  1. prettier 代码格式化核心
  2. eslint-plugin-prettier 插件,可以让eslint使用prettier规则进行检查,并使用--fix选项。像之前的格式不对时,eslint提示的红线。
  3. eslint-config-prettier 插件,之前说了eslint也会检查代码的格式,这个插件就是关闭所有不必要或可能跟prettier产生冲突的规则。

eslintrc.json添加如下配置:

{
 "extends": ["airbnb", "plugin:prettier/recommended"],
}

这个配置做如下三件事:

  1. 使eslint-plugin-prettier生效
  2. 不符合prettier/prettier的规则,会报错。就是之前截图中的红线。
  3. 使eslint-config-prettier生效。就是会覆盖eslint中与prettier冲突的配置。

prettier配置文件

prittier配置文件支持很多种,具体可以看这里。我使用的是.prettierrrc格式,因为试过其他格式,但是只有.prettierrrc,vscode才可以识别。 生成配置可以直接用官网上的try it out,左下角有导出配置。下面这份配置基本上是风格要求的全部了,具体可按照个人爱好进行配置。

{
  "printWidth": 120, // 一行最大多少字符
  "tabWidth": 2, // tab占用的字符数
  "useTabs": false, // 是否使用tab代替空格
  "semi": true, // 是否每句后都加分号
  "singleQuote": true, // 是否使用单引号
  "jsxSingleQuote": false, // jsx是否使用单引号
  "trailingComma": "all", // 数组尾逗号。
  "bracketSpacing": false, // {foo: xx}还是{ foo: xx }
  "jsxBracketSameLine": false, //看官网
  "arrowParens": "always" //剪头函数参数是否使用()
}

配置eslint钩子

.eslintrc.js

*vue开启eslint基本上不用配置,react可以配置自己的

module.exports = {
  parser: 'babel-eslint',
  plugins: ['react'],
  root: true,
  env: {
    browser: true,
    node: true,
    es6: true
  },
  parserOptions: {
    ecmaVersion: 6,
    sourceType: 'module'
  },
  globals: {
    document: true,
    localStorage: true,
    window: true,
    process: true,
    console: true,
    navigator: true,
    fetch: true,
    URL: true
  },
  rules: {
    'no-console': 'off',
    'no-alert': 0, //禁止使用alert confirm prompt
    'no-var': 0, //禁用var,用let和const代替
    'no-catch-shadow': 2, //禁止catch子句参数与外部作用域变量同名
    'default-case': 2, //switch语句最后必须有default
    'dot-notation': [0, { allowKeywords: true }], //避免不必要的方括号
    'no-constant-condition': 2, //禁止在条件中使用常量表达式 if(true) if(1)
    'no-dupe-args': 2, //函数参数不能重复
    'no-inline-comments': 0, //禁止行内备注
    'no-unreachable': 2, //不能有无法执行的代码
    'no-unused-vars': [2, { vars: 'all', args: 'after-used' }], //不能有声明后未被使用的变量或参数
    'no-unused-expressions': 2, //禁止无用的表达式 | 短路求值和三目运算都允许 0 | 2 都不允许
    // 'no-unused-expressions': [
    //   2,
    //   { allowShortCircuit: false, allowTernary: true },
    // ], // 允许三目,不允许短路:
    'no-mixed-spaces-and-tabs': [2, false], //禁止混用tab和空格
    'linebreak-style': [0, 'windows'], //换行风格
    'no-multiple-empty-lines': [1, { max: 2 }], //空行最多不能超过2行
    'no-extra-semi': 2, //禁止多余的冒号
    'no-debugger': 2, //禁止使用debugger
    // 'space-before-function-paren': [2, { anonymous: 'never', named: 'never' }], // 函数名后面加空格
    // 'space-before-function-paren': ['error', 'always'],
    // or
    'space-before-function-paren': [
      'error',
      {
        anonymous: 'always',
        named: 'always',
        asyncArrow: 'always'
      }
    ],
    'eol-last': 0, //文件以单一的换行符结束
    // eqeqeq: true, //必须使用全等 如果是true,则要求在所有的比较时使用===和!==
    // eqnull: true, // 如果是true,则允许使用== null
    'lines-around-comment': 0, //行前/行后备注
    'operator-linebreak': [2, 'after'], //换行时运算符在行尾还是行首
    'prefer-const': 0, //首选const
    quotes: [1, 'single'], //引号类型 `` "" ''
    'id-match': 0, //命名检测
    'array-bracket-spacing': [2, 'always'], // 指定数组的元素之间要以空格隔开(,后面), never参数:[ 之前和 ] 之后不能带空格,always参数:[ 之前和 ] 之后必须带空格
    quotes: [2, 'single'], // 全部单引号
    // 数组和对象键值对最后一个逗号, never参数:不能带末尾的逗号, always参数:必须带末尾的逗号,
    // always-multiline:多行模式必须带逗号,单行模式不能带逗号
    'comma-dangle': [2, 'never'],
    'computed-property-spacing': [2, 'never'], // 以方括号取对象属性时,[ 后面和 ] 前面是否需要空格, 可选参数 never, always
    semi: [2, 'never'], //语句强制分号结尾  不要分号
    'eol-last': 2, // 文件末尾强制换行
    'semi-spacing': [0, { before: false, after: true }], //分号前后空格
    'arrow-body-style': 0, // 不禁止箭头函数直接return对象
    strict: 2, //使用严格模式
    'use-isnan': 2, //禁止比较时使用NaN,只能用isNaN()
    'valid-typeof': 2, //必须使用合法的typeof的值
    'space-in-parens': [0, 'always'],
    'template-curly-spacing': [2, 'always'],
    'array-bracket-spacing': [2, 'always'],
    'object-curly-spacing': [2, 'always'],
    'computed-property-spacing': [2, 'always'],
    'no-multiple-empty-lines': [2, { max: 1, maxEOF: 0, maxBOF: 0 }],
    quotes: [1, 'single', 'avoid-escape'],
    'no-use-before-define': [2, { functions: false }],
    semi: [0, 'never'],
    'prefer-const': 1,
    'react/prefer-es6-class': 0,
    'react/jsx-filename-extension': 0,
    'react/jsx-curly-spacing': [2, 'always'],
    'react/jsx-indent': [2, 2],
    'react/prop-types': [1],
    'react/no-array-index-key': [1],
    'class-methods-use-this': 'off',
    'no-undef': [1],
    'no-case-declarations': [1],
    'no-return-assign': [1],
    'no-param-reassign': [1],
    'no-shadow': [1],
    camelcase: [1],
    'no-unused-vars': 'off',
    'no-underscore-dangle': [0, 'always']
  }
}

husky钩子pre-commit配置

react配置package.json

 "husky": {
    "hooks": {
      "commit-msg": "commitlint -e $HUSKY_GIT_PARAMS",
      "pre-commit": "lint-staged", // pre-commit,提交前的钩子
      "pre-add": "lint-staged",
      "pre-push": "lint-staged"
    }
  },
  "lint-staged": {
    // 此处可以配置文件夹和文件类型的范围
    "src/**/*.{jsx,txs,ts,js,json,css,md}": [
      "prettier --write", // 先使用prettier进行格式化
      "eslint --fix", // 再使用eslint进行自动修复
      "git add" // 所有通过的话执行git
    ]
  }

vue配置package.json

"gitHooks": {
    "commit-msg": "commitlint -e $HUSKY_GIT_PARAMS",
    "pre-commit": "lint-staged"
  },
  "lint-staged": {
    "*.js": [
      "vue-cli-service lint",
      "git add"
    ],
    "*.vue": [
      "vue-cli-service lint",
      "git add"
    ]
  }

husky会在你提交前,调用pre-commit钩子,执行lint-staged,如果代码不符合prettier配置的规则,会进行格式化;然后再用eslint的规则进行检查,如果有不符合规则且无法自动修复的,就会停止此次提交。如果都通过了就会讲代码添加到stage,然后commit

*如果想跳过校验

使用 --no-verify 指令可以跳过检验规则

git add . && git commit --no-verify -m "代码规范强制提交测试"

补充说明

关于前端工程打包对每一个开发人员都不可避免,发布线上频繁的build,是很浪费时间的,那么如何避免重复的操作?

其实gitHooks也可以帮我们做到比如我们做如下配置:

"gitHooks": {
    ......,
    "pre-push": "yarn run build && git add . &&  git commit -am 'prod build'"
  },
  "lint-staged": {......}

这样就完美解决了我们的顾虑,当然此方法也存在一定的缺陷。例如:

  1. 多人开发需要处理每次build带来的冲突;
  2. 每次push都会伴随一次build产生;
  3. 发布上线发现静态资源不是最新的(解决这一类问题有很多办法,比如出一个公共接口每次发布上线手动刷新我们的静态资源等等。)

针对build工程发布上线本节我们简单介绍一下,后续会为大家详细普及,敬请关注哦!

总结

有人说前端攻城狮是世界上最奇怪的动物,提交代码时用 prettier 把代码排版的很美观,但部署上线时又使用 uglify 把代码压缩的连亲妈都不认了,事实是,如果我们写出来的代码本来就很丑陋,就根本不需要用 uglify。希望读到这里的你能把 Lint 工作流打磨到极致,把更多时间专注在解决真正的问题上,成为真正高效的工程师