工程基础配置

一些脚手架基础配置

  • vscode settings
  • eslint -v9
  • eslint -nuxt -v9
  • commitlint
  • husky
  • lint-staged
  • 自定义eslint 规则

vscode settings

保存的时候自动格式化(非 prettier 格式化)

settings.json
json
settings.json

{
  // Disable the default formatter, use eslint instead
  "prettier.enable": false,
  "editor.formatOnSave": false,

  // Auto fix
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit",
    "source.organizeImports": "never"
  },

  // Silent the stylistic rules in you IDE, but still auto fix them
  "eslint.rules.customizations": [
    { "rule": "style/*", "severity": "off", "fixable": true },
    { "rule": "format/*", "severity": "off", "fixable": true },
    { "rule": "*-indent", "severity": "off", "fixable": true },
    { "rule": "*-spacing", "severity": "off", "fixable": true },
    { "rule": "*-spaces", "severity": "off", "fixable": true },
    { "rule": "*-order", "severity": "off", "fixable": true },
    { "rule": "*-dangle", "severity": "off", "fixable": true },
    { "rule": "*-newline", "severity": "off", "fixable": true },
    { "rule": "*quotes", "severity": "off", "fixable": true },
    { "rule": "*semi", "severity": "off", "fixable": true }
  ],

  // Enable eslint for all supported languages
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact",
    "vue",
    "html",
    "markdown",
    "json",
    "jsonc",
    "yaml",
    "toml",
    "xml",
    "gql",
    "graphql",
    "astro",
    "svelte",
    "css",
    "less",
    "scss",
    "pcss",
    "postcss"
  ]
}

eslint config

使用 antfu 的 eslint 配置;

使用 stylelistic 作为格式化;

prettier 格式化 stylelistic 不支持的文件格式, e.g. css 文件;

pnpm i @antfu/eslint-config eslint-plugin-format -D

eslint.config.js
js
eslint.config.js

import antfu from '@antfu/eslint-config'
export default antfu(
  {
    vue: true,
    rules: {
      'no-console': 'off',
      'vue/block-order': 'off',
      'antfu/top-level-function': 'off',
      'vue/component-name-in-template-casing': ['error', 'kebab-case', {
        registeredComponentsOnly: false,
      }],
      'vue/no-mutating-props': 'off',
      'ts/no-use-before-define': 'off',
      'ts/no-this-alias': 'off', // components : 组件复用,会有选项式api用法 - 允许this
      'eslint-comments/no-unlimited-disable': 'off',
      'import/no-unused-modules': 'error',
      'unused-imports/no-unused-imports': 'error',
    },
    formatters: {
      css: true,//css使用prettier 格式化
    },
  }
)

eslint config for Nuxt

同样使用 上述 .vsocde/settings.json

pnpm i @nuxt/eslint eslint-plugin-format eslint -D

eslint.config.js
js
eslint.config.js

// @ts-check
import withNuxt from './.nuxt/eslint.config.mjs'

export default withNuxt({
  rules: {
    'no-console': 'off',
    'vue/block-order': 'off',
    'vue/component-name-in-template-casing': [
      'error',
      'kebab-case',
      {
        registeredComponentsOnly: false,
      },
    ],
    'vue/no-mutating-props': 'off',
    '@typescript-eslint/no-use-before-define': 'off',
    '@typescript-eslint/no-this-alias': 'off', // components : 组件复用,会有选项式api用法 - 允许this
    'eslint-comments/no-unlimited-disable': 'off',
  },
})
js
nuxt.conig.ts

export default defineNuxtConfig({
  //...
  modules: ['@nuxt/eslint'],
  eslint: {
    config: {
      stylistic: true,
      formatters: {
        css: 'prettier',
        html: 'prettier',
      },
    },
  },
  //...
})

自定义eslint rules

可以尝试将规范结合到 eslint 规则中去,比如 🈚️ 滥用 localStorage

同时支持 .{js,ts}

pnpm i -D @typescript-eslint/parser

js
import tsEslintParaser from '@typescript-eslint/parser'
// 自定义eslint 规则做规范~ e.g: 禁止localStorage泛用,而应该模块化集中管理
const plugin = {
  meta: {
    name: 'eslint-plugin-booking',
  },
  configs: {},
  rules: {
    'no-localstorage': {
      meta: {
        schema: [
          {
            type: 'object',
            properties: {
              msg: { type: 'string' },
            },
            additionalProperties: false,
          },
        ],
      },

      create(context) {
        const option = context.options[0]

        return {
          MemberExpression(node) {
            let objectName
            if (node.object.type === 'Identifier') {
              objectName = node.object.name
            }
            else if (
              node.object.type === 'TSAsExpression'
              && node.object.expression.type === 'Identifier'
            ) {
              objectName = node.object.expression.name
            }
            else if (
              node.object.type === 'MemberExpression'
              && node.object.object.type === 'Identifier'
              && node.object.object.name === 'window'
              && node.object.property.type === 'Identifier'
              && node.object.property.name === 'localStorage'
            ) {
              objectName = 'window.localStorage'
            }

            if (objectName === 'localStorage' || objectName === 'window.localStorage') {
              context.report({
                node,
                message: `(订舱eslint): ${option.msg}.`,
              })
            }
          },

        }
      },

    },
  },
}

