前端代码规范与工程化实战:ESLint + Prettier + Husky 全流程搭建

前端工程

为什么你的团队需要代码规范工程化

说两个真实场景。

场景一:同事 A 用 2 空格缩进,同事 B 用 4 空格。每次 Code Review,80% 的 diff 是缩进和换行。评审人精力消耗在格式上,逻辑问题反而被忽略。

场景二:一个实习生提交了一段代码,npm run build 通过了,合并到主分支。第二天生产环境报错:「typo in variable name」。如果有自动化检查,这个提交根本过不了 pre-commit hook。

代码规范不是「条条框框约束人」,而是「用工具自动保证底线,让人把精力放在真正有价值的决策上」。这篇文章基于我在三个团队的实践——从最开始的 .eslintrc 一团糟,到现在 ESLint Flat Config + Prettier + Husky 全自动化的完整流程。


快速预览:搭建完后的效果

git add .
git commit -m "feat: add user login"

# Husky 自动触发 pre-commit hook
# → lint-staged 对暂存文件运行 ESLint --fix + Prettier --write
# → commitlint 校验 commit message 格式
# → 全部通过后才真正提交

如果 ESLint 报错或 commit message 不符合 Conventional Commits,提交会被拒绝。这意味着不会再有格式不统一或类型不安全的代码进入仓库。


ESLint Flat Config:告别 .eslintrc

2024 年底 ESLint 9.x 将 Flat Config 设为默认,旧的 .eslintrc.* 进入弃用周期。如果你还在用旧配置,现在就该迁移。

Flat Config 基础结构

// eslint.config.mjs(注意是 .mjs,Flat Config 默认使用 ESM)
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import globals from 'globals';

export default tseslint.config(
  // 1. 基础推荐规则
  js.configs.recommended,

  // 2. TypeScript 规则
  ...tseslint.configs.recommended,

  // 3. 全局配置
  {
    languageOptions: {
      globals: {
        ...globals.browser,
        ...globals.node,
      },
      parserOptions: {
        project: './tsconfig.json',
      },
    },
  },

  // 4. 自定义规则
  {
    rules: {
      // TypeScript 严格模式
      '@typescript-eslint/no-unused-vars': ['error', {
        argsIgnorePattern: '^_',
        varsIgnorePattern: '^_',
      }],
      '@typescript-eslint/explicit-function-return-type': 'off',
      '@typescript-eslint/no-explicit-any': 'warn',

      // 通用规则
      'no-console': ['warn', { allow: ['warn', 'error'] }],
      'prefer-const': 'error',
      'no-var': 'error',
      'eqeqeq': ['error', 'always'],
    },
  },

  // 5. 忽略文件
  {
    ignores: [
      'dist/**',
      'node_modules/**',
      '.next/**',
      'coverage/**',
      '*.config.{js,mjs,ts}',
    ],
  }
);

为什么迁移到 Flat Config

旧格式 (eslintrc) 新格式 (Flat Config) 说明
.eslintrc.js / .eslintrc.json eslint.config.mjs 统一为单个文件
extends 链式继承 tseslint.config() 组合 函数组合替代继承
overrides + 文件匹配 配置对象中的 files 更直观的覆盖规则
parserOptions 分散 统一在 languageOptions 结构更清晰
插件用字符串名称 import pluginX from '...' ESM 原生 import

从旧配置迁移

# 如果你的项目还在用 eslintrc:
# 1. 升级 ESLint 到 9.x
npm i -D eslint@^9.0.0

# 2. 其他插件也要升级到支持 Flat Config 的版本
npm i -D @eslint/js typescript-eslint@^8.0.0 globals

# 3. 运行迁移向导(会自动读取旧配置生成新配置)
npx @eslint/migrate-config .eslintrc.js

Prettier:格式化交给专业工具

ESLint 管代码质量,Prettier 管代码格式。不要让 ESLint 去操心缩进和换行——这不是它的本职工作,反而会导致规则冲突。

配置

// .prettierrc
{
  "semi": true,
  "singleQuote": true,
  "trailingComma": "all",
  "printWidth": 100,
  "tabWidth": 2,
  "arrowParens": "always",
  "endOfLine": "lf",
  "bracketSpacing": true,
  "jsxSingleQuote": false
}
// .prettierignore
dist
.next
node_modules
coverage
pnpm-lock.yaml
package-lock.json

让 ESLint 和 Prettier 和平共处

使用 eslint-config-prettier 关闭 ESLint 中与 Prettier 冲突的规则:

// eslint.config.mjs
import prettier from 'eslint-config-prettier';

export default tseslint.config(
  // ...前面的配置
  prettier,  // 放最后,确保覆盖所有冲突规则
);

Husky + lint-staged:在提交前拦住问题

只配置 ESLint 和 Prettier 是不够的——团队成员可能忘记运行。自动化 Git Hook 才是最后的防线。

安装

npm i -D husky lint-staged
npx husky init

lint-staged 配置

// package.json
{
  "lint-staged": {
    "*.{ts,tsx,js,jsx}": [
      "eslint --fix --max-warnings=0",
      "prettier --write"
    ],
    "*.{json,css,scss,md}": [
      "prettier --write"
    ],
    "*.{ts,tsx}": [
      "bash -c 'npx tsc --noEmit'"
    ]
  }
}

