跳至主要內容

编程规范

Mr.LRH大约 27 分钟

编程规范

EditorConfig

EditorConfigopen in new window 可以在不同的编辑器和 ide 中,为同一个项目工作的多个开发人员维护一致的编码风格。

如果使用 Visual Studio Code 编辑器,需要安装 EditorConfig for VS Code 插件。

.editorconfig
# .editorconfig
# https://editorconfig.org
# 表示是最顶层的配置文件,发现设为true时,才会停止查找.editorconfig文件
root = true

# 表示所有文件适用
[*]
# 设置编码,值为 latin1、utf-8、utf-8-bom、utf-16be和utf-16le,不建议使用 utf-8-bom
charset = utf-8
# 设置缩进风格(tab是硬缩进,space为软缩进)
indent_style = space
# 用一个整数定义的列数来设置缩进的宽度
# 如果 indent_style 为 tab,则此属性默认为 tab_width
indent_size = 2
# 设置换行符,值为lf、cr和crlf
end_of_line = lf
# 设为 true 表示使文件以一个空白行结尾
insert_final_newline = true
# 设为 true 表示会去除换行行首的任意空白字符
trim_trailing_whitespace = true

# 仅 .md 文件适用以下规则
[*.md]
# 设为 true 表示使文件以一个空白行结尾
insert_final_newline = false
# 设为 true 表示会去除换行行首的任意空白字符
trim_trailing_whitespace = false

Prettier

Prettier 可用于格式化代码,支持多种语言。

如果使用 Visual Studio Code 编辑器,需要安装 Prettier - Code formatter 插件。

Prettier 安装

npm i -D prettier

Prettier 命令

格式化所有文件(· 标识所有文件)命令如下:

{
  "scripts": {
    "prettier": "prettier --write ."
  }
}

Prettier 配置

.prettierrc.js
module.exports = {
  // 一行最多 100 字符
  printWidth: 100,
  // 使用 2 个空格缩进
  tabWidth: 2,
  // 不使用缩进符,而使用空格
  useTabs: false,
  // 行尾需要有分号
  semi: false,
  // 使用单引号
  singleQuote: true,
  // 对象的 key 仅在必要时用引号
  quoteProps: 'as-needed',
  // jsx 不使用单引号,而使用双引号
  jsxSingleQuote: false,
  // 末尾不需要逗号
  trailingComma: 'none',
  // 大括号内的首尾需要空格
  bracketSpacing: true,
  // jsx 标签的反尖括号需要换行
  jsxBracketSameLine: false,
  // (x) => {} 箭头函数参数只有一个时是否要有小括号
  // avoid:省略括号  always:只有一个参数的时候,也需要括号
  arrowParens: 'avoid',
  // 每个文件格式化的范围是文件的全部内容
  rangeStart: 0,
  rangeEnd: Infinity,
  // 不需要写文件开头的 @prettier
  requirePragma: false,
  // 不需要自动在文件开头插入 @prettier
  insertPragma: false,
  // 使用默认的折行标准
  proseWrap: 'preserve',
  // 根据显示样式决定 html 要不要折行
  htmlWhitespaceSensitivity: 'css',
  // 换行符使用 lf
  endOfLine: 'lf'
}

ESLint

