代码规范:ESLint + Prettier 配置实战
本文面向正在搭建代码规范工具链、想在 TypeScript React monorepo 中落地 Lint 的开发者。预计阅读时间11 分钟最终效果掌握 ESLint Flat Config、Biome 与 ESLint 的分工、Prettier 冲突处理以及 pre-commit 与 CI 双层门禁的搭建方法。为什么需要 Lint你一定遇到过这些情况提交代码后 CI 报了一堆红排查半天发现是一个未使用的变量或者团队成员的代码风格各异有人用单引号有人用双引号review 的时候光在格式上浪费时间。Lint 工具解决的就是这类问题自动发现 bug未使用的变量、错误的依赖数组、缺失的 key 属性统一代码风格缩进、引号、分号——不需要在 code review 里争论提升可维护性强制最佳实践减少技术债积累CI 门禁在合并前拦截问题而不是部署后才发现ChatCrystal 是一个包含三个 workspaceshared、server、client的 monorepo代码检查需要覆盖所有子项目。项目的npm run lint命令会同时运行 Biome全仓库通用检查和 ESLint客户端 React/TypeScript 专项检查两者互补。ESLint 配置Flat Config 时代从 ESLint v9 开始官方推荐使用Flat Config扁平配置也就是eslint.config.js文件取代了传统的.eslintrc系列格式。ChatCrystal 的客户端 ESLint 配置client/eslint.config.jsimportjsfromeslint/jsimportglobalsfromglobalsimportreactHooksfromeslint-plugin-react-hooksimportreactRefreshfromeslint-plugin-react-refreshimporttseslintfromtypescript-eslintimport{defineConfig,globalIgnores}fromeslint/configexportdefaultdefineConfig([globalIgnores([dist]),{files:[**/*.{ts,tsx}],extends:[js.configs.recommended,tseslint.configs.recommended,reactHooks.configs.flat.recommended,reactRefresh.configs.vite,],languageOptions:{ecmaVersion:2020,globals:globals.browser,},rules:{react-hooks/set-state-in-effect:off,},},])逐行拆解globalIgnores([dist])— 全局忽略构建产物目录。Flat Config 不再使用.eslintignore文件忽略规则直接写在配置里。files: [**/*.{ts,tsx}]— 只对 TypeScript 文件生效。纯 JS 文件不参与检查避免对第三方代码或配置文件误报。extends配置组js.configs.recommended— ESLint 自身的推荐规则涵盖常见的 JS 错误模式tseslint.configs.recommended— TypeScript 专属规则包括类型相关的检查reactHooks.configs.flat.recommended— React Hooks 的规则防止useEffect依赖遗漏等问题reactRefresh.configs.vite— Vite HMR 环境下的 React 组件导出规范languageOptions.globals— 声明window、document等浏览器全局变量避免 ESLint 报 “xxx is not defined”。rules覆盖—react-hooks/set-state-in-effect设为off这是项目中特定场景下的权衡不是每个项目都需要。TypeScript ESLint 集成TypeScript 是 ChatCrystal 的核心语言typescript-eslint包提供了 TypeScript 语法感知的 lint 能力。安装依赖npminstall-Deslint typescript-eslint eslint/js globalstypescript-eslint的作用不仅仅是让 ESLint 能解析.ts文件的语法比如类型注解、接口声明更重要的是提供了一系列 TypeScript 特有的规则禁止any类型的滥用检查未使用的导入和变量强制枚举成员使用 PascalCase检测ts-ignore的使用这些规则在纯 JavaScript 的 ESLint 中是无法实现的因为它们需要理解 TypeScript 的类型系统。Monorepo 中的 Lint 策略ChatCrystal 采用双层 lint架构{scripts:{lint:biome check . --diagnostic-levelerror npm run lint -w client,lint:fix:biome check --fix . npm run lint -w client -- --fix}}第一层Biome负责全仓库检查server、shared、client、electron、scripts第二层ESLint专门负责客户端的 React TypeScript 规则。这种设计的原因是Biome 快用 Rust 编写对整个 monorepo 的 lint 耗时极短适合做通用检查ESLint 生态丰富React Hooks、React Refresh 等插件是 Biome 目前不完全覆盖的职责分离通用规则和框架专属规则各司其职运行方式# 检查问题不修改文件npmrun lint# 自动修复可修复的问题npmrun lint:fix客户端 workspace 内部也有独立的 lint 脚本# 仅对 client 目录运行 ESLintnpmrun lint-wclient# 自动修复npmrun lint-wclient ----fixPrettier格式化的分工在 ChatCrystal 中Biome 只负责全仓库 lint——biome.json里formatter和assist都是关闭的enabled: false只开了linter。也就是说项目并没有启用独立的格式化工具而是靠 lint 规则加编辑器默认格式化来保持风格一致。下面这套 Prettier 配置是面向如果你的项目要引入专门的格式化工具的通用方案不代表 ChatCrystal 的实际配置。但如果你的项目选择 Prettier 做格式化关键是要解决 ESLint 和 Prettier 的规则冲突。ESLint 的部分规则比如no-mixed-spaces-and-tabs和 Prettier 的格式化行为可能互相矛盾。解决方案是安装eslint-config-prettier它会关闭所有和 Prettier 冲突的 ESLint 规则npminstall-Dprettier eslint-config-prettier在配置中把 Prettier 放在最后// 传统 .eslintrc 格式{extends:[eslint:recommended,plugin:typescript-eslint/recommended,prettier// 放在最后关闭冲突规则]}Flat Config 中的写法importprettierConfigfromeslint-config-prettierexportdefault[js.configs.recommended,tseslint.configs.recommended,prettierConfig,// 放在最后]Prettier 本身的配置文件.prettierrc{semi:false,singleQuote:true,trailingComma:all,printWidth:100,tabWidth:2}核心原则ESLint 管代码质量Prettier 管代码格式两者不重叠。编辑器集成代码规范工具的最大价值在于开发时即时反馈而不是提交时才发现问题。VS Code 配置.vscode/settings.json{editor.formatOnSave:true,editor.defaultFormatter:esbenp.prettier-vscode,editor.codeActionsOnSave:{source.fixAll.eslint:explicit},eslint.validate:[typescript,typescriptreact]}配置后保存文件时会自动格式化 自动修复 ESLint 问题开发体验流畅。Cursor / 其他 VS Code 分支的配置方式相同因为它们共享同一套 extension API。Pre-commit Hooks提交前的最后一道防线编辑器集成依赖开发者主动配置而 pre-commit hooks 是强制执行的机制——不管用什么编辑器提交代码前必须通过检查。使用huskylint-staged的经典方案npminstall-Dhusky lint-staged npx husky init在package.json中配置 lint-staged{lint-staged:{*.{ts,tsx}:[eslint --fix,biome check --fix],*.{json,css,md}:[biome check --fix]}}.husky/pre-commit文件npx lint-staged工作流程git commit触发 husky → husky 运行 lint-staged → lint-staged 只对暂存区的文件执行检查和修复 → 检查通过才允许提交。lint-staged 的优势是只检查本次修改的文件而不是整个项目所以速度很快不会影响日常开发节奏。CI 中的 Lint 检查Pre-commit hooks 是本地防线CI 是远程门禁。两者缺一不可——开发者可以绕过本地 hooksgit commit --no-verify但无法绕过 CI。在 GitHub Actions 中添加 lint 步骤name:CIon:[push,pull_request]jobs:lint:runs-on:ubuntu-lateststeps:-uses:actions/checkoutv4-uses:actions/setup-nodev4with:node-version:20cache:npm-run:npm ci-run:npm run lintnpm run lint命令会依次运行 Biome 和 ESLint任何一步失败都会让 CI 变红阻止 PR 合并。建议在 CI 中只检查不修复用npm run lint而非npm run lint:fix因为 CI 的职责是发现问题而不是悄悄改代码。实用技巧1. 忽略特定规则有时某条规则在特定场景下不合理可以用行内注释临时关闭// eslint-disable-next-line typescript-eslint/no-explicit-anyconstdata:anyawaitfetchLegacyApi()但要注意这只是临时方案。如果同一规则被大量 disable说明这条规则可能不适合你的项目应该在配置文件中统一调整。2. 查看所有生效的规则npx eslint --print-config src/App.tsx这会输出对指定文件生效的完整规则列表调试配置问题时非常有用。3. 渐进式接入在一个没有 lint 的项目中引入 ESLint 时不要一开始就开启所有推荐规则。可以先用warn级别代替error逐步收紧rules:{typescript-eslint/no-explicit-any:warn,typescript-eslint/no-unused-vars:warn,}等团队适应后再升级为error避免一次性冒出上百个报错。小结工具职责ChatCrystal 中的角色ESLint代码质量检查客户端 React/TypeScript 专项检查BiomeLintformatter 已关闭全仓库通用 lint替代部分 ESLintPrettier代码格式化未使用本仓库未启用独立格式化工具husky lint-stagedPre-commit hook本地提交门禁GitHub ActionsCI 检查远程合并门禁ChatCrystal 的实践表明代码规范不需要追求工具数量关键是覆盖完整、分工明确、执行到位。用 Biome 做通用检查用 ESLint 做框架专属检查配合 pre-commit hooks 和 CI 形成完整的质量保障链路。下一步在自己的项目中运行npx eslint --init体验 Flat Config 的交互式初始化对比 Biome 和 ESLint Prettier 的性能差异选择适合团队的方案配置 husky lint-staged让代码规范成为团队习惯而非文档条款阅读 ESLint 官方文档 了解完整的规则配置ChatCrystal 项目代码开源在 GitHub欢迎查阅完整的 lint 配置实现。项目地址github.com/ZengLiangYi/ChatCrystal如有疑问欢迎在 GitHub Issues 或私信交流很乐意解答。