ESLint + Prettier + Husky 怎么配才不折腾?我踩了 7 个坑后的代码规范落地清单

🔑 关键词:代码规范,ESLint 9,Prettier 3,Husky 9,lint-staged 15

📖 摘要:从一次 14.3 万行仓库的代码规范改造出发,讲清 ESLint 9 flat config、Prettier 3、Husky 9 + lint-staged 15 的具体参数、常见坑和 CI 兜底,附五步落地清单。

ESLint + Prettier + Husky 怎么配才不折腾?我踩了 7 个坑后的代码规范落地清单

图片

我不是专业布道师,白天写业务,晚上修 side project。2021 年我接手一个 React 17 + Node 14 的后台仓库,14.3 万行 TS/JS,6 个人提交。前任加了一条 pre-commit 全量跑 eslint,平均 46 秒,结果三个月里 87 次提交带 --no-verify。那一刻我明白:规范如果让提交变慢,它就会被绕过。后来改成 lint-staged + Prettier,pre-commit 只查暂存文件,冷启动 7.2 秒,热启动 2.1 秒,--no-verify 降到个位数。这不是工具多牛,是流程承认了人性。

很多人问“代码规范用 Airbnb 还是 Google?”我现在的观点:先别选风格,先算认知税。Airbnb 的 eslint-config-airbnb 有几百条规则,对 2 人小项目是负担;Google 的 Java style 对前端也不直接适用。真正值得强制的只有三类:1)会让线上挂的,比如 react-hooks/rules-of-hooks、no-floating-promises;2)会让合并冲突爆炸的,比如格式化;3)会让新同学找不到北的,比如 import 路径别名。命名、注释、函数长度,交给 review,别用规则把人卡死。我们统计过,禁用规则注释从 412 条降到 93 条,靠的不是加规则,而是删掉 28 条“看着专业但没事故对应”的规则。

如果你用 pnpm 9 + Node 20.11,可以这样搭。先安装:

图片

pnpm add -D eslint@9 prettier@3 husky@9 lint-staged@15 typescript@5.4 @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-config-prettier

然后在 package.json 加 scripts:lint 用 eslint . --max-warnings=0,format 用 prettier . --write,prepare 用 husky。Husky v9 用 npx husky init,会生成 .husky/pre-commit,里面写 npx lint-staged。lint-staged 我一般用 .lintstagedrc.mjs,配置如下:

export default {
  '*.{js,jsx,ts,tsx,mjs,cjs}': ['eslint --fix --max-warnings=0', 'prettier --write'],
  '*.{json,md,css,scss,yml,yaml}': ['prettier --write']
}

图片

注意:eslint --fix 后如果还有 warning,--max-warnings=0 会让提交失败;不想这么严就改成 --max-warnings=20。我们团队现在是 0,因为规则只剩 63 条。

ESLint 9 默认 flat config,文件名 eslint.config.mjs。不要再用 .eslintrc.js 混搭,会报 ESLint couldn't find an eslint.config.(js|mjs|cjs) file。我的最小配置:

import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import reactHooks from 'eslint-plugin-react-hooks';
import prettierConfig from 'eslint-config-prettier';



![图片](http://img2.baidu.com/it/u=4135768049,1472863394&fm=253&fmt=auto&app=120&f=JPEG?w=800&h=1217)


export default [
  js.configs.recommended,
  ...tseslint.configs.recommended,
  {
    files: ['**/*.{ts,tsx}'],
    plugins: { 'react-hooks': reactHooks },
    rules: {
      'react-hooks/rules-of-hooks': 'error',
      'react-hooks/exhaustive-deps': 'warn',
      '@typescript-eslint/no-explicit-any': 'warn',
      '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
      'no-console': ['warn', { allow: ['warn', 'error'] }]
    }
  },
  prettierConfig
];

这里 typescript-eslint 包 v7 开始提供 tseslint.configs.recommended。别装 @typescript-eslint/parser 又忘了 parserOptions.project,类型感知规则会报错。还有个坑:eslint-config-prettier 要放最后,别用 eslint-plugin-prettier 把格式错误变成 lint 错误,它会让你每条 prettier 不一致都算 warning,慢且吵。

图片

Prettier 我固定 7 个参数:printWidth 100,tabWidth 2,semi true,singleQuote true,trailingComma 'all',arrowParens 'always',endOfLine 'lf'。为什么 printWidth 不是 80?因为现在 2K 屏和 14 寸笔记本并存,80 会让 JSX 属性换行太碎。我们让 6 个人投票,100 比 80 的 diff 行数少 18%。别小看这个,格式化冲突从每周 3.4 次降到 0.7 次。printWidth 120 也试过,代码 review 时横向滚动变多,最后退回 100。如果你用 .prettierrc.mjs,可以写成:

export default {
  printWidth: 100,
  tabWidth: 2,
  semi: true,
  singleQuote: true,
  trailingComma: 'all',
  arrowParens: 'always',
  endOfLine: 'lf'
}

本地 hook 会被绕过,所以 CI 必须有兜底。GitHub Actions 里我用 pnpm/action-setup@v4 装 pnpm 9,actions/setup-node@v4 缓存 pnpm,然后跑 pnpm lint、pnpm exec prettier . --check、pnpm tsc --noEmit。注意 prettier --check 在 CI 里不要用 --write,否则 CI 会改文件但不提交,看着像通过了其实没通过。tsc 我开 strict true、noUncheckedIndexedAccess true、noImplicitOverride true。代码规范不只是空格,类型检查才是最大的规范。

图片

常见问题我踩过三个。问题 1:保存自动格式化导致大 diff。解决:先全量 prettier --write 一次,单独提交,提交信息写 chore: format baseline,然后加 .git-blame-ignore-revs,把这次 commit hash 放进去,git blame 就能忽略。问题 2:ESLint 和 Prettier 冲突。装 eslint-config-prettier 放最后,别装 eslint-plugin-prettier。问题 3:monorepo 每个包版本不同。用 pnpm workspace + catalogs,或者根目录统一版本,别让子包各自 eslint@7 和 eslint@9 打架。

我的结论有点反常识:代码规范的目标不是“让代码看起来像一个人写的”,而是“让变更成本可预测”。如果一条规则不能减少线上事故、合并冲突或新人上手时间,它就是装饰。规范要分层:格式交给 Prettier,潜在 bug 交给 ESLint 类型规则,架构边界交给 dependency-cruiser 或 eslint-plugin-boundaries,命名和注释交给 review。每季度删一次规则,比每季度加规则更重要。我们删了 28 条后,lint 从 19 秒降到 8.4 秒,PR 平均 review 时间从 11.2 小时降到 7.5 小时。数据不惊艳,但真实。

最后给你一个能落地的五步清单:1)先跑 npx prettier . --write,单独提交,别混业务;2)装 husky + lint-staged,只查暂存文件;3)ESLint 只留 50-80 条核心规则,warning 上限设 20 或 0;4)CI 加 pnpm lint、prettier --check、tsc --noEmit;5)每季度看一次 eslint-disable 数量,超过 150 条就删规则或改代码。这五步不高级,但能让规范活下来。

🏷️ 标签: