前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版

手把手基于ESLint 9+Husky+Prettier+Commitlint+Lint-staged配置前端规范

首页2025-08-06 14:40:12Front-End
ESLintPrettierHuskyCommitlintLint-staged前端工程化

新人进组第二天提了个 PR,diff 里三百多行,真正改的业务逻辑只有五行,剩下全是缩进、引号、分号的差异。review 的人翻了十分钟没看出他到底做了什么,最后只能让他 revert 重来。这种事在没有统一规范的仓库里几乎每两周就要上演一次。

这篇把我在一个 Next.js 16 + TypeScript 项目里实际用的那套规范链路完整拆开写,ESLint 9 的扁平化配置怎么组、Prettier 和 ESLint 的边界划在哪、Lint-staged 为什么只检查暂存文件、Husky 9 的钩子该怎么写才不会在半年后失效。读完你能直接照着搭一份跑得起来的配置,也知道每个开关是为什么打开的。

在本篇文章中,我们将从浅入深,和大家一起学习以下知识:

  • 这套工具链里每个工具各自负责哪一段,边界划错了会怎样
  • Next.js 16 项目的依赖安装清单与版本约束
  • ESLint 9 扁平化配置(Flat Config)的完整写法与规则取舍
  • Prettier、EditorConfig、VSCode 三者如何配合做到保存即修复
  • Commitlint 约束提交信息、Lint-staged 做增量检查
  • Husky 9 把钩子串起来,以及 husky add 已经不能用了这件事
  • 一次完整提交在这条链路上都发生了什么
  • 我自己踩过的几个坑和排查思路

# 一、先分清楚这几个工具各管什么

搭之前有个问题得想明白,为什么一套规范要装这么多包,一个 ESLint 不够吗。

不够。ESLint 管的是代码质量,比如变量声明了没用、Hook 写在条件分支里、img 标签缺 alt 属性,这些是「写错了」的问题。Prettier 管的是代码风格,单引号还是双引号、一行放多少个字符、对象末尾要不要留逗号,这些是「长得不一样」的问题。两件事混在一个工具里做,结果就是规则互相打架,改一个报另一个。

分工大概是这样:

工具 负责什么 配置文件
ESLint 9 代码质量与潜在 bug eslint.config.mjs
Prettier 代码风格与排版 .prettierrc.js
EditorConfig 编辑器层面的缩进换行 .editorconfig
Commitlint 提交信息格式 commitlint.config.js
Lint-staged 只检查暂存区文件 lint-staged.config.js
Husky 在 Git 生命周期上挂钩子 .husky/

后面三个是「什么时候执行」的问题,前面三个是「执行什么」的问题。理清这条线,配置文件之间的引用关系就顺了。

顺着上面聊,EditorConfig 看起来和 Prettier 重复,其实不是。Prettier 只在你保存或者跑命令时才动手,EditorConfig 是编辑器打开文件那一刻就生效的,管的是你敲 Tab 键出来几个空格。团队里有人用 WebStorm 有人用 VSCode 的时候,这个文件能省掉不少扯皮。

# 二、项目初始化与依赖安装

先起一个 Next.js 16 项目。

npx create-next-app@latest my-app --typescript --tailwind --eslint
cd my-app
@前端进阶之旅: 代码已经复制到剪贴板

--eslint 会带一份最基础的 eslint.config.mjs,我们后面会整个覆盖掉,但留着它可以省去手动装 eslint-config-next 的步骤。

接下来装 ESLint 9 这一组。@eslint/js 提供官方推荐规则集,typescript-eslint 是新的统一入口包,替代了以前要同时装 @typescript-eslint/parser 和 @typescript-eslint/eslint-plugin 的写法。

npm install eslint@9 @eslint/js@9 --save-dev
npm install typescript-eslint@8 --save-dev
npm install eslint-config-next@16 --save-dev
npm install eslint-plugin-simple-import-sort@12 eslint-plugin-unused-imports@4 --save-dev
npm install @eslint/compat@1 --save-dev
@前端进阶之旅: 代码已经复制到剪贴板

@eslint/compat 这个包很关键。ESLint 9 对插件的写法做了破坏性调整,社区里有不少插件当时还没跟上,fixupPluginRules 能把老写法的插件包一层适配到扁平化配置里。关于 ESLint 8 到 9 到底改了哪些东西,我之前写过一篇更细的拆解,可以配合看 ESLint 9 完全指南。

然后是 Prettier 这一组。

npm install prettier@3 prettier-plugin-tailwindcss@0.7 --save-dev
npm install eslint-config-prettier@10 --save-dev
@前端进阶之旅: 代码已经复制到剪贴板

eslint-config-prettier 不加任何规则,它只做一件事,把 ESLint 里所有和排版相关、会跟 Prettier 打架的规则全部关掉。装了它,上面说的「规则互相打架」就基本消失了。

最后是 Git 钩子这一组。

npm install husky@9 lint-staged@16 --save-dev
npm install @commitlint/cli@17 @commitlint/config-conventional@17 --save-dev
@前端进阶之旅: 代码已经复制到剪贴板

装完对一遍完整的清单,版本号锁在这个区间是我实际验证过能跑通的组合。

{
  "devDependencies": {
    "@commitlint/cli": "^17.7.2",
    "@commitlint/config-conventional": "^17.7.0",
    "@eslint/compat": "^1.4.1",
    "@eslint/eslintrc": "^3",
    "eslint": "^9.39.0",
    "eslint-config-next": "^16.0.1",
    "eslint-config-prettier": "^10.1.8",
    "eslint-plugin-prettier": "^5.5.4",
    "eslint-plugin-react-hooks": "^7.0.1",
    "eslint-plugin-simple-import-sort": "^12.1.1",
    "eslint-plugin-unused-imports": "^4.3.0",
    "husky": "^9.1.7",
    "lint-staged": "^16.2.6",
    "prettier": "^3.6.2",
    "prettier-plugin-tailwindcss": "^0.7.1",
    "typescript": "^5.9.3"
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

这里有个细节容易被忽略,@eslint/eslintrc 和 @eslint/compat 是两个不同的东西。前者用来把老的 .eslintrc 配置塞进扁平化配置(FlatCompat),后者用来适配老写法的插件。如果你是全新项目,@eslint/eslintrc 其实可以不装。

# 三、ESLint 9 扁平化配置怎么写

ESLint 9 把配置格式换成了 Flat Config,文件名从 .eslintrc.json 变成 eslint.config.mjs,结构从一个大对象变成了一个数组。数组里每一项是一段独立配置,按顺序合并,后面的覆盖前面的。

先看骨架部分。

// eslint.config.mjs
import { defineConfig, globalIgnores } from 'eslint/config'
import nextVitals from 'eslint-config-next/core-web-vitals'
import nextTs from 'eslint-config-next/typescript'
import tseslint from 'typescript-eslint'
import simpleImportSort from 'eslint-plugin-simple-import-sort'
import unusedImports from 'eslint-plugin-unused-imports'
import { fixupPluginRules } from '@eslint/compat'

const eslintConfig = defineConfig([
  ...nextVitals,
  ...nextTs,
  tseslint.configs.recommendedTypeChecked,
  // ...下面是我们自己的那一段
])

export default eslintConfig
@前端进阶之旅: 代码已经复制到剪贴板

eslint-config-next 在 16 版本里拆成了 core-web-vitals 和 typescript 两个子路径导出,直接展开进数组就行,不再需要以前 extends: 'next/core-web-vitals' 那种字符串写法。这也是 Flat Config 最舒服的一点,配置就是普通的 JS 模块,能 import 能展开能条件判断。

tseslint.configs.recommendedTypeChecked 是带类型信息的规则集,它比 recommended 强得多,能查出 await 一个非 Promise 这类问题,代价是要求 ESLint 能读到 TS 的类型信息,也就是下面 projectService: true 那行。

{
  files: ['src/**/*.{js,jsx,ts,tsx}'],
  plugins: {
    'simple-import-sort': fixupPluginRules(simpleImportSort),
    'unused-imports': fixupPluginRules(unusedImports)
  },
  extends: [],
  languageOptions: {
    parserOptions: {
      projectService: true
    },
    globals: {
      JSX: true
    }
  },
  rules: {
    // 见下一段
  }
}
@前端进阶之旅: 代码已经复制到剪贴板

projectService: true 是 typescript-eslint 8 推荐的新写法,替代了以前手写 project: './tsconfig.json' 的方式,它会自动找到每个文件对应的 tsconfig,monorepo 里省事很多。这里有个坑要注意,一旦开了类型检查规则,ESLint 的执行时间会明显变长,因为它要跑一遍 TS 的类型推导。项目大了之后 npx eslint src 跑个几十秒是正常的,所以后面才需要 Lint-staged 只检查改动文件。

files 限定成 src/** 也是有意的。构建产物、配置文件、脚本目录不该被带类型的规则扫,不然会因为它们不在 tsconfig 的 include 范围里直接报错。

规则那一块比较长,我按用途拆成三组说。第一组是把默认开着但和项目习惯冲突的规则关掉。

fe
  • 一、先分清楚这几个工具各管什么
  • 二、项目初始化与依赖安装
  • 三、ESLint 9 扁平化配置怎么写
  • 四、Prettier 负责风格这一半
  • 五、EditorConfig 与 VSCode 保存即修复
  • 六、Commitlint 约束提交信息
  • 七、Lint-staged 只检查改动的文件
  • 八、Husky 把钩子串起来
  • 九、一次提交在这条链路上发生了什么
  • 十、我踩过的几个坑
  • 总结
  • 参考

← React 状态管理库选型指南 四个主流方案横向对比Next.js App Router 避坑指南 10个高频错误与正确写法 →