Eslint + Prettier + Husky + Jest 规范前端工程

168 阅读6分钟

总结一份自己的前端工作流搭建流程,适用vue项目,主要包括以下四个步骤:

  1. Eslint
  2. Prettier
  3. husky
  4. jest

1. 代码检查工具eslint

  • 项目集成 eslint
pnpm i eslint -D 
npx eslint --init
pnpm i eslint-plugin-vue vite-plugin-eslint -D
  • 配置 .eslintrc.js
module.exports = {
  root: true,
  env: {
    browser: true,
    node: true,
    es2021: true
  },
  extends: [
    'eslint:recommended',
    'plugin:vue/vue3-essential'
  ],
  parser: 'vue-eslint-parser',
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module'
  },
  plugins: ['vue'],
  rules: {
    // 自定义规则
  }
};
  • 更新 package.json
{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "lint": "eslint src --ext .vue,.js,.ts,.jsx,.tsx --ignore-path .gitignore --fix",
  }
}
  • 修改 vite.config.js
import { defineConfig } from "vite";
import { resolve } from 'path';
import vue from "@vitejs/plugin-vue";
import eslintPlugin from "vite-plugin-eslint";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue(), eslintPlugin()],
  resolve: {
    alias: {
      '@': resolve(__dirname, './src')
    }
  }
});

2. 代码风格工具prettier

  • 项目集成 prettier
pnpm i prettier eslint-config-prettier eslint-plugin-prettier -D
  • 创建并配置 .prettierrc.cjs
module.exports = {
  // 一行最多多少个字符
  printWidth: 150,
  // 指定每个缩进级别的空格数
  tabWidth: 2,
  // 使用制表符而不是空格缩进行
  useTabs: false,
  // 在语句末尾是否需要分号
  semi: true,
  // 是否使用单引号
  singleQuote: true,
  // 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
  quoteProps: "as-needed",
  // 在JSX中使用单引号而不是双引号
  jsxSingleQuote: false,
  // jsx 标签的反尖括号需要换行
  jsxBracketSameLine: false,
  // 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
  trailingComma: "es5",
  // 在对象文字中的括号之间打印空格
  bracketSpacing: true,
  // 在单独的箭头函数参数周围包括括号 always:(x) => x
  arrowParens: "always",
  // 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
  rangeStart: 0,
  rangeEnd: Infinity,
  // 指定要使用的解析器,不需要写文件开头的 @prettier
  requirePragma: false,
  // 不需要自动在文件开头插入 @prettier
  insertPragma: false,
  // 使用默认的折行标准 always\never\preserve
  proseWrap: "preserve",
  // 指定HTML文件的全局空格敏感度 css\strict\ignore
  htmlWhitespaceSensitivity: "css",
  // 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
  endOfLine: "auto",
  // Vue文件脚本和样式标签缩进
  vueIndentScriptAndStyle: false,
}
  • 修改 .eslintrc.js
module.exports = {
  root: true,
  env: {
    browser: true,
    node: true,
    es2021: true
  },
  extends: [
    'eslint:recommended',
    'plugin:vue/vue3-essential',
    'plugin:prettier/recommended'
  ],
  // 添加 vue文件解析器  解析template文件
  parser: 'vue-eslint-parser',
  parserOptions: {
    ecmaVersion: 'latest',
    sourceType: 'module'
  },
  plugins: ['vue', 'prettier'],
  rules: {
    'vue/multi-word-component-names': 0, // 关闭多个单词组成的组件名
    'vue/no-reserved-component-names': 0, // 关闭组件名不要和html标签重名
    'vue/no-useless-template-attributes': 0, // 关闭无用的template属性检测
    'vue/no-v-html': 0, // 关闭v-html校验
    'vue/no-mutating-props': 0, // 关闭禁止修改props
    'no-undef': 0, // 关闭未定义变量检测
    'no-unused-vars': 0, // 关闭未使用变量检测
    'no-console': 0, // 关闭console检测
    'no-debugger': 0, // 关闭debugger检测
    'no-array-constructor': 2, // 禁止使用数组构造器
    'no-class-assign': 2, // 禁止给类赋值
    'no-cond-assign': 2, // 禁止在条件表达式中使用赋值语句
    'no-const-assign': 2, // 禁止修改const声明的变量
    'no-delete-var': 2, // 不能对var声明的变量使用delete操作符
    'no-dupe-args': 2, // 函数参数不能重复
    'no-dupe-class-members': 2, // 不允许类中出现重复的声明
    'no-dupe-keys': 2, // 在创建对象字面量时不允许键重复
    'no-duplicate-case': 2, // switch中的case标签不能重复
    'no-empty-pattern': 2, // 禁止使用空解构模式
    'no-eval': 2, // 禁止使用eval
    'no-implied-eval': 2, // 禁止使用隐式eval
    'no-ex-assign': 2, // 禁止给catch语句中的异常参数赋值
    'no-extra-bind': 2, // 禁止不必要的函数绑定
    'no-extra-boolean-cast': 2, // 禁止不必要的bool转换
    'no-extra-parens': [2, 'functions'], // 禁止非必要的括号
    'no-func-assign': 2, // 禁止重复的函数声明
    'no-inner-declarations': [2, 'functions'], // 禁止在块语句中使用声明(变量或函数)
    'no-irregular-whitespace': 2, // 不能有不规则的空格
    'no-obj-calls': 2, // 不能调用内置的全局对象,比如Math() JSON()
    'no-redeclare': 2, // 禁止重复声明变量
    'no-return-assign': [2, 'except-parens'], // return 语句中不能有赋值表达式
    'no-self-assign': 2, // 不能自声明自己
    'no-self-compare': 2, // 不能自比较
    'no-sequences': 2, // 禁止使用逗号运算符
    'no-shadow-restricted-names': 2, // 不能使用保留字作为变量名
    'no-spaced-func': 2, // 函数调用时,函数名与()之间不能有空格
    'no-sparse-arrays': 2, // 禁止稀疏数组
    'no-this-before-super': 2, // 在调用super()之前不能使用this或super
    'no-throw-literal': 2, // 禁止抛出字面量错误 throw "error";
    'no-trailing-spaces': 2, // 一行结束后面不要有空格
    'no-undef-init': 2, // 变量初始化时不能直接给它赋值为undefined
    'no-unexpected-multiline': 2, // 避免多行表达式
    'no-unmodified-loop-condition': 2, // 禁止出现未使用过的变量
    'no-useless-call': 2, // 禁止不必要的call和apply
    'no-useless-computed-key': 2, // 禁止在对象中使用不必要的计算属性
    'no-unreachable': 0, // 不能有无法执行的代码
    'no-whitespace-before-property': 2, // 禁止属性前有空白
    'no-with': 2, // 禁用with
    'use-isnan': 0, // 禁止比较时使用NaN,只能用isNaN()
    'comma-style': [2, 'last'], // 逗号风格,换行时在行首还是行尾
    'comma-spacing': [2, { before: false, after: true }], // 控制逗号前后的空格
    'arrow-spacing': [2, { before: true, after: true }], // 箭头函数前后要有空格
    'block-spacing': [2, 'always'], // 单行代码块两边加空格
    'brace-style': [2, '1tbs', { allowSingleLine: true }], // if、function后面的{必须与if在同一行
    'prefer-template': 2, // 使用模板字符串
    'prefer-const': 2, // 使用const
    'prettier/prettier': 1, // prettier 格式化
    quotes: [2, 'single', { avoidEscape: true, allowTemplateLiterals: true }], // 字符串使用单引号
    semi: [2, 'always'], // 强制使用分号
    eqeqeq: [2, 'always', { null: 'ignore' }], // 要求使用 === 和 !==
  }
};
  • 更新 package.json
{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx --fix",
    "prettier": "prettier --write \"./**/*.{html,vue,ts,js,json,md}\""
  }
}