Husky pre-commit hook

# .husky/pre-commit
npx lint-staged

注意:--max-warnings=0 意味着即使只有一个 warning 级别的问题也会阻止提交。这在团队初期会很痛苦,但长期来看能保证代码库质量不上涨。


commitlint:统一提交信息格式

「fix bug」「update」「修改了一下」——这种提交信息会让半年后的你恨死自己。Conventional Commits 规范让 changelog 可以自动生成,让版本号可以自动推断。

npm i -D @commitlint/cli @commitlint/config-conventional
// commitlint.config.mjs
export default {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [
      2,
      'always',
      [
        'feat',     // 新功能
        'fix',      // 修 bug
        'docs',     // 文档
        'style',    // 格式(不影响代码逻辑)
        'refactor', // 重构
        'perf',     // 性能优化
        'test',     // 测试
        'chore',    // 构建/工具
        'ci',       // CI 配置
        'revert',   // 回滚
      ],
    ],
    'subject-case': [0],  // subject 不限制大小写
  },
};
# .husky/commit-msg
npx --no -- commitlint --edit $1

正确的提交格式:

git commit -m "feat: add user authentication module"
git commit -m "fix: resolve token refresh race condition"
git commit -m "perf: reduce bundle size by tree-shaking unused utils"
git commit -m "docs: update API documentation for v2.0"

VS Code 团队配置同步

.vscode/settings.json 不加入 .gitignore——它应该进仓库,保证团队统一:

// .vscode/settings.json
{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  },
  "eslint.useFlatConfig": true,
  "typescript.tsdk": "node_modules/typescript/lib",
  "typescript.enablePromptUseWorkspaceTsdk": true,
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[typescriptreact]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "files.eol": "\n",
  "files.trimTrailingWhitespace": true,
  "files.insertFinalNewline": true
}
// .vscode/extensions.json
{
  "recommendations": [
    "dbaeumer.vscode-eslint",
    "esbenp.prettier-vscode",
    "bradlc.vscode-tailwindcss"
  ],
  "unwantedRecommendations": []
}

新成员 clone 仓库后打开 VS Code,右下角会自动提示安装推荐插件。配置也同步到位——零沟通成本。


CI 流水线集成

本地 Hook 可能被 --no-verify 跳过,CI 里再校验一层:

# .github/workflows/lint.yml
name: Lint & Format Check

on:
  pull_request:
    branches: [main, develop]

jobs:
  lint:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install dependencies
        run: npm ci

      - name: Run ESLint
        run: npx eslint . --max-warnings=0

      - name: Check formatting
        run: npx prettier --check "src/**/*.{ts,tsx,js,jsx,json,css}"

      - name: Type check
        run: npx tsc --noEmit

迁移真实项目的经验

第一步:先格式,后规则

不要一上来就开启所有 ESLint 规则——项目会爆几百上千个错误。

# 1. 先用 Prettier 格式化整个项目
npx prettier --write "src/**/*.{ts,tsx}"

# 2. 提交格式化变更(一次巨大的 diff,但内容是安全的)
git add .
git commit -m "style: format all files with Prettier"

# 3. 逐步开启 ESLint 规则
# 先只开 error,warning 暂时关闭

第二步:渐进式收紧

eslint.config.mjs 中:

// 第一期:只开 error 级别
{
  rules: {
    '@typescript-eslint/no-explicit-any': 'warn',    // 先 warn
    'no-console': 'off',                              // 先不开
    '@typescript-eslint/no-unused-vars': 'error',     // 直接 error
  },
}

// 第二期:提升 warn 为 error
{
  rules: {
    '@typescript-eslint/no-explicit-any': 'error',
    'no-console': ['warn', { allow: ['warn', 'error'] }],
  },
}

第三步:处理历史遗留

对于实在不想改的历史代码:

// 方案 A:文件级忽略
/* eslint-disable */
// 整个文件跳过(不推荐)

// 方案 B:行级忽略 + 说明
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const legacyData: any = window.__LEGACY_DATA__;

// 方案 C:目录级忽略(eslint.config.mjs)
{
  ignores: ['src/legacy/**'],
}

常见坑与解决

坑 1:ESLint Flat Config 和 VSCode 插件不兼容

现象:VS Code 编辑器里不显示 lint 错误,但命令行 eslint 正常。

解决:确认 VSCode 的 ESLint 插件版本 ≥ 3.0,并在 .vscode/settings.json 中设置:

{
  "eslint.useFlatConfig": true,
  "eslint.options": {
    "overrideConfigFile": "eslint.config.mjs"
  }
}

坑 2:lint-staged 对 ts(x) 文件做类型检查太慢

方案:类型检查单独在 CI 里跑,pre-commit 不做:

{
  "lint-staged": {
    "*.{ts,tsx,js,jsx}": [
      "eslint --fix --max-warnings=0"
    ]
  }
}

坑 3:Windows 和 macOS 换行符打架

.prettierrc 中设 "endOfLine": "lf",再加 .gitattributes

* text=auto eol=lf

相关工具

本站提供浏览器本地工具,免注册即可试用 →

#ESLint#Prettier#代码规范#工程化#前端#教程