2021-05-06-vue编码规范

背景

前端规范管理,eslint、stylelint辅助管理。

ESLint、Stylelint、Prettier、EditorConfig这些检测工具的工作原理都比较类似,不外乎对象实例化-编译Javascript-遍历AST检测-输出检测结果这四步流程

  • ESLint:检测代码质量和代码风格;
  • StyleLint:检测样式代码质量和代码风格;
  • Prettier:检测代码风格;(ESLint、StyleLint调和剂,解决重复的规则【Error Loop】)
  • EditorConfig:统一不同IDE之间的配置差异,例如一个编辑器行缩进使用的是Tab,另一个编辑器使用的则是空格Space;

规范配置步骤

  • vue项目完整的规范配置,分为以下四步:
    1. 核心规则配置:各检测工具必要的规则配置文件,没有它们检测工具无法执行,如.eslintrc文件。
    2. 命令行脚本配置:package.json的script属性配置执行脚本,开发人员手动执行命令,如npm run lint。
    3. 开发环境配置:webpack开发环境构建配置中,添加开发环境运行时检测插件,类似于启动器,运行时自动执行各自的检测工具进行检测。
    4. 编辑器配置:VSCode编辑器安装插件并配置属性,实时进行检测。

第一步:

1. eslint配置 - .eslintrc.js

module.exports = {
  root: true,
  env: {
    node: true,
    browser: true,
  },
  extends: ['plugin:vue/essential', 'airbnb-base', 'plugin:prettier/recommended'],
  parserOptions: {
    parser: 'babel-eslint',
    ecmaFeatures: {
      jsx: true,
    },
  },
  plugins: ['import'],
  settings: {
    // 别名
    'import/resolver': {
      alias: {
        map: [['@', './src/']],
        extensions: ['.js', '.vue'],
      },
    },
  },
  rules: {
    'func-names': 0,
    'import/extensions': 0,
    'no-unused-expressions': 0,
    /**
     * 导入语句前不允许有任何非导入语句
     */
    'import/first': 'error',
    /**
     * 禁止重复导入模块
     */
    'import/no-duplicates': 'error',
    /**
     * 禁止使用 let 导出
     */
    'import/no-mutable-exports': 'warn',
    /**
     * 禁用导入的模块时使用 webpack 特有的语法(感叹号)
     */
    'import/no-webpack-loader-syntax': 'warn',
    /**
     * 当只有一个导出时,必须使用 export default 来导出
     */
    'import/prefer-default-export': 'off',
    'import/no-extraneous-dependencies': [
      'error',
      {
        devDependencies: true,
      },
    ],
    'no-plusplus': [
      'error',
      {
        allowForLoopAfterthoughts: true,
      },
    ],
    'import/no-dynamic-require': 0,
    'global-require': 0,
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
  },
};

1.1eslint忽略文件 .eslintignore

dist/**
es/**
lib/**
**/node_modules

2. stylelint配置

