Vue3 + TypeScript + vite 实现前端代码规范化

1,378 阅读7分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

在多人协同开发中,如何确保代码的语法和风格统一,成为了一个不可避免的问题。本文将介绍在 Vue3 + TypeScript 的项目中如何实现统一的代码规范。

ESLint + Prettier

首先我们来了解一下 ESLintPrettier

ESLint 是一个语法规则和代码风格的检查工具,可以用来保证写出语法正确、风格统一的代码。

Prettier 是一个代码格式化工具。

ESLintPrettier 通常是一起使用的,前者只负责检测修复语法规则,后者负责代码格式化。

安装

首先,我们需要安装以下依赖

pnpm i eslint eslint-config-prettier eslint-plugin-prettier eslint-plugin-vue @typescript-eslint/eslint-plugin @typescript-eslint/parser prettier -D
依赖版本作用
eslint8.13.0ESLint 核心库
eslint-config-prettier8.5.0关掉所有和 Prettier 冲突的 ESLint 的配置
eslint-plugin-prettier4.0.0将 Prettier 的 rules 以插件的形式加入到 ESLint 里面
eslint-plugin-vue8.6.0为 Vue 使用 ESlint 的插件
@typescript-eslint/eslint-plugin5.19.0ESLint 插件,包含了各类定义好的检测 TypeScript 代码的规范
@typescript-eslint/parser5.19.0ESLint 的解析器,用于解析 TypeScript,从而检查和规范 TypeScript 代码
prettier2.6.2Prettier 核心库

配置 ESLint

安装完成之后,在根目录下创建 .eslintrc.js 文件,并添加以下代码

// @ts-check
module.exports = {
  root: true,
  env: {
    browser: true,
    node: true,
    es6: true
  },
  parser: 'vue-eslint-parser',
  parserOptions: {
    parser: '@typescript-eslint/parser',
    ecmaVersion: 2020,
    sourceType: 'module',
    jsxPragma: 'React',
    ecmaFeatures: {
      jsx: true
    }
  },
  extends: [
    'plugin:vue/vue3-recommended',
    'plugin:@typescript-eslint/recommended',
    'prettier',
    'plugin:prettier/recommended'
  ],
  rules: {
    'vue/script-setup-uses-vars': 'error',
    '@typescript-eslint/ban-ts-ignore': 'off',
    '@typescript-eslint/explicit-function-return-type': 'off',
    '@typescript-eslint/no-explicit-any': 'off',
    '@typescript-eslint/no-var-requires': 'off',
    '@typescript-eslint/no-empty-function': 'off',
    'vue/custom-event-name-casing': 'off',
    'no-use-before-define': 'off',
    '@typescript-eslint/no-use-before-define': 'off',
    '@typescript-eslint/ban-ts-comment': 'off',
    '@typescript-eslint/ban-types': 'off',
    '@typescript-eslint/no-non-null-assertion': 'off',
    '@typescript-eslint/explicit-module-boundary-types': 'off',
    '@typescript-eslint/no-unused-vars': 'error',
    'no-unused-vars': 'error',
    'space-before-function-paren': 'off',
    'vue/attributes-order': 'off',
    'vue/one-component-per-file': 'off',
    'vue/html-closing-bracket-newline': 'off',
    'vue/max-attributes-per-line': 'off',
    'vue/multiline-html-element-content-newline': 'off',
    'vue/singleline-html-element-content-newline': 'off',
    'vue/attribute-hyphenation': 'off',
    'vue/require-default-prop': 'off',
    'vue/require-explicit-emits': 'off',
    'vue/multi-word-component-names': 'off'
  }
}

我们会看到,在配置文件里面,有 extends 这个字段,这个字段可以理解为别人已经配置好的 ESlint 规则,我们可以直接拿来用,如果有多个规则重复,则后面的会覆盖前面的,所以引入顺序很重要。

当然,如果对 extends 的引入的规则都不满意的话,可以在 rules 中进行某个规则的设置。

rules 可以根据团队的情况自行配置,更多的配置请查看 eslint-plugin-vue 以及 @typescript-eslint/eslint-plugin

如果对某个文件或者目录下的文件不检测,可以在根目录下创建 .eslintignore 文件,然后添加对应的文件名或路径即可,如不检测 dist 目录下的所有文件。

/dist*

配置 Prettier

在根目录下创建 prettier.config.js 文件,并添加以下代码

module.exports = {
  printWidth: 100,
  tabWidth: 2,
  useTabs: false,
  semi: false,
  vueIndentScriptAndStyle: false,
  singleQuote: true,
  quoteProps: 'as-needed',
  bracketSpacing: true,
  trailingComma: 'none',
  jsxSingleQuote: false,
  arrowParens: 'always',
  insertPragma: false,
  requirePragma: false,
  proseWrap: 'never',
  htmlWhitespaceSensitivity: 'strict',
  endOfLine: 'auto',
  rangeStart: 0
}

更多 Prettier 配置可以查看官方文档 www.prettier.cn/docs/option…

或者通过在线配置 www.prettier.cn/playground/ ,选择对应的语言及选项,确认好配置点击 Copy config JSON 进行复制,然后粘贴到项目中来即可。