/**
 * @param {object} options - 配置项
 * @param {string} options.msg - report 消息
 * @param {Array<string>} options.ignores - 忽略模块
 * @returns {Array<object>} ESLint Flat Config 配置数组
 */
const createLocalEslintRule = (options) => {
  const { msg = '', ignores = [] } = options
  return [
    {
      files: ['**/*.{js,ts,jsx,tsx}'],
      languageOptions: {
        parser: tsEslintParaser,
      },
      plugins: {
        '@booking-eslint': plugin,
      },
      rules: {
        '@booking-eslint/no-localstorage': ['error', { msg }],
      },
    },
    {
      files: ignores,
      rules: {
        '@booking-eslint/no-localstorage': 'off',
      },
    },
  ]
}
export default {
  createLocalEslintRule,
}

commitlint & cz-git

pnpm i @commitlint/cli @commitlint/config-conventional cz-git -D

提供消息格式校验和命令行交互prompt

使用 cli: > npx cz

js
import { defineConfig } from 'cz-git'

export default defineConfig({
  rules: {
    // @see: https://commitlint.js.org/#/reference-rules
  },
  prompt: {
    alias: { fd: 'docs: fix typos' },
    messages: {
      type: 'Select the type of change that you\'re committing:',
      scope: 'Denote the SCOPE of this change (optional):',
      customScope: 'Denote the SCOPE of this change:',
      subject: 'Write a SHORT, IMPERATIVE tense description of the change:\n',
      body: 'Provide a LONGER description of the change (optional). Use "|" to break new line:\n',
      breaking: 'List any BREAKING CHANGES (optional). Use "|" to break new line:\n',
      footerPrefixSelect: 'Select the ISSUES type of changeList by this change (optional):',
      customFooterPrefix: 'Input ISSUES prefix:',
      footer: 'List any ISSUES by this change. E.g.: #31, #34:\n',
      generatingByAI: 'Generating your AI commit subject...',
      generatedSelectByAI: 'Select suitable subject by AI generated:',
      confirmCommit: 'Are you sure you want to proceed with the commit above?',
    },
    types: [
      { value: 'feat', name: 'feat:     A new feature', emoji: ':sparkles:' },
      { value: 'fix', name: 'fix:      A bug fix', emoji: ':bug:' },
      { value: 'docs', name: 'docs:     Documentation only changes', emoji: ':memo:' },
      { value: 'style', name: 'style:    Changes that do not affect the meaning of the code', emoji: ':lipstick:' },
      { value: 'refactor', name: 'refactor: A code change that neither fixes a bug nor adds a feature', emoji: ':recycle:' },
      { value: 'perf', name: 'perf:     A code change that improves performance', emoji: ':zap:' },
      { value: 'test', name: 'test:     Adding missing tests or correcting existing tests', emoji: ':white_check_mark:' },
      { value: 'build', name: 'build:    Changes that affect the build system or external dependencies', emoji: ':package:' },
      { value: 'ci', name: 'ci:       Changes to our CI configuration files and scripts', emoji: ':ferris_wheel:' },
      { value: 'chore', name: 'chore:    Other changes that don\'t modify src or test files', emoji: ':hammer:' },
      { value: 'revert', name: 'revert:   Reverts a previous commit', emoji: ':rewind:' },
    ],
    useEmoji: true,
    emojiAlign: 'center',
    useAI: false,
    aiNumber: 1,
    themeColorCode: '',
    scopes: [],
    allowCustomScopes: true,
    allowEmptyScopes: true,
    customScopesAlign: 'bottom',
    customScopesAlias: 'custom',
    emptyScopesAlias: 'empty',
    upperCaseSubject: false,
    markBreakingChangeMode: false,
    allowBreakingChanges: ['feat', 'fix'],
    breaklineNumber: 100,
    breaklineChar: '|',
    skipQuestions: [],
    issuePrefixes: [{ value: 'closed', name: 'closed:   ISSUES has been processed' }],
    customIssuePrefixAlign: 'top',
    emptyIssuePrefixAlias: 'skip',
    customIssuePrefixAlias: 'custom',
    allowCustomIssuePrefix: true,
    allowEmptyIssuePrefix: true,
    confirmColorize: true,
    scopeOverrides: undefined,
    defaultBody: '',
    defaultIssues: '',
    defaultScope: '',
    defaultSubject: '',
  },
})

package.json 增加如下

json
{
  "config": {
    "commitizen": {
      "path": "node_modules/cz-git"
      }
  }
}

husky & lint-staged

pnpm i husky lint-staged -D

npx husky init

在 git hooks pre-commit 增加 eslint ts check 等校验

.husky/pre-commit

shell
npx lint-staged

package.json 添加如下

json
 "lint-staged": {
    "*.{js,jsx,ts,tsx,json,css,scss,md,vue}": "eslint --fix"
  }

校验 commit-msg:

.husky/commit-msg

shell
npx --no -- commitlint --edit $1
微前端原理解析