3. git提交规范Husky

  • 项目集成 Husky
pnpm i lint-staged husky -D
  • 更新 package.json
{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "prepare": "husky install",
    "lint": "eslint src --ext .vue,.js,.ts,.jsx,.tsx --ignore-path .gitignore --fix",
  }
}
pnpm run prepare # 初始化husky,将 git hooks 钩子交由husky执行
npx husky add .husky/pre-commit "npx lint-staged"
pnpm i commitlint @commitlint/config-conventional -D 
npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'
  • 创建并配置 .lintstagedrc.json
{
    "*.{js,jsx,ts,tsx}": ["prettier --write .", "eslint  --fix"],
    "*.md": ["prettier --write"]
}
  • 配置 .husky/commit-msg
#!/bin/sh

# 用 `` 可以将命令的输出结果赋值给变量
# 获取当前提交的 commit msg
commit_msg=$(cat "$1")

# 获取用户 email
email=$(git config user.email)

# feat: feature 新功能提交
# fix: 修复 bug 的提交
# perf: 性能优化的提交
# refactor: 代码重构的提交
# merge: 合并分支的提交
# docs: 文档修改的提交
# style: 代码格式修改,注意不是 css 修改的提交
# test: 测试用例的修改
# build: 构建系统或者包依赖更新
# revert: 恢复上一次提交
# ci: 持续集成相关文件修改
# chore: 更新构建脚本,但是不会更新产品代码
# release: 发布新版本
# workflow: 工作流相关文件修改
msg_re="^(feat|fix|perf|refactor|merge|docs|style|test|build|revert|ci|chore|release|workflow)(\(.+\))?: .{1,100}"

if [[ ! $commit_msg =~ $msg_re ]]
then
	echo "不合法的 commit 消息提交格式,请使用正确的格式,请使用 feat|fix|perf|refactor|merge|docs|style 加冒号等开头格式"

	# 异常退出
	exit 1
fi

4. 代码测试工具Jest

  • 项目集成
pnpm i --save-dev jest jest-environment-jsdom babel-jest @babel/preset-env @vue/vue3-jest @vue/test-utils
  • 创建并配置 jest.config.js
module.exports = {
    collectCoverage: true,
    testEnvironment: "jsdom",
    transform: {
        "^.+\.vue$": "@vue/vue3-jest",
        "^.+\js$": "babel-jest",
    },
    testRegex: "(/__tests__/.*|(\.|/)(test|spec))\.(js|ts)$",
    moduleFileExtensions: ["vue", "js"],
    moduleNameMapper: {
        "^@/(.*)$": "<rootDir>/src/$1",
    },
    coveragePathIgnorePatterns: ["/node_modules/", "/tests/"],
    coverageReporters: ["text", "json-summary"],
    // https://test-utils.vuejs.org/migration/#test-runners-upgrade-notes
    testEnvironmentOptions: {
        customExportConditions: ["node", "node-addons"],
    },
}
  • 创建并配置 babel.config.js
module.exports = {
    env: {
        test: {
            presets: [
                [
                    "@babel/preset-env",
                    {
                        targets: {
                            node: "current",
                        },
                    },
                ],
            ],
        },
    },
}
  • 更新 package.json
{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx --fix",
    "prettier": "prettier --write \"./**/*.{html,vue,ts,js,json,md}\"",
    "test": "jest"
  }
}

注意:如果报错 exports is not defined in ES module scope ,需要将 babel.config.jsjest.config.js 后缀改为 .cjs 即可