同理,如果对某个文件或者某个路径下的文件不进行检测的话,在根目录下创建 .prettierignore 并添加对应文件名或路径,如

/dist*

修复

配置完 ESlintPrettier 之后,我们在 package.json 中去添加对应的修复命令

"lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src",
"lint:format": "prettier --write --loglevel warn \"src/**/*.{js,ts,json,tsx,css,less,vue,html,md}\""

然后运行 npm run lint:eslint 执行 ESlint 的检测修复,运行 npm run lint:format 执行 Prettier 代码格式化。

这样就可以显示代码的语法和风格检测修复了,如果能修复的,插件自动修复,没办法修复的,需要自己手动进行修复。

实时检测修复

代码的语法和风格检测,都是需要运行对应命令才能进行的,那么如何实时检测呢,这里需要使用到一个插件 vite-plugin-eslint

首先安装 vite-plugin-eslint

pnpm i vite-plugin-eslint -D

然后在 vite.config.ts 中添加以下代码

+ import EslintPlugin from 'vite-plugin-eslint'

export default defineConfig({
  plugins: [
    // ...
+    EslintPlugin({
+      cache: false,
+      include: ['src/**/*.vue', 'src/**/*.ts', 'src/**/*.tsx'] // 检查的文件
+    })
  ]
})

这样,在我们保存的时候,控制台上就能看到相对应的错误了。

配合编辑器

我们也可以在代码编辑器中安装相应的扩展,然后保存的时候自动帮我们进行代码格式化,这里以 VScode 为例。

安装 Prettier ESLintPrettierESLint 这三个扩展,并且设置 Format on Save 为 true,这样在我们代码保存的时候,编辑器就能自动帮我们进行格式化代码了。

image.png

StyleLint

以上我们针对的都是 templatejavaScript ,在样式中,也同样可以通过插件的形式来实现代码风格的统一。

安装

首先安装以下依赖

pnpm i stylelint stylelint-config-html stylelint-config-prettier stylelint-config-recommended stylelint-config-standard stylelint-order postcss-html -D
依赖版本作用
stylelint14.6.1StyleLint 核心库
stylelint-config-html1.0.0解析 HTML 文件中的样式
stylelint-config-prettier9.0.3结合 Prettier 使用
stylelint-config-recommended7.0.0用来检验 CSS 文件
stylelint-config-standard25.0.0StyleLint 的标准可共享配置
stylelint-order5.0.0顺序包,用于排列 CSS 属性的顺序
stylelint-order5.0.0用于解析 HTML(和类似 HTML)的 PostCSS语法

配置

在根目录下创建 stylelint.config.js 文件,并添加以下代码

module.exports = {
  root: true,
  plugins: ['stylelint-order'],
  customSyntax: 'postcss-html',
  extends: ['stylelint-config-standard', 'stylelint-config-prettier'],
  rules: {
    'selector-pseudo-class-no-unknown': [
      true,
      {
        ignorePseudoClasses: ['global', 'deep']
      }
    ],
    'at-rule-no-unknown': [
      true,
      {
        ignoreAtRules: ['function', 'if', 'each', 'include', 'mixin']
      }
    ],
    'no-empty-source': null,
    'named-grid-areas-no-invalid': null,
    'unicode-bom': 'never',
    'no-descending-specificity': null,
    'font-family-no-missing-generic-family-keyword': null,
    'declaration-colon-space-after': 'always-single-line',
    'declaration-colon-space-before': 'never',
    'declaration-block-trailing-semicolon': null,
    'rule-empty-line-before': [
      'always',
      {
        ignore: ['after-comment', 'first-nested']
      }
    ],
    'unit-no-unknown': [
      true,
      {
        ignoreUnits: ['rpx']
      }
    ],
    'order/order': [
      [
        'dollar-variables',
        'custom-properties',
        'at-rules',
        'declarations',
        {
          type: 'at-rule',
          name: 'supports'
        },
        {
          type: 'at-rule',
          name: 'media'
        },
        'rules'
      ],
      {
        severity: 'warning'
      }
    ],
    // Specify the alphabetical order of the attributes in the declaration block
    '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'
    ]
  },
  ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'],
  overrides: [
    {
      files: ['*.vue', '**/*.vue', '*.html', '**/*.html'],
      extends: ['stylelint-config-recommended', 'stylelint-config-html'],
      rules: {
        'keyframes-name-pattern': null,
        'selector-pseudo-class-no-unknown': [
          true,
          {
            ignorePseudoClasses: ['deep', 'global']
          }
        ],
        'selector-pseudo-element-no-unknown': [
          true,
          {
            ignorePseudoElements: ['v-deep', 'v-global', 'v-slotted']
          }
        ]
      }
    }
  ]
}

rules 字段可以根据团队情况进行配置,如果需要忽略某个文件或者某个目录下的文件,则可以在根目录下创建 .stylelintignore 文件,如

/dist*

接着就可以在 package.json 中配置检测修复命令了

"lint:style": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/"

运行 npm run lint:style 即可查看效果

总结

到这里,我们就实现了统一代码语法和风格的配置了,从而保证代码的可读性。

如果想要查看相应代码,可查看 vue-element-plus-admin