module.exports = {
  // processors: ['stylelint-processor-html'],
  // extends: ['stylelint-config-tx 替换tx','css-properties-sorting', 'stylelint-config-prettier'],
  extends: ['css-properties-sorting', 'stylelint-config-prettier'],
  plugins: ['stylelint-order'], // stylelint-order是CSS属性排序插件
  rules: {
    // "color-hex-case": "lower", // 颜色值为小写字母(stylelint-config-standard)
    // "color-no-invalid-hex": true, // 颜色值不能为无效值(stylelint-config-standard)
    'font-family-name-quotes': 'always-where-recommended', // 字体系列中命名时带引号
    'function-url-quotes': 'always', // 地址一定要写引号
    // 'number-leading-zero': 'never', // 或分数低于1的数字是否需要前导零
    'number-no-trailing-zeros': true, // 禁止在数量尾随零
    // 'string-quotes': 'double', // 指定字串
    'length-zero-no-unit': true, // 禁止单位零长度。
    'value-keyword-case': 'lower', // 指定小写关键字的值
    'value-list-comma-newline-after': 'always-multi-line', // 在值列表的逗号后指定一个换行符或禁止留有空格
    'shorthand-property-no-redundant-values': true, // 不允许在简写属性冗余值
    // "property-case": "lower", // 为属性指定小写(stylelint-config-standard)
    'keyframe-declaration-no-important': true, // 不允许!important在关键帧声明
    // "block-closing-brace-empty-line-before": "never", // 不允许关闭括号前空一行(stylelint-config-standard)
    // "block-closing-brace-newline-after": "always", // 需要一个换行符关闭括号后的空白(stylelint-config-standard)
    // "block-opening-brace-newline-after": "always-multi-line", // 开括号的块之后需要新的一行(stylelint-config-standard)
    'selector-class-pattern': "^[a-z]+([a-z0-9]?|[a-z0-9(-_'% ]*[a-z0-9)])$", // 兼容stylelint的bug(less的mixin没处理好), 暂时先这样处理~~~指定一个模式类选择符,限制选择器名称写法
    'selector-id-pattern': "^[a-z]+([a-z0-9]?|[a-z0-9(-_'% ]*[a-z0-9)])$", // 兼容stylelint的bug(less的mixin没处理好), 暂时先这样处理~~~指定一个模式,id选择器,限制选择器名称写法
    'no-empty-source': null, // 不允许空的来源
    'at-rule-no-unknown': null, // 不允许at-rules不明
    // "indentation": 2, // 指定缩进(stylelint-config-standard)
    'max-nesting-depth': [5, { severity: 'warning' }], // 允许嵌套的深度为5
    'no-duplicate-selectors': true, // 不允许重复的选择器
    // "no-eol-whitespace": true, // 不允许行尾空白(stylelint-config-standard)
    // "no-invalid-double-slash-comments": true // 不允许双斜杠注释(/ /…)不支持CSS(stylelint-config-standard)
    'order/order': [
      // 指定声明块内的内容顺序
      ['custom-properties', 'declarations'],
    ],
    // "declaration-block-properties-order": "alphabetical",
    'order/properties-order': [
      // 指定声明块内属性的字母顺序
      'position',
      'top',
      'right',
      'bottom',
      'left',
      'z-index',
      'display',
      'float',
      'width',
      'height',
      'max-width',
      'max-height',
      'min-width',
      'min-height',
      'padding',
      'padding-top',
      'padding-right',
      'padding-bottom',
      'padding-left',
      'margin',
      'margin-top',
      'margin-right',
      'margin-bottom',
      'margin-left',
      'margin-collapse',
      'margin-top-collapse',
      'margin-right-collapse',
      'margin-bottom-collapse',
      'margin-left-collapse',
      'overflow',
      'overflow-x',
      'overflow-y',
      'clip',
      'clear',
      'font',
      'font-family',
      'font-size',
      'font-smoothing',
      'osx-font-smoothing',
      'font-style',
      'font-weight',
      'hyphens',
      'src',
      'line-height',
      'letter-spacing',
      'word-spacing',
      'color',
      'text-align',
      'text-decoration',
      'text-indent',
      'text-overflow',
      'text-rendering',
      'text-size-adjust',
      'text-shadow',
      'text-transform',
      'word-break',
      'word-wrap',
      'white-space',
      'vertical-align',
      'list-style',
      'list-style-type',
      'list-style-position',
      'list-style-image',
      'pointer-events',
      'cursor',
      'background',
      'background-attachment',
      'background-color',
      'background-image',
      'background-position',
      'background-repeat',
      'background-size',
      'border',
      'border-collapse',
      'border-top',
      'border-right',
      'border-bottom',
      'border-left',
      'border-color',
      'border-image',
      'border-top-color',
      'border-right-color',
      'border-bottom-color',
      'border-left-color',
      'border-spacing',
      'border-style',
      'border-top-style',
      'border-right-style',
      'border-bottom-style',
      'border-left-style',
      'border-width',
      'border-top-width',
      'border-right-width',
      'border-bottom-width',
      'border-left-width',
      'border-radius',
      'border-top-right-radius',
      'border-bottom-right-radius',
      'border-bottom-left-radius',
      'border-top-left-radius',
      'border-radius-topright',
      'border-radius-bottomright',
      'border-radius-bottomleft',
      'border-radius-topleft',
      'content',
      'quotes',
      'outline',
      'outline-offset',
      'opacity',
      'filter',
      'visibility',
      'size',
      'zoom',
      'transform',
      'box-align',
      'box-flex',
      'box-orient',
      'box-pack',
      'box-shadow',
      'box-sizing',
      'table-layout',
      'animation',
      'animation-delay',
      'animation-duration',
      'animation-iteration-count',
      'animation-name',
      'animation-play-state',
      'animation-timing-function',
      'animation-fill-mode',
      'transition',
      'transition-delay',
      'transition-duration',
      'transition-property',
      'transition-timing-function',
      'background-clip',
      'backface-visibility',
      'resize',
      'appearance',
      'user-select',
      'interpolation-mode',
      'direction',
      'marks',
      'page',
      'set-link-source',
      'unicode-bidi',
      'speak',
    ],
    'selector-pseudo-element-no-unknown': [
      true,
      {
        ignorePseudoElements: ['v-deep'],
      },
    ],
  },
};

3. prettier配置 - prettier.config.js

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

4. EditorConfig部分:

editorConfig的作用是专门抹平不同编辑器同一配置之间差异。比较神奇的是它并不需要项目配依赖且配置及其简单,只需要编辑器安装指定扩展插件以及项目配置文件.editorConfig即可。

  • VSCode中安装插件:EditorConfig for VS Code
  • 项目根目录下创建并配置.editorConfig:
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 4
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

依赖清单

// 依赖清单
[
    // ESLint
    eslint,
    babel-eslint,
    eslint-plugin-vue,
    eslint-webpack-plugin,      // 开发环境实时检测(可选
    
    // StyleLint(可选)
    stylelint,
    stylelint-webpack-plugin,   // 开发环境实时检测
    stylelint-config-standard,
    
    // Prettier(可选)
    prettier,
    eslint-config-prettier,
    eslint-plugin-prettier,
    stylelint-config-prettier
]

完整示例


    // ESLint
    "eslint": "^6.7.2",
    "eslint-config-airbnb-base": "^14.2.1",
    "eslint-import-resolver-alias": "^1.1.2",
    "eslint-plugin-vue": "^6.2.2",

    // StyleLint(可选)
    "stylelint": "^13.12.0",
    "stylelint-order": "^4.1.0",

    // Prettier(可选)
    "prettier": "^2.3.2",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-prettier": "^3.3.1",
    "stylelint-config-prettier": "^8.0.2",

第二步 - 命令行脚本配置

在package.json文件的script脚本中配置

{
 // 省略其余属性...
 "script": {
     "eslint-lint": "eslint --ext .js,.vue --fix src",
     "stylelint-lint": "stylelint src/**/.css src/**/.vue src/**/.less --fix"
 }
}

【有待争议的规则点】

vue相关

1、vue/html-self-closing 自关闭标签 2、vue/attributes-order 属性顺序 3、vue/attribute-hyphenation 属性需用连字符分割单词,(这里不建议,父子组件使用时,无法直接搜索)