Skip to content

前端工程化

EditorConfig

EditorConfig是编辑器级别的,通过在项目中添加配置文件.editorconfig,EditrConfig插件读取配置文件,并根据配置文件来设置编辑的设置

editorconfig
root = true #表示是最顶层的配置文件,设为 true 时,停止向上查找

[*]
charset = utf-8 # 编码字符集
indent_style = space #设置缩进为 tab 或 space 。如果为tab,输入tab变现为为indent_size个空格,实际上是添加制表符;如果为space,会把tab替换为indent_size个空格
indent_size = 2 #设置缩进所占列数
end_of_line = lf #设置换行符,值为lf、cr和crlf
insert_final_newline = true #设为 true 表示使文件以一个空白行结尾
trim_trailing_whitespace = true #设为 true 表示会去除行尾的空白字符

[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

注意:

  • vscode需要安装插件,才会应用配置文件的规则

    image-20230825174639784

ESlint

通过使用ESlint对代码质量进行检查(ESLint也有部分配置,是针对于代码风格的)

ESLint中文网

整体认识

  • ESLint包

    代码质量检查的工具,需要通过npm下载到项目中,通过命令触发代码检查、修复

  • ESLint插件

    依赖于ESlint包(先在项目中安装ESlint包)才能实现其功能。

    Eslint包才具有对代码进行检查的功能,而Eslint插件仅仅是和编译器结合起来,将检查出来的问题在编辑器中做出提示(见下图)等等功能。

    image-20221030162601887

    我们能够通过编译器对ESlint插件进行设置,调整触发ESLint检查的时机(可以设置保存自动触发)

  • ESLint配置文件

    推荐为下面格式

    text

eslint.config.ts eslint.config.js


### 前世今生

ESLint之前有JSLint和JSHint,之所以ESLint能大火是因为 ES6 的出现。ES6 发布后,因为新增了很多语法,JSHint 短期内无法提供支持,而从上面的原理分析,我们就能知道 ESLint 只需要有合适的解析器就能够进行 lint 检查。当时 babel 为 ESLint 提供了支持,开发了 babel-eslint,让ESLint 成为最快支持 ES6 语法的 lint 工具

### 细节学习

* ESLint包

npm install -d eslint


安装依赖之后,命令行调用下面的命令即可对指定的文件、目录、匹配项进行ESLint检查和修复

与Prettier一样,通常我们不会选择手动调用,而是集成到 Git Hooks 中,使其成为自动化流程中的一部分。

```shell
# --fix修复
eslint --fix

注意:并非所有的ESLint规则,都可以使用--fix修复,请查阅ESLint规则大全

image-20220323185257385

  • ESLint插件

    WebStrom中内置了ESLint插件不必自动安装,但是必须要先安装ESLint包,才能起作用

    image-20220323100746808

    另一个重要的设置,这里可以设置WebStrom按照ESLint配置文件进行检查(默认也是这样),如果违反ESLint规则就是在代码中,用波浪线标识出来

    image-20220323102158132

ESLint 的底层要素是 语法树(AST) 和 规则(Rules)。ESLint 的内部工作步骤可以概括为:

  • ESLint 通过解析器(parser,ESLint默认使用的解析器是espress)将源代码解析成语法树(AST)

  • 深度遍历 AST,遍历到节点和路径时触发特定的钩子

  • Rule 在钩子上挂载检测逻辑;执行检测逻辑时发现当前语法不符合规范,直接向 ESLint 上报错误信息。

自定义ESLint配置

重点是 plugin 引入插件,files指定文件类型,然后 rules 是罗列该插件的规则

ts
// --------------
// 1、eslint9.x  extends选项已废弃

// 2、
// languageOptions: {  // 这个选项下的配置和具体的解析器相关。
//     parser: parserVue,  // 同级都是vue 解析器配置
//     sourceType: 'module',
//     parserOptions: {
//        ecmaFeatures: {
//           jsx: true
//        },
//        extraFileExtensions: ['.vue'],
//        parser:parserTS // 同级都是ts解析器的配置
//     }
// },

// 3、插件对象 key ,就是规则的前缀,这个 key 不一定是插件包名,可以是任意的。关闭规则: eslint-disable-next-line ts/no-unused-vars
//    但是发现 @eslint/js 官方提供的插件,规则就是 no-debugger 这种没有前缀

// 4、(插件x、插件x内的规则)需要在一个配置对象内,不能跨对象
// plugins: {
//     ts: pluginTS,
//     vue: pluginVue,
//
// },
// rules: {
//   'ts/no-unused-vars':['warn', {argsIgnorePattern: '^_'}],
//   'vue/component-name-in-template-casing': ['error', 'PascalCase'],
// }

// --------------
import type { Linter } from 'eslint'

// eslint 自带的 js 规则 https://www.npmjs.com/package/@eslint/js
import js from '@eslint/js'

// eslint 风格化相关的规则收集到里面
import stylisticPlugin from '@stylistic/eslint-plugin'

// @typescript-eslint/eslint-plugin
// ts插件。经过调试发现这个插件导出configs['flat/recommended']也是个数组,没法直接取出来 rules
import pluginTS from '@typescript-eslint/eslint-plugin'

// @typescript-eslint/parser ts解析器(直接解析 ts、与vue-eslint-parser嵌套使用解析vue 中的ts)
// 1、配置项 https://typescript-eslint.io/packages/parser/#configuration
import parserTS from '@typescript-eslint/parser'

// 鼠标右键调试看下引入的插件的结构。 plugin.configs 如果只有rules直接放到 rules 字段,如果还有其他直接展开

import unocssConfig from '@unocss/eslint-config/flat'

// eslint-plugin-vue 文档:https://eslint.vuejs.org/
// 1、提供 vue-<template>的一些规则
import pluginVue from 'eslint-plugin-vue'
// globals全局变量配置
import globals from 'globals'

// export default resConfig;

// import { Better } from '@baidu/better-core'
// import { Linter } from '@baidu/better-plugin-linter'
//
// const better = new Better().register(
//   new Linter({
//     vue: true,
//     typescript: true,
//     unocss: true,
//     stylistic: true,
//     include: ['test-file/**/*'],
//   }),
// )
// const config = await better.apply('linter')
//
// export default config

// vue-eslint-parser 文档:https://www.npmjs.com/package/vue-eslint-parser
// 1、解析 vue 文件的 <template> 标签
// 2、默认跳过 <script>标签,可以指定 languageOptions.parser 为 ts 解析器(@typescript-eslint/parser提供)
import parserVue from 'vue-eslint-parser'

const indent = 2
const stylistic = true

const tsRules = (pluginTS.configs['flat/strict'] as Linter.Config[]).reduce((pre, cur) => {
  if (cur.rules) {
    for (const key in cur.rules) {
      const replacedKey = key.replace('@typescript-eslint/', 'ts/')
      pre[replacedKey] = cur.rules[key]
    }
  }

  return pre
}, {} as Record<string, any>)

function renameRule(options: {
  rules: Record<string, any>[]
  origin: string
  target: string
}) {
  const { rules, origin, target } = options
  return rules.reduce((resultRules, rule) => {
    for (const key in rule) {
      const replacedKey = key.replace(origin, target)
      resultRules[replacedKey] = rule[key]
    }
    return resultRules
  }, {} as Record<string, any>)
}

function composeRulesFromConfigs(configs: Linter.Config[]) {
  return configs.reduce((pre, cur) => {
    if (cur.rules) {
      for (const key in cur.rules) {
        const replacedKey = key.replace('@typescript-eslint/', 'ts/')
        pre[replacedKey] = cur.rules[key]
      }
    }

    return pre
  }, {} as Record<string, any>)
}

const vueconfig = {
  name: 'my/vue',
  files: ['src/**/*.vue'],
  plugins: {
    vue: pluginVue,
    ts: pluginTS,
  },
  languageOptions: {
    parser: parserVue,
    sourceType: 'module',
    parserOptions: {
      ecmaFeatures: {
        jsx: true,
      },
      extraFileExtensions: ['.vue'],
      parser: parserTS,
      sourceType: 'module',
    },
  },
  processor: pluginVue.processors['.vue'],
  rules: {
    ...composeRulesFromConfigs(pluginVue.configs['flat/recommended']),
    'no-debugger': 'error',
    'vue/block-order': [
      'error',
      {
        order: ['template', 'script', 'style'],
      },
    ],
    'vue/component-name-in-template-casing': ['error', 'PascalCase'],
    'vue/component-options-name-casing': ['error', 'PascalCase'],
    'vue/custom-event-name-casing': ['error', 'camelCase'],
    'vue/define-macros-order': [
      'error',
      {
        order: ['defineOptions', 'defineProps', 'defineEmits', 'defineSlots'],
      },
    ],
    'vue/dot-location': ['error', 'property'],
    'vue/dot-notation': ['error', { allowKeywords: true }],
    'vue/eqeqeq': ['error', 'smart'],
    'vue/html-indent': ['error', indent],
    'vue/html-quotes': ['error', 'double'],
    'vue/max-attributes-per-line': 'off',
    'vue/multi-word-component-names': 'off',
    'vue/no-dupe-keys': 'off',
    'vue/no-empty-pattern': 'error',
    'vue/no-irregular-whitespace': 'error',
    'vue/no-loss-of-precision': 'error',
    'vue/no-restricted-syntax': [
      'error',
      'DebuggerStatement',
      'LabeledStatement',
      'WithStatement',
    ],
    'vue/no-restricted-v-bind': ['error', '/^v-/'],
    'vue/no-setup-props-reactivity-loss': 'off',
    'vue/no-sparse-arrays': 'error',
    'vue/no-unused-refs': 'error',
    'vue/no-useless-v-bind': 'error',
    'vue/no-v-html': 'off',
    'vue/object-shorthand': [
      'error',
      'always',
      {
        avoidQuotes: true,
        ignoreConstructors: false,
      },
    ],
    'vue/prefer-separate-static-class': 'error',
    'vue/prefer-template': 'error',
    'vue/prop-name-casing': ['error', 'camelCase'],
    'vue/require-default-prop': 'off',
    'vue/require-prop-types': 'off',
    'vue/space-infix-ops': 'error',
    'vue/space-unary-ops': ['error', { nonwords: false, words: true }],

    ...(stylistic
      ? {
          'vue/array-bracket-spacing': ['error', 'never'],
          'vue/arrow-spacing': ['error', { after: true, before: true }],
          'vue/block-spacing': ['error', 'always'],
          'vue/block-tag-newline': [
            'error',
            {
              multiline: 'always',
              singleline: 'always',
            },
          ],
          'vue/brace-style': [
            'error',
            'stroustrup',
            { allowSingleLine: true },
          ],
          'vue/comma-dangle': ['error', 'always-multiline'],
          'vue/comma-spacing': ['error', { after: true, before: false }],
          'vue/comma-style': ['error', 'last'],
          'vue/html-comment-content-spacing': [
            'error',
            'always',
            {
              exceptions: ['-'],
            },
          ],
          'vue/key-spacing': [
            'error',
            { afterColon: true, beforeColon: false },
          ],
          'vue/keyword-spacing': ['error', { after: true, before: true }],
          'vue/object-curly-newline': 'off',
          'vue/object-curly-spacing': ['error', 'always'],
          'vue/object-property-newline': [
            'error',
            { allowMultiplePropertiesPerLine: true },
          ],
          'vue/operator-linebreak': ['error', 'before'],
          'vue/padding-line-between-blocks': ['error', 'always'],
          'vue/quote-props': ['error', 'consistent-as-needed'],
          'vue/space-in-parens': ['error', 'never'],
          'vue/template-curly-spacing': 'error',
        }
      : {}),
  },
}

const stylisticRules = renameRule({

  rules: [stylisticPlugin.configs.recommended.rules!],
  origin: '@stylistic/',
  target: 'stylistic/',
})

export default [

  {
    name: 'my/global',
    languageOptions: {
      ecmaVersion: 2022,
      globals: {
        ...globals.browser,
        ...globals.es2021,
        ...globals.node,
        document: 'readonly',
        navigator: 'readonly',
        window: 'readonly',
      },
      parserOptions: {
        ecmaFeatures: {
          jsx: true,
        },
        ecmaVersion: 2022,
        sourceType: 'module',
      },
      sourceType: 'module',
    },
    linterOptions: {
      reportUnusedDisableDirectives: true,
    },

  },
  {
    name: 'my/ts',
    files: ['**/*.?([cm])tsx', '**/*.ts', '**/*.vue'],
    plugins: {
      ts: pluginTS,
    },
    languageOptions: {
      parser: parserTS,
      sourceType: 'module',
    },
    rules: {
      'no-debugger': 'error',
      ...composeRulesFromConfigs(pluginTS.configs['flat/strict'] as Linter.Config[]),
    },
  },
  {
    name: 'my/js',
    files: ['**/*.?([cm])jsx', 'src/**/*.js'],
    plugins: {
      js,
    },
    rules: {
      ...js.configs.recommended.rules,
    },
  },
  {
    name: 'my/unocss',
    ...unocssConfig,
  },
  // ...(pluginTS.configs['flat/recommended'] as Linter.Config[]),
  vueconfig,
  // ...pluginVue.configs['flat/recommended'],
  {
    name: 'my/stylistic',
    // ...stylisticPlugin.configs.recommended,
    plugins: {
      stylistic: stylisticPlugin,
    },
    rules: {
      ...stylisticRules,
      'stylistic/semi': ['error', 'always'],
      'stylistic/indent': ['error', 4],
    },
  },

]

工作流自动化集成

Git Hooks

Git预先定义了一些事件钩子,如commit-msgpre-commit等,当我们执行对应的Git操作时会触发它们,然后项目根目录下的.git/hooks 目录中对应的脚本就会执行,如图所示:

image-20220323191501634

默认情况下,这里shell脚本都是.sample结尾的,所以我们执行对应的Git操作时,并不会触发对应的脚本执行。我们可以将.sample结尾去掉,就会触发执行,其中的shell脚本了。同时,我们可以编写shell脚本,就能让Git触发勾子事件时,按照我们要求去执行任务了

但前端工程师大多对linux/windows shell并不擅长, 因此我们通过编写git hooks脚本来优化前端工作流的这条道路十分艰难。但是,Nodejs的出现改变了这一切,它让JS拥有了控制“操作系统”的能力,你只需要安装npm包husky,它会帮我们自动生成.git/hooks目录下的shell script,我们便可以很轻松的使用更熟悉的JS处理git hooks任务,而无需关注shell脚本的实现细节

图中,pre-commit文件之所以,没有.sample结尾,就是我安装了husky包,然后在package中配置触发的任务后,husky自动帮我生成脚本放入其中,并去掉.sample

husky旧版本

安装husky

shell
npm install -D husky@3.1.0

package.json中配置husky

json
"scripts": {
    "test": "node test.js"
},
"husky": {
    "hooks": {
      // 可以执行一个js文件,将控制权转移给我们更熟悉的nodejs
      "pre-commit": "node test.js", 
      // 也可以调用其他脚本或者执行一段原生shell命令
      "commit-msg": "npm run test && echo succeed" 
      
    }
}

常用下面的方式集成钩子

json
"husky": {
    "hooks": {
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
      "pre-commit": "lint-staged"
    }
  },

husky新版本

新版本引入破坏性变动,使用方式和旧版本有较大出入

新版不在使用默认的.git/hooks目录存储脚本,而是指定了项目内的文件夹为脚本目录

安装husky

shell
npm install -D husky

package.json中添加

prepare脚本会在npm install(不带参数)之后自动执行

当我们执行npm install安装完项目依赖后会执行 husky install命令,该命令会在当前目录创建.husky/目录并指定该目录为git hooks所在的目录。【默认git hooks目录在.git/hooks下,这个目录是不会被提交到仓库的】

如果不添加为prepare脚本,即使.husky文件夹被提交到git仓库,其他人在clone项目后,本地的git不会将Git钩子的目录指定为项目中的.husky目录

image-20220324180608409

json
//手动在package.json中添加
{
  "scripts": {
    "prepare": "husky install"
  }
}

//使用npm命令添加
npm set-script prepare "husky install"

添加git hooks

husky add:第一个参数是在.husky目录下新建的文件名,第二个参数是文件中的写入的脚本

在.husky目录下新建pre-commit脚本,

shell
npx husky add .husky/pre-commit "npx lint-staged"

image-20220327171214174

在.husky目录下新建commit-msg脚本

shell
npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"
#--no-install 参数表示强制npx使用项目中node_modules目录中的commitlint包

image-20220327171056231

这里提到的,在脚本中添加的几个命令,会在后面提到

lint-staged

Git Hooks可以让我们在提交流程中触发自定义操作,完成各种工作的自动化集成。

通常情况下,我们只需要对自己的本次提交变更的代码,进行各种校验或修复

lint-staged就能完成这样的任务,可以让我们的操作只针对于暂存区的代码

安装

shell
npm install -D lint-staged

配置

基本格式

"lint-staged": {
    "文件格式": [
      "命令1",
      "命令2",
      "...其他命令"
    ]
}

例子:对暂存区中的js、jsx、vue文件,使用eslint修复命令。因为eslint修复后,代码可能会出现变化,这些可能出现的变化放自动放入到暂存区

json
"lint-staged": {
    "*.{js,jsx,vue}": [
      "eslint --fix --ext .js app/",
    ]
}

集成commitlint

规范化提交,更便于在仓库中查找某次提交

可以直接用规范化的commit message生成change log

commitlint官方网站

  • 安装

    • @commitlint/cli 是commitlint提供的命令行工具,安装后会将cli脚本放置在./node_modules/.bin/目录下

    • @commitlint/config-conventional是社区中一些共享的配置,我们可以扩展这些配置,也可以不安装这个包自定义配置

      shell
      npm install -D @commitlint/cli 
      npm install -D @commitlint/config-conventional
  • 提交规范

    commit message 由 header(必须) 、body(可选)、footer(可选)

    text
    <type>[scope]: <subject>
    [body]
    [footer]
    • header

      text
      <type>[scope]: <subject>

      type:表示提交的类型,@commitlint/config-conventional中包含的可选值有

      text
      feat: 新功能
      
      fix: bug 修复
      
      docs: 仅修改文档
      
      style: 修改格式(空格,格式化,省略分号等),对代码运行没有影响
      
      refactor: 重构(既不是修 bug ,也不是加功能)
      
      build: 构建流程、外部依赖变更,比如升级 npm 包、修改 webpack 配置等
      
      perf: 性能优化
      
      test: 测试相关
      
      chore: 对构建过程或辅助工具和库(如文档生成)的更改
      
      ci: ci 相关的更改
      
      revert: 当前提交是为了撤销之前的某次提交,应该用 revert 开头,后面加上被撤销的提交的 header,在 body 中应该注明:This reverts commit <hash>. ,hash 指的就是将要被撤销的 commit SHA 
      // 例如
      revert: feat(user): add user type
      This reverts commit ca16a365467e17915f0273392f4a13331b17617d.

      scope:表示本次提交代码的影响的范围

      subject:表示对本次提交代码的简短描述

    • body

      body 部分是对本地 commit 的详细描述,可以分成多行

    • footer

      基本用在这两种情况:

      • 不兼容的改动( Breaking Changes ),通常用 BREAKING CHANGE: 开头,后面跟一个空格或两个换行符。剩余的部分就是用来说明这个变动的信息和迁移方法等。

        shell
        refactor: 
        BREAKING CHANGE:某某发生了更改
            按照下面的例子迁移代码:
            Before:
            scope: {
              myAttr: 'attribute',
              myBind: 'bind',
              myExpression: 'expression'
            }
            After:
            scope: {
              myAttr: '@',
              myBind: '@',
              myExpression: '&'
            }
            某某发生了更改
      • 关闭 Issue(Closes #Issue号)

        shell
        fix: 修复用户登陆注册
        Closes #2314, #3421
  • 初始化@commitlint/cli的配置文件

    项目根目录下,创建commitlint.config.js文件,也可以是.commitlintrc.js

    官网给出的配置文件完整的格式

    js
    module.exports = {
      /*
       * Resolve and load @commitlint/config-conventional from node_modules.
       * Referenced packages must be installed
       */
      extends: ['@commitlint/config-conventional'],
      /*
       * Resolve and load conventional-changelog-atom from node_modules.
       * Referenced packages must be installed
       */
      parserPreset: 'conventional-changelog-atom',
      /*
       * Resolve and load @commitlint/format from node_modules.
       * Referenced package must be installed
       */
      formatter: '@commitlint/format',
      /*
       * Any rules defined here will override rules from @commitlint/config-conventional
       */
      rules: {
        'type-enum': [2, 'always', ['foo']],
      },
      /*
       * Functions that return true if commitlint should ignore the given message.
       */
      ignores: [(commit) => commit === ''],
      /*
       * Whether commitlint uses the default ignore rules.
       */
      defaultIgnores: true,
      /*
       * Custom URL to show upon failure
       */
      helpUrl:
        'https://github.com/conventional-changelog/commitlint/#what-is-commitlint',
      /*
       * Custom prompt configs
       */
      prompt: {
        messages: {},
        questions: {
          type: {
            description: 'please input type:',
          },
        },
      },
    };

    通常我们使用第一步安装的第三方配置就可以了

    js
    /*使用第一步安装的第三方配置*/
    module.exports = {
        extends: ['@commitlint/config-conventional'],
    }

    如果想要自定义一些配置

    js
    module.exports = {
        extends: ['@commitlint/config-conventional'],
      	/*可以在下面,继续写其他字段的配置,这些配置会覆盖@commitlint/config-conventional中对应的配置*/
    }

    比如Rules,按照官网的Rules字段配置,这里我们讲下配置的格式

    text
    '规则名':[Level,Applicable,Value]
    
    //规则名:从官网查到所有的规则名的第一部分都是commit message的不同部分,例如:header-* 、scope-* 、type-*,表示规则作用的部分
    //Level : 错误提示等级,0是关闭提示,1是警告提示,2是错误提示
    //Applicable : always启用规则,never关闭规则
    //Value : 有些规则需要传递参数,在这里传递,比如 'header-max-length': [2, 'always', 72] 表示 header最长72

    例子:覆盖@commitlint/config-conventional中的rules字段

    js
    /**
     * feature:新功能
     * update:更新某功能
     * fixbug:修补某功能的bug
     * refactor:重构某个功能
     * optimize: 优化构建工具或运行时性能
     * style:仅样式改动
     * docs:仅文档新增/改动
     * chore:构建过程或辅助工具的变动
     */
    module.exports = {
        extends: ['@commitlint/config-conventional'],
      	
      	/*自定义配置,会覆盖@commitlint/config-conventional中的配置*/
        rules: {
          	'header-max-length': [2, 'never', 72], // header 最长72
          
            'body-leading-blank': [2, 'always'], // body换行
            
          	//type的枚举字段,提交时的type值,只能是这里的值,@commitlint/config-conventional中的可选值全部被覆盖了
            'type-enum': [2, 'always', ['feature', 'update', 'fixbug', 'refactor', 'optimize', 'style', 'docs', 'chore']],
        }
    }
  • 添加到Git钩子中

    旧版husky

    json
    //package.json文件中
    "husky": {
        "hooks": {
          "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 
        }
    }

    新版husky(参照上面Git Hooks章节的使用)

    shell
    npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"
    #--no-install 参数表示强制npx使用项目中node_modules目录中的commitlint包
  • lint-staged

    这里仅仅是校验commit message,不是暂存区代码,所以不必使用lint-staged

  • 提交

    image-20220328142551798

  • 编写commit message的辅助工具

    commitzen是一款工具,可以通过在终端选择的方式,帮助我们编写合格的commit message

    shell
    npm install -D commitizen

    让commitzen其支持 Angular 的 Commit message 格式

    shell
    commitizen init cz-conventional-changelog --save-exact

    查看package.json,就会多出一句

    json
    "config": {
    	"commitizen": {
    		"path": "./node_modules/cz-conventional-changelog"
       }
    }

    以后,凡是用到git commit命令,一律改为使用git cz。这时,就会出现选项

    image-20220329203304247

集成Changelog

如果你的所有 Commit 都符合 Angular 格式,那么发布新版本时, Change log 就可以用脚本自动生成

生成的文档包括以下三个部分。

  • New features
  • Bug fixes
  • Breaking changes.

每个部分都会罗列相关的 commit ,并且有指向这些 commit 的链接。当然,生成的文档允许手动修改,所以发布前,你还可以添加其他内容

shell
npm install -D conventional-changelog-cli

生成CHANGELOG.md文件

shell
conventional-changelog -p angular -i CHANGELOG.md -w #命令不会覆盖以前的 Change log,只会在CHANGELOG.md的头部加上自从上次发布以来的变动

conventional-changelog -p angular -i CHANGELOG.md -w -r 0 #生成所有的 Change log,要改为运行下面的命令

文件的格式大概是下面这个样子

image-20220329210029744

集成到package.json的脚本中

json
scripts: {
   "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md"
    }

集成Prettier

一般情况下,我们并不会把Prettier集成到自动化流程中

因为,如果我们对项目中其他人的页面进行修改,在提交时自动触发Prettier格式化,可能会将页面中其他人的代码格式化,这样在提交记录上来看,其他人的代码,变成了自己提交的代码

  • 安装

    shell
    npm install -D prettier
  • 根目录下新建配置文件prettierrc

    json
    {
        "printWidth": 80, 
        "tabWidth": 2, 
        "useTabs": false, 
        "semi": true,
        "singleQuote": false, 
        "bracketSpacing": true, 
        "bracketSameLine": true, 
        "endOfLine": "lf"
    }
  • package.json中配置

    这里仅仅是暂存区的文件需要进行Prettier格式化,所以使用了lint-staged

    json
    {
    	 "scripts": {
        "prettier": "prettier --write"
      },
      
      "lint-staged": {
        "*.{js,vue}": [
          "npm run prettier",
          "git add"
        ]
      }
    }
  • 添加Git Hooks

    shell
    npx husky add .husky/pre-commit "npx lint-staged"

Git官网介绍了,所有的Git勾子的详细信息

流程:

  • Git Hooks在Git提交的某个阶段触发的勾子
  • 勾子中使用lint-staged,使得格式化工具只作用于暂存区代码

自动化,只会用到提交工作流钩子,提交工作流包含 4 个钩子:

flow
st=>start: git commit -m "提交文本"
op1=>operation: pre-commit
op2=>operation: prepare-commit-msg
op3=>operation: commit-msg
op4=>operation: post-commit
e=>end: 提交完成
st->op1->op2->op3->op4
op4->e
  • pre-commit 钩子在键入提交信息前运行,在这个阶段加入 代码检查 流程,会代码

  • prepare-commit-msg 钩子在启动提交信息编辑器之前,默认信息被创建之后运行。它对一般的提交来说并没有什么用;然而对那些会自动产生默认信息的提交,如提交信息模板、合并提交、压缩提交和修订提交等非常实用。 你可以结合提交模板来使用它,动态地插入信息「可在这个阶段加载 commitizen 之类的辅助填写工具」

  • commit-msg 钩子在即将提交前运行,勾子接收一个参数,即存有当前提交信息的临时文件的路径,可在这个阶段借助 commitlint 进行提交信息规范性检查;

  • post-commit 钩子在整个提交过程完成后运行,它不接收任何参数,在这个阶段一般做一些通知操作。

使用 Git 钩子最直观的方式是操作 .git/hooks 下的示例文件,将对应钩子文件的 .sample 后缀名移除即可启用。然而这种操作方式存在弊端:

  1. 需要操作项目范围外的 .git 目录(有系统级别、全局级别的.git配置文件)
  2. 无法同步 .git/hooks 到远程仓库(.git是本地的git配置文件,不会被提交)

可以将Git勾子的目录设置在项目的根目录下

shell
# 指定 Git hooksPath 为根目录下的 .githooks 目录(原来是 .git/hooks 下的文件 )
git config core.hooksPath .githooks

安装husky

shell
npm install -d husky

安装lint-staged

shell
npm install -d lint-staged

在package.json

json
//在hooks中可以定有Git勾子
"husky": {
    "hooks": {
      "pre-commit": "lint-staged",
      "commit-msg": ""
    }
},

//针对暂存区进行操作
"lint-staged": {
  	//指定 什么类型的文件:["执行命令1","执行的命令2"]
  	//一般把命令放在script字段里,这里直接调用其中的命令
  	//最后以一个命令是git add ,将前面命令做的代码变更重新加到暂存区
    "**/*.{js,vue}":[
      "npm run lint",
      "npm run prettier",
      "git add" 
    ]
},

//
"scripts": {
    "dev": "xxxxx",
    "build": "xxxxx",
  	//eslint修复 --fix是自动修复
    "lint-fix": "eslint --fix --ext .js app/",
  	//eslint --ext指定文件类型和目录(支持glob方式)
    "lint": "eslint --ext .js app/",
  
  	//prettier
  	"prettier":"prettier --write",
  
  	//
    "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s"
},

待补充

其他配置文件

.gitignore

.gitignore
#mac系统下,记录文件夹位置的文件
.DS_Store

#依赖安装目录
node_modules

#打包路径
/dist

# local env files
.env.local
.env.*.local

# 日志文件
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

# 编辑器配置文件
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

总结

EditorConfig 作用于预览和输入阶段,Prettier 在保存和提交阶段重新组织代码,Prettier 会成为代码形态的最终决定者

参考

深入理解 ESLint

Eslint中plugins和extends的区别

ESLint中文网

前端代码规范化:EditorConfig + Prettier + ESLint

让你的 commit 更有价值(规范)

Commit message 和 Change log 编写指南——阮一峰

最后更新时间:

Released under the MIT License.