ESLintopen in new window 可组装的 JavaScript 和 JSX 检查工具。(ESLint中文文档 : https://cn.eslint.org/open in new window

ESLint 运行原理:

  • 使用 JavaScript 解析器 Espree 将 JavaScript 代码解析成 AST(抽象语法树,Abstract Syntax Tree)
  • 深度遍历 AST ,监听匹配过程。在解析 AST 之后,ESLint 会以 "从上至下" 再 "从下至上" 的顺序遍历每个选择器两次
  • 触发监听选择器的 rule 回调。在深度遍历的过程中,生效的每条规则都会对其中的某一个或多个选择器进行监听,每当匹配到选择器,监听该选择器的 rule,都会触发对应的回调
  • 具体的检测规则等细节内容

注: astexplorer.netopen in new window 可查看代码解析为 AST

ESLint 配置

parserOptions - 解析器配置

解析器选项可以在 .eslintrc.* 文件使用 parserOptions 属性设置。可用选项有:

  • parser
  • ecmaVersion : 默认设置为 3,5(默认),可使用 6、7、8、9 或 10 来指定使用的 ECMAScript 版本。
  • sourceType : 设置为 script (默认) 或 module(如果代码是 ECMAScript 模块)
  • allowImportExportEverywhere : 默认情况下,importexport 声明只能出现在代码头部。设置该选项为 true 时,则允许他们在代码的任何地方使用。
  • ecmaFeatures : 对象,表示使用的额外的语言特性
    • globalReturn : 允许在全局作用域下使用 return 语句
    • impliedStrict : 启用全局 strict mode (严格模式,如果 ecmaVersion 是 5 或更高)
    • jsx : 启用 JSXopen in new window

extends - 集成

从基础配置中继承已启用的规则,如果是数组后面继承前面的。

extends 属性值:

  • 指定配置的字符串(配置文件的路径、可共享配置的名称、eslint:recommendedeslint:all
    • eslint:recommended : 启用 ESLint 核心规则。ESLint 核心规则为在 规则列表 Rulesopen in new window 中被标记为 的规则。
    • Vue.js 2.x 相关规则配置,需安装 eslint-plugin-vue 插件
      • plugin:vue/recommended : 使用 Vue2.x 风格指南 中,规则归类为 优先级A:必要的 规则。
      • plugin:vue/strongly-recommended : 使用 Vue2.x 风格指南 中,规则归类为 优先级 B:强烈推荐 规则。
      • plugin:vue/recommended : 使用 Vue2.x 风格指南 中,规则归类为 优先级 C:推荐 规则。
    • Vue.js 3.x 相关规则配置,需安装 eslint-plugin-vue 插件
      • plugin:vue/vue3-recommended : 使用 Vue3.x 风格指南 中,规则归类为 优先级A:必要的 规则。
      • plugin:vue/vue3-strongly-recommended : 使用 Vue3.x 风格指南 中,规则归类为 优先级 B:强烈推荐 规则。
      • plugin:vue/vue3-recommended : 使用 Vue3.x 风格指南 中,规则归类为 优先级 C:推荐 规则。
    • eslint-plugin-prettier
      • plugin:prettier/recommended
  • 字符串数组:每个配置继承它前面的配置

rules 属性可以做下面的任何事情以扩展(或覆盖)规则:

  • 启用额外的规则
  • 改变继承的规则级别而不改变它的选项
  • 覆盖基础配置中的规则的选项

注:

plugins - 插件

ESLint 支持使用第三方插件。在使用插件之前,必须使用 npm 安装它。

相关插件:

rules - 规则

所有的规则默认都是禁用的。在配置文件中,使用 "extends": "eslint:recommended" 来启用推荐的规则。(在 规则列表 Rulesopen in new window 中被标记为 的规则)

env - 环境配置

在配置文件里指定环境,使用 env 关键字指定启用的环境,并设置为 true。常用的环境包括:

  • browser : 浏览器环境中的全局变量
  • node : Node.js 全局变量和 Node.js 作用域。
  • es6 : 启用除了 modules 以外的所有 ECMAScript 6 特性(该选项会自动设置 ecmaVersion 解析器选项为 6)
  • jest : Jest 全局变量。
  • ...

命令行

eslint [options] file.js [file.js] [dir] 可以通过运行 eslint -h 查看所有选项。

options 相关配置如下:

  • --fix : 指示 ESLint 尝试修复尽可能多的问题,不是所有的问题都能使用这个选项进行修复。
  • --ext : 指定 ESLint 在指定目录下查找 JavaScript 文件时,要使用的文件扩展名。
{
  "scripts": {
    "lint": "eslint --fix --ext .js,.vue src"
  }
}

Prettier 和 ESLint 的冲突解决

需要使用插件

相关配置如下

module.exports = {
  ...
  // 添加 Prettier 相关插件 - plugin:prettier/recommended
  extends: ['plugin:vue/recommended', 'eslint:recommended', 'plugin:prettier/recommended'],
  rules: {
    // 表示被 Prettier 标记的地方抛出错误信息
    'prettier/prettier': 'error',
    ...
  }
}

相关 ESLint 规则冲突可通过调整 ESLint 规则 Rules 进行处理。

ESLint 配置示例

.eslintrc.js
module.exports = {
  // 告知eslint是否要继续从父目录寻找配置文档,true表示停止在父级目录中寻找
  root: true,
  // 解析器选项
  parserOptions: {
    parser: 'babel-eslint',
    sourceType: 'module', // 设置为 "script" (默认) 或 "module"(如果代码是 ECMAScript 模块)
    allowImportExportEverywhere: true
  },
  // 环境配置
  env: {
    browser: true,
    node: true,
    es6: true
  },
  // 继承 - 从基础配置中继承已启用的规则,如果是数组后面继承前面的
  extends: ['plugin:vue/recommended', 'eslint:recommended', 'plugin:prettier/recommended'],
  // add your custom rules here
  // it is base on https://github.com/vuejs/eslint-config-vue
  // 配置规则,继承或覆盖重复规则
  rules: {
    'prettier/prettier': 'error',
    // 强制执行每行的最大属性数
    'vue/max-attributes-per-line': [
      2,
      {
        singleline: 10,
        multiline: {
          max: 1,
          allowFirstLine: false // 如果为 true,它允许属性与标签名在同一行
        }
      }
    ],
    // 要求在单行元素的内容前后都需要一个换行符
    'vue/singleline-html-element-content-newline': 'off',
    // 要求在多行元素的内容之前和之后需要一个换行符
    'vue/multiline-html-element-content-newline': 'off',
    // 改规则被弃用,由 vue/component-definition-name-casing 替代
    'vue/name-property-casing': ['error', 'PascalCase'],
    // vue/component-definition-name-casing - 强制组件定义名称的特定大小写
    'vue/component-definition-name-casing': ['error', 'PascalCase'],
    // 禁止使用v-html来防止XSS攻击
    'vue/no-v-html': 'off',
    // 禁止修改 props 的值
    'vue/no-mutating-props': 'off',
    // 强制 getter 和 setter 在对象中成对出现
    'accessor-pairs': [
      2,
      {
        setWithoutGet: true,
        getWithoutSet: false
      }
    ],
    // 强制箭头函数的箭头前后使用一致的空格:(a) => {}
    'arrow-spacing': [
      2,
      {
        before: true,
        after: true
      }
    ],
    // 禁止或强制在代码块中开括号前和闭括号后有空格
    'block-spacing': [2, 'always'],
    // 强制在代码块中使用一致的大括号风格
    'brace-style': [
      2,
      '1tbs', // 大括号风格: 1tbs 、 stroustrup 、 allman
      {
        allowSingleLine: true // 允许块的开括号和闭括号在 同一行
      }
    ],
    // 强制使用骆驼拼写法命名约
    camelcase: [
      0,
      {
        properties: 'always' //  强制属性名称为驼峰风格
      }
    ],
    // 要求或禁止末尾逗号 - never:禁用末尾逗号
    'comma-dangle': [2, 'never'],
    // 强制在逗号前后使用一致的空格
    'comma-spacing': [
      2,
      {
        before: false, // 禁止在逗号前使用空格
        after: true // 要求在逗号后使用一个或多个空格
      }
    ],
    // 强制使用一致的逗号风格 - last:要求逗号放在数组元素、对象属性或变量声明之后,且在同一行
    'comma-style': [2, 'last'],
    // 要求在构造函数中有 super() 的调用
    'constructor-super': 2,
    // 强制所有控制语句使用一致的括号风格 - multi-line:允许在单行中省略大括号,而if、else if、else、for、while 和 do,在其他使用中依然会强制使用大括号
    curly: [2, 'multi-line'],
    // 强制在点号之前和之后一致的换行 - property:表达式中的点号操作符应该和属性在同一行
    'dot-location': [2, 'property'],
    // 要求或禁止文件末尾存在空行
    'eol-last': 2,
    // 要求使用 === 和 !== - always:强制在任何情况下都使用 === 和 !== , 忽略 null
    eqeqeq: ['error', 'always', { null: 'ignore' }],
    // 强制 generator 函数中 * 号周围使用一致的空格
    'generator-star-spacing': [
      2,
      {
        before: true, // 在 * 和 function 关键字之间有空格,要求有空格,否则不允许有空格
        after: true // 在 * 和函数名之间有空格 (或匿名 generator 函数的左括号),要求有空格,否则不允许有空格
      }
    ],
    // 要求回调函数中有容错处理
    'handle-callback-err': [2, '^(err|error)$'],
    // 强制使用一致的缩进
    // indent: [
    //   2,
    //   2, // 2 个空格缩进
    //   {
    //     // 强制 switch 语句中的 case 子句的缩进级别。
    //     // SwitchCase: 1 - case 子句将相对于 switch 语句缩进 2 个空格
    //     SwitchCase: 1
    //   }
    // ],
    indent: 0,
    // 强制在 JSX 属性中一致地使用双引号或单引号
    // prefer-single - 强制所有不包含单引号的 JSX 属性值使用单引号
    // 'jsx-quotes': [2, 'prefer-single'],
    'jsx-quotes': 0,
    // 强制在对象字面量的属性中键和值之间使用一致的间距
    'key-spacing': [
      2,
      {
        beforeColon: false, // 禁止在对象字面量的键和冒号之间存在空格
        afterColon: true // 要求在对象字面量的冒号和值之间存在至少有一个空格
      }
    ],
    // 强制在关键字前后使用一致的空格
    'keyword-spacing': [
      2,
      {
        before: true, // 要求在关键字之前至少有一个空格
        after: true // 要求在关键字之后至少有一个空格
      }
    ],
    // 求构造函数首字母大写
    'new-cap': [
      2,
      {
        newIsCap: true, // 要求调用 new 操作符时有首字母大小的函数
        capIsNew: false // 要求调用首字母大写的函数时有 new 操作符
      }
    ],
    // 强制或禁止调用无参构造函数时有圆括号 - 强制括号后的新构造函数没有参数(默认)
    'new-parens': 2,
    // 禁用 Array 构造函数
    'no-array-constructor': 2,
    // 禁用 arguments.caller 或 arguments.callee
    'no-caller': 2,
    // 禁用 console
    'no-console': 'off',
    // 禁止修改类声明的变量
    'no-class-assign': 2,
    // 禁止条件表达式中出现赋值操作符
    'no-cond-assign': 2,
    // 禁止修改 const 声明的变量
    'no-const-assign': 2,
    // 禁止在正则表达式中使用控制字符
    'no-control-regex': 0,
    // 禁止删除变量
    'no-delete-var': 2,
    // 禁止 function 定义中出现重名参数
    'no-dupe-args': 2,
    // 禁止类成员中出现重复的名称
    'no-dupe-class-members': 2,
    // 禁止对象字面量中出现重复的 key
    'no-dupe-keys': 2,
    // 禁止出现重复的 case 标签
    'no-duplicate-case': 2,
    // 禁止在正则表达式中使用空字符集
    'no-empty-character-class': 2,
    // 禁止使用空解构模式
    'no-empty-pattern': 2,
    // 禁用 eval()
    'no-eval': 2,
    // 禁止对 catch 子句的参数重新赋值
    'no-ex-assign': 2,
    // 禁止扩展原生类型
    'no-extend-native': 2,
    // 禁止不必要的 .bind() 调用
    'no-extra-bind': 2,
    // 禁止不必要的布尔转换
    'no-extra-boolean-cast': 2,
    // 禁止不必要的括号 - functions:在 函数表达式周围禁止不必要的圆括号
    'no-extra-parens': [2, 'functions'],
    // 禁止 case 语句落空
    'no-fallthrough': 2,
    // 禁止数字字面量中使用前导和末尾小数点
    'no-floating-decimal': 2,
    // 禁止对 function 声明重新赋值
    'no-func-assign': 2,
    // 禁止使用类似 eval() 的方法
    'no-implied-eval': 2,
    // 禁止在嵌套的块中出现变量声明或 function 声明 - functions:禁止 function 声明出现在嵌套的语句块中
    'no-inner-declarations': [2, 'functions'],
    // 禁止 RegExp 构造函数中存在无效的正则表达式字符串
    'no-invalid-regexp': 2,
    // 禁止不规则的空白
    'no-irregular-whitespace': 2,
    // 禁用 __iterator__ 属性
    'no-iterator': 2,
    // 不允许标签与变量同名
    'no-label-var': 2,
    // 禁用标签语句
    'no-labels': [
      2,
      {
        allowLoop: false, // 如果这个选项被设置为 true,该规则忽略循环语句中的标签
        allowSwitch: false // 如果这个选项被设置为 true,该规则忽略 switch 语句中的标签
      }
    ],
    // 禁用不必要的嵌套块
    'no-lone-blocks': 2,
    // 禁止空格和 tab 的混合缩进
    'no-mixed-spaces-and-tabs': 2,
    // 禁止使用多个空格
    'no-multi-spaces': 2,
    // 禁止使用多行字符串
    'no-multi-str': 2,
    // 禁止出现多行空行
    'no-multiple-empty-lines': [
      2,
      {
        max: 1 // 强制最大连续空行数
      }
    ],
    // 在 ESLint v3.3.0 中已弃用,并由 no-global-assign 规则取代
    'no-native-reassign': 2,
    // 禁止对原生对象或只读的全局对象进行赋值
    'no-global-assign': 2,
    // 在 ESLint v3.3.0  中已弃用,并由 no-unsafe-negation 规则取代
    'no-negated-in-lhs': 2,
    // 禁止对关系运算符的左操作数使用否定操作符
    'no-unsafe-negation': 2,
    // 禁用 Object 的构造函数
    'no-new-object': 2,
    // 禁止调用 require 时使用 new 操作符
    'no-new-require': 2,
    // 禁止 Symbolnew 操作符和 new 一起使用
    'no-new-symbol': 2,
    // 禁止对 String,Number 和 Boolean 使用 new 操作符
    'no-new-wrappers': 2,
    // 禁止把全局对象作为函数调用
    'no-obj-calls': 2,
    // 禁用八进制字面量
    'no-octal': 2,
    // 禁止在字符串中使用八进制转义序列
    'no-octal-escape': 2,
    // 禁止对 __dirname 和 __filename 进行字符串连接
    'no-path-concat': 2,
    // 禁止直接调用 Object.prototypes 的内置属性
    'no-proto': 2,
    // 禁止多次声明同一变量
    'no-redeclare': 2,
    // 禁止正则表达式字面量中出现多个空格
    'no-regex-spaces': 2,
    // 禁止在 return 语句中使用赋值语句 - except-parens:禁止出现赋值语句,除非使用括号把它们括起来
    'no-return-assign': [2, 'except-parens'],
    // 禁止自我赋值
    'no-self-assign': 2,
    // 禁止自身比较
    'no-self-compare': 2,
    // 禁用逗号操作符
    'no-sequences': 2,
    // 禁止将标识符定义为受限的名字
    'no-shadow-restricted-names': 2,
    // 在 ESLint v3.3.0 中已弃用,并由 func-call-spacing 规则取代
    'no-spaced-func': 2,
    // 要求或禁止在函数标识符和其调用之间有空格 - never"(默认) 禁止在函数名和开括号之间有空格
    'func-call-spacing': 2,
    // 禁用稀疏数组
    'no-sparse-arrays': 2,
    // 禁止在构造函数中,在调用 super() 之前使用 this 或 super
    'no-this-before-super': 2,
    // 禁止抛出异常字面量
    'no-throw-literal': 2,
    // 禁用行尾空格
    'no-trailing-spaces': 2,
    // 禁用未声明的变量,除非它们在 /*global */ 注释中被提到
    'no-undef': 2,
    // 禁止将变量初始化为 undefined
    'no-undef-init': 2,
    // 禁止出现令人困惑的多行表达式
    'no-unexpected-multiline': 2,
    // 禁用一成不变的循环条件
    'no-unmodified-loop-condition': 2,
    // 禁止可以在有更简单的可替代的表达式时使用三元操作符
    'no-unneeded-ternary': [
      2,
      {
        defaultAssignment: false // 禁止条件表达式作为默认的赋值模式
      }
    ],
    // 禁止在 return、throw、continue 和 break 语句之后出现不可达代码
    'no-unreachable': 2,
    // 禁止在 finally 语句块中出现控制流语句
    'no-unsafe-finally': 2,
    // 禁止出现未使用过的变量
    'no-unused-vars': [
      2,
      {
        vars: 'all', // all - 检测所有变量,包括全局环境中的变量
        args: 'none' // none - 不检查参数
      }
    ],
    // 禁止不必要的 .call() 和 .apply()
    'no-useless-call': 2,
    // 禁止在对象中使用不必要的计算属性
    'no-useless-computed-key': 2,
    // 禁用不必要的构造函数
    'no-useless-constructor': 2,
    // 禁用不必要的转义字符
    'no-useless-escape': 0,
    // 禁止属性前有空白
    'no-whitespace-before-property': 2,
    // 禁用 with 语句
    'no-with': 2,
    // 强制函数中的变量要么一起声明要么分开声明
    'one-var': [
      2,
      {
        initialized: 'never' // 要求每个作用域的初始化的变量有多个变量声明
      }
    ],
    // 强制操作符使用一致的换行符
    'operator-linebreak': [
      2,
      'after', // 要求把换行符放在操作符后面
      {
        // 覆盖对指定的操作的全局设置
        overrides: {
          '?': 'before',
          ':': 'before'
        }
      }
    ],
    // 要求或禁止块内填充 - never:禁止块语句和类的开始或末尾有空行
    'padded-blocks': [2, 'never'],
    // 强制使用一致的反勾号、双引号或单引号
    quotes: [
      2,
      'single', // 要求尽可能地使用单引号
      {
        avoidEscape: true, // 允许字符串使用单引号或双引号,只要字符串中包含了一个其它引号,否则需要转义
        allowTemplateLiterals: true // 允许字符串使用反勾号
      }
    ],
    // 要求或禁止使用分号代替 ASI - never:禁止在语句末尾使用分号 (除了消除以 [、(、/、+ 或 - 开始的语句的歧义)
    semi: [2, 'never'],
    // 强制分号之前和之后使用一致的空格
    'semi-spacing': [
      2,
      {
        before: false, // 分号之前禁止有空格
        after: true // 分号之后强制有空格
      }
    ],
    // 强制在块之前使用一致的空格 - always:块语句必须总是至少有一个前置空格
    'space-before-blocks': [2, 'always'],
    // 'space-before-function-paren': [
    //   2,
    //   {
    //     anonymous: 'always',
    //     named: 'always',
    //     asyncArrow: 'always'
    //   }
    // ],
    // 强制在 function的左括号之前使用一致的空格 - never:禁止在参数的 ( 前面有空格
    // 'space-before-function-paren': [2, 'never'],
    'space-before-function-paren': 0,
    // 强制在圆括号内使用一致的空格 - never:强制圆括号内没有空格
    'space-in-parens': [2, 'never'],
    // 要求操作符周围有空格
    'space-infix-ops': 2,
    // 强制在一元操作符前后使用一致的空格
    'space-unary-ops': [
      2,
      {
        words: true, // 适用于单词类一元操作符,操作符之后禁用空格。例如:new、delete、typeof、void、yield
        nonwords: false // 操作符左右要求有空格,适用于这些一元操作符: -、+、--、++、!、!!
      }
    ],
    // 强制在注释中 // 或 /* 使用一致的空格
    'spaced-comment': [
      2,
      'always', // // 或 /* 必须跟随至少一个空白
      {
        markers: ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
      }
    ],
    // 要求或禁止模板字符串中的嵌入表达式周围空格的使用 - never (默认) :禁止花括号内出现空格
    'template-curly-spacing': [2, 'never'],
    // 要求使用 isNaN() 检查 NaN
    'use-isnan': 2,
    // 强制 typeof 表达式与有效的字符串进行比较
    'valid-typeof': 2,
    // 要求 IIFE 使用括号括起来 - any:强制总是包裹,但允许其它风格。
    'wrap-iife': [2, 'any'],
    // 强制在 yield* 表达式中 * 周围使用空格
    'yield-star-spacing': [2, 'both'],
    // 要求或禁止 “Yoda” 条件
    yoda: [2, 'never'],
    // 要求使用 const 声明那些声明后不再被修改的变量
    'prefer-const': 2,
    // 禁用 debugger
    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
    // 强制在大括号中使用一致的空格
    'object-curly-spacing': [
      2,
      'always', // 要求花括号内有空格 (除了 {})
      {
        objectsInObjects: true // 要求以对象元素开始或结尾的对象的花括号中有空格 (当第一个选项为 always 时生效)
      }
    ],
    // 强制数组方括号中使用一致的空格 - never:禁止在数组括号内出现空格
    'array-bracket-spacing': [2, 'never']
  }
}
.eslintignore
build/*.js
src/assets
public
dist

Stylelint

stylelintopen in new window 一个强大的,现代的代码检查工具,可以避免错误并在样式中强制执行约定。(stylelint中文文档 : https://stylelint.docschina.org/open in new window

相关插件

  • stylelint-config-standard : stylelint 标准(standard)配置
  • stylelint-config-css-modules : 调整stylesint规则以接受css模块特定的语法。
  • stylelint-config-rational-order : 一个用于规范css属性写作顺序的规则集成。配合stylelint-order插件使用。
  • stylelint-config-prettier : 关闭所有不必要的或可能与 Prettier 冲突的规则
  • stylelint-no-unsupported-browser-features : 检查正在使用的 CSS 是否被目标浏览器支持
  • stylelint-order : 用于规范样式属性写作顺序的插件
  • stylelint-declaration-block-no-ignored-properties : 用于提示写矛盾的样式。例如:{ display: inline; width: 100px; }

相关配置

Stylelint 配置示例

.stylelintrc.js
module.exports = {
  extends: [
    'stylelint-config-standard',
    'stylelint-config-css-modules',
    'stylelint-config-rational-order',
    'stylelint-config-prettier',
    'stylelint-no-unsupported-browser-features'
  ],
  plugins: ['stylelint-order', 'stylelint-declaration-block-no-ignored-properties'],
  rules: {
    // 禁止低优先级的选择器出现在高优先级的选择器之后
    'no-descending-specificity': null,
    // https://github.com/stylelint/stylelint/issues/4114
    // 允许在 calc 函数中使用无效的表达式
    'function-calc-no-invalid': null,
    // 要求或禁止 url 使用引号
    'function-url-quotes': 'always',
    // 禁止字体家族名称列表中缺少通用家族
    'font-family-no-missing-generic-family-keyword': null,
    'plugin/declaration-block-no-ignored-properties': true,
    // 禁止未知单位
    'unit-no-unknown': [true, { ignoreUnits: ['rpx'] }]
  }
}

Commit 规范

Conventional Commit(约定式提交)open in new window :一种用于给提交信息增加人机可读含义的规范

Commit message 格式

Commit message 包含三个部分: HeaderBodyFooter。其中,Header 是必需的,BodyFooter 可以省略。

<type>(<scope>): <subject>

<body>

<footer>

Header 部分只有一行,包括三个字段:type(必需)、scope(可选)和 subject(必需)。

  • type : 用于说明 commit 的类别,只允许使用下面7个标识。
    • feat:新功能(feature)
    • fix:修补bug
    • docs:文档(documentation)
    • style: 格式(不影响代码运行的变动)
    • refactor:重构(即不是新增功能,也不是修改bug的代码变动)
    • test:增加测试
    • chore:构建过程或辅助工具的变动
  • scope : 用于说明 commit 影响的范围,比如数据层、控制层、视图层等等,视项目不同而不同。
  • subject : 用于说明 commit 目的的简短描述,不超过50个字符
    • 以动词开头,使用第一人称现在时,比如change,而不是changed或changes
    • 第一个字母小写
    • 结尾不加句号(.)

Body

Body 部分是对本次 commit 的详细描述,可以分成多行。

More detailed explanatory text, if necessary.  Wrap it to about 72 characters or so.

Further paragraphs come after blank lines.

- Bullet points are okay, too
- Use a hanging indent

有两个注意点。

  • 使用第一人称现在时,比如使用 change 而不是 changed 或 changes。
  • 应该说明代码变动的动机,以及与以前行为的对比。

Footer 部分只用于两种情况。

  • 不兼容变动

    如果当前代码与上一个版本不兼容,则 Footer 部分以 BREAKING CHANGE 开头,后面是对变动的描述、以及变动理由和迁移方法。

    BREAKING CHANGE: isolate scope bindings definition has changed.
    
      To migrate the code follow the example below:
    
      Before:
    
      scope: {
        myAttr: 'attribute',
      }
    
      After:
    
      scope: {
        myAttr: '@',
      }
    
      The removed `inject` wasn't generaly useful for directives so there should be no code using it.
    
  • 关闭 Issue

    如果当前 commit 针对某个issue,那么可以在 Footer 部分关闭这个 issue 。

Revert

如果当前 commit 用于撤销以前的 commit,则必须以 revert: 开头,后面跟着被撤销 Commit 的 Header。

revert: feat(pencil): add 'graphiteWidth' option

This reverts commit 667ecc1654a317a13331b17617d973392f415f02.

Body部分的格式是固定的,必须写成 This reverts commit <hash>,其中的 hash 是被撤销 commit 的 SHA 标识符。

如果当前 commit 与被撤销的 commit,在同一个发布(release)里面,那么它们都不会出现在 Change log 里面。如果两者在不同的发布,那么当前 commit,会出现在 Change log 的Reverts小标题下面。

Commitizen 规范提交代码

Commitizenopen in new window 是一个帮助撰写规范 commit message 的工具。它有一个命令行工具 cz-cli

Commitizen 安装

# 安装 Commitizen
$ npm install commitizen --save-dev

# 初始化项目
$ npx commitizen init cz-conventional-changelog --save-dev --save-exact

安装 cz-conventional-changelog 初始化项目时,会在 package.json 中,添加如下配置

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

Commitizen 使用

git commit 命令修改为 git cz,则会出现相关选项,用来生成符合格式的 Commit message。

$ git cz

cz-cli@4.2.3, cz-conventional-changelog@3.3.0

? Select the type of change that you're committing: (Use arrow keys)
> feat:     A new feature
  fix:      A bug fix
  docs:     Documentation only changes
  style:    Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
  refactor: A code change that neither fixes a bug nor adds a feature
  perf:     A code change that improves performance
  test:     Adding missing tests or correcting existing tests
(Move up and down to reveal more choices)

【注意】运行 git cz 时,如果提示 git: 'cz' is not a git command. See 'git --help'. ,可以将 commitizen 全局安装: npm install -g commitizen

cz-customizable 自定义配置

cz-customizable 初始化项目

# 使用 cz-customizable 初始化项目,需要加 --force 覆盖
$ npx commitizen init cz-customizable --save-dev --save-exact --force

安装 cz-customizable 初始化项目时,会在 package.json 中,添加如下配置

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

cz-customizable 使用

在项目根目录创建 .cz-config.js 文件,进行配置。官方示例open in new window

公司内部的代码仓库也不需要管理 issue,可以把询问 body 和 footer 的步骤跳过(在 .cz-config.js 中修改成 skipQuestions: ['body', 'footer'])。

module.exports = {
  // type 类型(定义之后,可通过上下键选择)
  types: [
    { value: 'feat', name: 'feat:     新增功能' },
    { value: 'fix', name: 'fix:      修复 bug' },
    { value: 'docs', name: 'docs:     文档变更' },
    { value: 'style', name: 'style:    代码格式(不影响功能,例如空格、分号等格式修正)' },
    { value: 'refactor', name: 'refactor: 代码重构(不包括 bug 修复、功能新增)' },
    { value: 'perf', name: 'perf:     性能优化' },
    { value: 'test', name: 'test:     添加、修改测试用例' },
    { value: 'build', name: 'build:    构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等)' },
    { value: 'ci', name: 'ci:       修改 CI 配置、脚本' },
    { value: 'chore', name: 'chore:    对构建过程或辅助工具和库的更改(不影响源文件、测试用例)' },
    { value: 'revert', name: 'revert:   回滚 commit' }
  ],

  // scope 类型(定义之后,可通过上下键选择)
  scopes: [
    ['components', '组件相关'],
    ['hooks', 'hook 相关'],
    ['utils', 'utils 相关'],
    ['element-ui', '对 element-ui 的调整'],
    ['styles', '样式相关'],
    ['deps', '项目依赖'],
    ['auth', '对 auth 修改'],
    ['other', '其他修改'],
    // 如果选择 custom,后面会让你再输入一个自定义的 scope。也可以不设置此项,把后面的 allowCustomScopes 设置为 true
    ['custom', '以上都不是?我要自定义']
  ].map(([value, description]) => {
    return {
      value,
      name: `${value.padEnd(30)} (${description})`
    }
  }),

  // 是否允许自定义填写 scope,在 scope 选择的时候,会有 empty 和 custom 可以选择。
  // allowCustomScopes: true,

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

  // 针对每一个 type 去定义对应的 scopes,例如 fix
  /*
  scopeOverrides: {
    fix: [
      { name: 'merge' },
      { name: 'style' },
      { name: 'e2eTest' },
      { name: 'unitTest' }
    ]
  },
  */

  // 交互提示信息
  messages: {
    type: '确保本次提交遵循 Angular 规范!\n选择你要提交的类型:',
    scope: '\n选择一个 scope(可选):',
    // 选择 scope: custom 时会出下面的提示
    customScope: '请输入自定义的 scope:',
    subject: '填写简短精炼的变更描述:\n',
    body: '填写更加详细的变更描述(可选)。使用 "|" 换行:\n',
    breaking: '列举非兼容性重大的变更(可选):\n',
    footer: '列举出所有变更的 ISSUES CLOSED(可选)。 例如: #31, #34:\n',
    confirmCommit: '确认提交?'
  },

  // 设置只有 type 选择了 feat 或 fix,才询问 breaking message
  allowBreakingChanges: ['feat', 'fix'],

  // 跳过要询问的步骤
  // skipQuestions: ['body', 'footer'],

  // subject 限制长度
  subjectLimit: 100,
  breaklineChar: '|', // 支持 body 和 footer
  // footerPrefix : 'ISSUES CLOSED:'
  // askForBreakingChangeFirst : true,
}

Commitlint 提交信息验证

Commitlintopen in new windowgit commit 提交之前使用 git 钩子来验证信息。提交不符合规则的信息将会被阻止提交。

Commitlint 安装

# 安装 commitlint
$ npm i @commitlint/config-conventional @commitlint/cli -D

Commitlint 配置

在项目根目录下,创建 commitlint.config.js 文件。配置如下:

// commitlint.config.js
module.exports = {
  // 继承的规则
  extends: ['@commitlint/config-conventional'],
  // 定义规则类型
  rules: {
    // type 类型定义,表示 git 提交的 type 必须在以下类型范围内
    'type-enum': [
      2,
      'always',
      [
        'feat', // 新增功能
        'fix', // 修复 bug
        'docs', // 文档变更
        'style', // 代码格式(不影响功能,例如空格、分号等格式修正)
        'refactor', // 代码重构(不包括 bug 修复、功能新增)
        'perf', // 性能优化
        'test', // 添加、修改测试用例
        'build', // 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等
        'ci', // 修改 CI 配置、脚本
        'chore', // 对构建过程或辅助工具和库的更改(不影响源文件、测试用例
        'revert', // 回滚 commit
      ]
    ],
    'subject-case': [0] // subject 大小写不做校验
  }
}

使用 husky 的 commit-msg hook 触发验证提交信息的命令。在 .husky 目录下,创建 commit-msg 文件,并执行添加 commit message 的验证命令。

# 添加执行 commit message 的验证命令
$ npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"

集成 husky 和 lint-staged

常用 Git HOOK :

  • pre-commitgit commit 执行前。
    • 用于检查即将提交的快照,例如,检查是否有所遗漏,确保测试运行,以及核查代码。
    • 如果该钩子以非零值退出,Git 将放弃此次提交。
    • 可以用 git commit --no-verify 来绕过。
  • commit-msggit commit 执行前。
    • 可以用来在提交通过前验证项目状态或提交
    • 如果该钩子以非零值退出,Git 将放弃此次提交。

注:更多 Git Hook 请参考 自定义 Git - Git 钩子open in new window

husky

# 安装 husky 依赖包,并初始化项目
$ npx husky-init && npm install

安装并初始化命令,主要进行了如下操作:

  • 安装 husky 依赖包

  • 在项目根目录下,创建 .husky 目录

  • .husky 目录下,创建了 pre-commit Hook 文件

    #!/bin/sh
    . "$(dirname "$0")/_/husky.sh"
    
    npm test
    
  • 修改 package.json 中的 script 中,增加 "prepare": "husky install"

pre-commit Hook文件配置

当执行 git commit -m 'xxx' 的时候,会先对 src 目录下的 .vue.js.ts 文件执行 eslint --fx 命令,如果 ESLint 检查通过,则成功 commit ,否则终止 commit

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

eslint --fix ./src --ext .vue,.js,.ts

commit-msg Hook文件配置

当执行 git commit -m 'xxx' 的时候,使用 commitlint 对提交信息进验证,不符合规范的 commit 将不能提交。

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

npx --no-install commitlint --edit $1

lint-staegd

lint-staged 一般结合 husky 来使用,可以让 husky 的 hook 触发的命令只作用于 git add 的文件(即 git 暂存区的文件),而不会影响到其他文件。

  • 安装

    # 安装 lint-staged 依赖包
    $ npm i lint-staged -D
    
  • 配置: 在 package.json 中增加 lint-staged 配置项,配置如下,表示只对 git 暂存区的 src 目录下的 .vue.js.ts 文件执行 eslint --fix

    {
      "lint-staged": {
        "src/**/*.{js,vue}": [
          "eslint --fix",
          "git add"
        ]
      },
    }
    
  • 修改 .husky/pre-commit hook 的触发命令为:npx lint-staged

    #!/bin/sh
    . "$(dirname "$0")/_/husky.sh"
    
    npx lint-staged
    

相关参考

上次编辑于:
贡献者: lingronghai