从什么时候开始前端项目“格式化”变成了个需要开会讨论的事我印象最深的一次是团队里三个人写同一个组件库有人用单引号有人用双引号有人喜欢加分号有人觉得分号是噪音。Git 提交记录里一半是“fix style”Code Review 里最热闹的评论永远是“这里该加个空格”。后来我把 ESLint 和 Prettier 这套组合装进项目提交前自动检查、保存时自动修复规则直接锁死在配置文件里争论瞬间消失提交记录干净了新人进来也不再问“你们的缩进是几个空格”这种问题了。这篇文章不聊虚的就是一套可以直接抄走的实战方案ESLint 做代码质量检查Prettier 做代码统一格式化两者搭配使用再配合 Husky 和 lint-staged 在 Git 提交前自动把关。不管你是正在搭建前端工程化的新手还是被团队代码风格折磨到崩溃的老手这套组合都能让你的项目“风格自治”。先说清楚这两兄弟的分工再一步步把工具链配起来最后给你一份排坑实录。1. ESLint 和 Prettier 到底解决什么问题先搞清楚分工再动手很多新手会有个困惑ESLint 不是能格式化代码吗为什么还要装 Prettier这俩功能不是重叠的吗这是个特别常见的问题但答案其实很简单——它们的核心职责完全不一样。1.1 一个是“纪律委员”一个是“排版工人”ESLint 的角色更像团队里的纪律委员它管的是“代码对不对”。比如你定义了一个变量但从来没用过它会报错你在循环里改了循环变量的值它也能查出来你用了而不是它会提醒你有安全隐患。这些属于代码质量和潜在 bug 的范畴ESLint 就是干这个的。Prettier 的角色则是个偏执狂级别的排版工人它只关心“代码长得帅不帅”。字符串用什么引号、行尾加不加分号、换行时怎么缩进、一行最多多少列——这些纯风格问题Prettier 会用一套固定规则把代码重新“打印”一遍输出结果在任何机器上都是一模一样的没有任何主观判断空间。简单记忆ESLint 查“错”Prettier 管“美”。前者抓的是逻辑隐患后者治的是风格分裂。1.2 为什么两者必须搭配使用这里要纠正一个误区ESLint 确实自带一部分格式化能力比如quotes规则可以强制单引号或双引号semi规则可以强制加不加分号。但问题是ESLint 的强项是“找问题”它的格式化规则覆盖范围有限而且不同规则的配置粒度很细细到你为了一件小事要写一大堆配置。Prettier 则完全不同它不需要你逐条声明该怎么格式化它内置了一套经过大量社区验证的默认风格你只需要在.prettierrc里改几个关键开关。它的格式化能力是全量覆盖的从 JSX 属性换行到 Markdown 表格对齐全都拿捏得死死的。最合理的分工是让 Prettier 负责所有风格问题再用 ESLint 负责代码质量和潜在错误检测。这样一来两个工具各司其职你不会在 ESLint 里为了对齐引号风格浪费精力也不用担心 Prettier 帮你写了错代码。1.3 多人协作时这套组合真正解决的问题实际项目里代码风格混乱从来都不是技术问题是协作成本问题。举个我遇到过的真实场景项目里有个老同事标准的“加分号党”提交的代码每个语句结尾都带分号另一个新同事是从 Python 那边转过来的习惯无分号风格。两个人改同一个文件时Git 的 diff 里全是引号和分号的改动真正的逻辑改动反而被淹没在一堆“风格漂移”里。Code Review 的时候评审人被迫在一堆格式噪音里找真正的 bug效率极低。更现实的是这种格式争论会消耗团队情绪本来该讨论的是实现方案结果每次都在“该不该加分号”这种破事上吵半小时。ESLint Prettier 这套组合的核心价值就在这里把风格决策从“人”手里拿走交给工具。规则定好之后谁都不需要再争了机器说了算。提交之前 check 一下不规范根本不允许提交省下的时间拿去写业务不香吗2. 从零搭建初始化项目与安装工具链接下来直接进入实操环节。我用目前最典型的项目场景来演示Vite React TypeScript。这套组合的本质其实不绑定任何框架换成 Vue、Svelte 或者纯 Node 项目原理完全一致只是插件名会有些变化。2.1 创建项目与安装依赖先创建一个 Vite React TS 项目这一步大家都会就不多啰嗦了npm create vitelatest my-project -- --template react-ts cd my-project npm install然后安装 ESLint、Prettier 以及相关插件npm install -D eslint prettier npm install -D typescript-eslint eslint/js npm install -D eslint-plugin-react-hooks eslint-plugin-react-refresh npm install -D eslint-config-prettier这里每个包都是有明确职责的eslint核心检查引擎prettier核心格式化引擎eslint/jsESLint 官方推荐的 JavaScript 规则集typescript-eslint让 ESLint 能理解 TypeScript 语法并提供 TS 专属规则eslint-plugin-react-hooksReact Hooks 规则检查比如 hooks 的依赖数组规范eslint-plugin-react-refreshReact Fast Refresh 相关检查Vite 项目标配eslint-config-prettier这个很关键它的作用是把 ESLint 里和 Prettier 冲突的格式化规则全部关掉避免两兄弟打架注意如果你用的是 Vue 项目把react-hooks和react-refresh替换成eslint-plugin-vue即可。原理一致插件不同。2.2 版本选择我为什么要站在 ESLint 9 和 Prettier 3 这边写这篇文章的时候ESLint 已经到了 9.x 版本配置文件默认是 flat config也就是eslint.config.js以前的.eslintrc那套配置方式已经被官方标记为废弃。网上大量教程还停留在旧版 eslintrc 的写法新手照着抄经常会遇到ESLintError: .eslintrc is no longer supported之类的问题非常劝退。所以我这边直接用 flat config 来讲这套配置在当前和未来一两年内都是主流。Prettier 3.x 没有什么颠覆性的配置变更但它把配置文件解析、CLI 输出这些底层能力做了不少优化而且和 ESLint 的兼容性处理得更干净。直接用最新稳定版就好。如果你是为新项目搭建版本选eslint^9、prettier^3没有历史包袱。2.3 基础文件结构安装完依赖之后在项目根目录创建这些文件├── .prettierrc ├── .prettierignore ├── eslint.config.js ├── package.json └── src/等一下这里有一个细节我必须强调新版 ESLint 的 flat config 只有一个入口文件那就是eslint.config.js以前常见的.eslintrc.js和eslintConfigpackage.json 里的字段都不再被读取了。你这个项目如果是新搭建的就老老实实新建eslint.config.js别再去网上找老教程了。3. 核心配置ESLint 的 flat config 与 Prettier 的无缝配合配置环节是整个工程化的核心也是大多数人容易踩坑的地方。我先把完整配置贴出来再逐块解释每一段在干什么为什么这么写。3.1 先说说 ESLint 9 的 flat config 是怎么回事flat config 是 ESLint 从 9.0 开始默认的配置系统它把旧版基于“继承 extends”的写法改成了“纯对象数组”的写法。每个配置对象都明确告诉你这些文件用这些规则。没有继承链的魔法没有配置项的隐式叠加一切都在一个数组里平铺。好处是配置可预测性大幅提升。你看到的配置就是最终生效的配置不会再遇到那种“我在 A 配置里 extends 了一下结果规则被某个依赖覆盖成奇怪行为”的玄学问题。坏处唯一的坏处是你需要习惯这种新写法但一旦理解了其实比旧版简单太多。3.2 配置文件详解一份能直接落地的 eslint.config.js下面是我实际项目里在用的一份配置做了一些简化但核心结构完全一致import js from eslint/js; import globals from globals; import reactHooks from eslint-plugin-react-hooks; import reactRefresh from eslint-plugin-react-refresh; import tseslint from typescript-eslint; import prettier from eslint-config-prettier; export default tseslint.config( { ignores: [dist, node_modules] }, { files: [**/*.{ts,tsx}], extends: [ js.configs.recommended, ...tseslint.configs.recommended, ], languageOptions: { ecmaVersion: 2020, globals: { ...globals.browser, ...globals.node, }, }, plugins: { react-hooks: reactHooks, react-refresh: reactRefresh, }, rules: { ...reactHooks.configs.recommended.rules, react-refresh/only-export-components: [ warn, { allowConstantExport: true }, ], }, }, prettier, );这个配置里每个部分都有讲究。tseslint.config()是个封装方法它把 TypeScript ESLint 的配置项做了一层扁平化合并比手动展开数组要省事不少还能处理配置覆盖的优先级问题。{ ignores: [dist, node_modules] }是全局忽略配置告诉 ESLint 别去检查构建产物和依赖目录不然跑一次检查要扫几万个文件速度慢还没意义。files: [**/*.{ts,tsx}]指定这套规则只作用于 TypeScript 文件。如果你项目里还有.js文件可以再加一个对象或者用[**/*.{js,jsx,ts,tsx}]全部覆盖。languageOptions里设置 ECMAScript 版本和全局变量。globals.browser把window、document、localStorage这些浏览器环境变量都声明了不然 ESLint 会爆window is not defined这种误报。globals.node则让process、__dirname这类 Node 环境变量正常通过。这个配置在纯前端项目中很常用因为 Vite 的配置文件本身也是 Node 环境下跑的。plugins和rules部分是 React 项目专属配置。react-hooks的推荐规则会强制你正确使用useEffect、useMemo的依赖数组react-refresh/only-export-components则检查你是否在组件文件里导出了非组件的内容这个规则对 Vite 的 Fast Refresh 体验影响很大导出一个常量函数会导致热更新失效配置成 warn 能提前提醒你。最后一行prettier就是刚才说的eslint-config-prettier它会把 ESLint 里所有跟格式化相关的规则全部关掉。这块太关键了我单独拉出来讲。3.3 Prettier 的核心参数选择与理由Prettier 的配置文件放根目录命名是.prettierrc用 JSON 格式写。我用的配置如下{ semi: true, singleQuote: true, printWidth: 100, trailingComma: all, tabWidth: 2, jsxSingleQuote: false, arrowParens: always, endOfLine: auto }逐个解释一下这些参数的取舍semi: true行尾加分号。这个配置在网上争议最大但我的观点是加分号是默认安全选项。虽然现代 JavaScript 有 ASI自动分号插入机制不加分号大多时候也不会出错但在某些情况下比如一行以[或(开头时不加分号会导致上一行被错误解析。团队协作时为了少数人觉得“好看”去承受这种不确定性性价比不高。singleQuote: true字符串用单引号。这更多是行业审美趋势也跟团队习惯有关。JS 社区对单引号的偏好度确实更高而且 JSX 里的属性又强制用双引号单双混用反而是某种程度上的自动分类视觉上能区分“这是字符串”和“这是 JSX 属性”。printWidth: 100每行最多 100 列。Prettier 默认是 80但 80 在现代宽屏显示器下太紧了嵌套几层就疯狂换行反而影响阅读。我个人经验是 100 是个不错的平衡点既不会太长导致横向滚动又不会频繁折行。如果你团队习惯比较保守或者经常多人 review120 也完全可以但这个参数决定了 Prettier 的换行策略建议全团队保持一致后不要轻易改。trailingComma: all所有函数参数、数组、对象等多行结构的结尾都加上逗号。这个参数我个人强烈推荐原因很实用git diff更干净。你在数组末尾加一项时如果最后一项后面有逗号diff 只会显示新增的一行如果没逗号还得顺手改上一行diff 就变成两行。这种小优化在频繁改动数据配置的项目里体验差别巨大。tabWidth: 2缩进 2 个空格。这个是前端社区的绝对主流不多解释。jsxSingleQuote: falseJSX 属性保持双引号。配合上面说的单双引号分离策略JSX 属性统一双引号代码视觉上更清晰。arrowParens: always箭头函数参数永远加括号。即使只有一个参数也不省略括号。这是为了避免“今天写x x明天加一个参数变成(x, y) x y原来那个x x也要跟着改”这种无意义 diff。endOfLine: auto换行符自动识别。这个是 Windows Mac 协作项目必备。Windows 默认 CRLFMac/Linux 是 LF如果不处理这个参数Prettier 会把整个文件的换行符全改一遍Git diff 里全是^M符号非常痛苦。配置完之后再建一个.prettierignore文件跟 ESLint 的 ignores 功能一样告诉 Prettier 哪些文件不需要格式化dist node_modules package-lock.json pnpm-lock.yaml.gitignore文件已经有这些目录了但 Prettier 不认识.gitignore必须单独声明。3.4 解决 ESLint 和 Prettier 的规则冲突很多人第一次同时跑 ESLint 和 Prettier 会碰到这种场景ESLint 提示“字符串必须用双引号”Prettier 却把代码统一成了单引号。两个工具的命令一前一后跑代码被来回改像两个人在编辑同一个文件打架。解决方式就是eslint-config-prettier。它做的事情很简单粗暴把 ESLint 内置规则里所有与 Prettier 冲突的规则全部关掉。因为风格问题已经交给 Prettier 了ESLint 再用自己那套格式化规则去管就是多管闲事还产生冲突。配置方法非常轻量在上面那段配置文件的数组里把prettier作为最后一个配置对象传入即可。为什么必须放最后一个因为 flat config 数组后面的配置会覆盖前面的配置只有放在最后它才能确保关闭规则的优先级最高。这一步就是“配置顺序第一坑”很多人把prettier放在数组前面结果冲突规则又被后面的配置覆盖回来了白配了。3.5 适配 TypeScript 和 React 的注意事项TypeScript 的适配主要依赖typescript-eslint项目。它提供了两套规则集recommended推荐和strict严格。默认推荐配置已经能拦住绝大多数与类型相关的常见问题但如果你的项目对类型安全要求特别高可以升级到strict模式但要做好心理准备初期会暴露出不少历史遗留的类型问题。React 项目还有两个专属插件值得投入时间eslint-plugin-react-hooks的规则主要是管useEffect依赖的。它会在你遗漏依赖项时警告比如你用了useEffect(() { fetchData(id) }, [])但fetchData和id都没进依赖数组它就会提醒你“请把 id 加进依赖数组”。这个检查能预防很多闭包过期的问题。eslint-plugin-react-refresh则是在 Vite 项目里配合 Fast Refresh 用的。Fast Refresh 要求一个文件只导出 React 组件如果在同一个文件里既导出了组件又导出了一个工具函数热更新会退化成整页刷新开发体验直线下降。这个插件就是提前帮你发现这类问题。4. 自动修复与编辑器集成把规范变成零成本配置写完之后接下来要解决的问题是怎么让这套规范真正跑起来并且在开发过程中自动生效而不是等提交的时候才被拦截。4.1 命令行自动修复命令ESLint 提供--fix参数可以直接自动修复所有能修复的问题npx eslint . --fixPrettier 也有对应的格式化命令npx prettier --write .--fix能修复的包括未使用变量删除、变量定义改为const、字符串引号统一这些但有一些问题它不会帮你修比如逻辑错误、变量泄漏为全局这种需要人判断的问题。Prettier 的--write则是“全格式化”它会对文件重新排版改完基本整个文件的格式都统一了。注意别把eslint . --fix和prettier --write的执行顺序搞反。先跑prettier --write格式化风格再跑eslint . --fix修复可以自动修复的代码质量问题。反过来很容易出现 Prettier 把 ESLint 刚修好的格式又改回去的尴尬情况。当然实际开发中你不会频繁手动敲这两个命令这就是下面编辑器集成的意义。4.2 VSCode 保存时自动格式化如果你用 VSCode大多数人强烈建议配置保存时自动格式化。安装两个扩展ESLint 和 Prettier。然后在项目根目录建一个.vscode/settings.json{ editor.defaultFormatter: esbenp.prettier-vscode, editor.formatOnSave: true, editor.codeActionsOnSave: { source.fixAll.eslint: explicit } }这个配置实现了三件事editor.formatOnSave: true让编辑器在你 CtrlS 保存时自动运行 Prettier 格式化当前文件这样不规范的文件一保存就被重排了。editor.codeActionsOnSave.source.fixAll.eslint让编辑器在保存时自动执行 ESLint 的--fix操作。这个动作主要是为了修复 ESLint 能自动修的那些代码质量问题比如自动导入、未使用变量清理等。editor.defaultFormatter指定默认格式化工具是 Prettier避免和其他格式化插件冲突。这里有个常见的坑VSCode 默认格式化工具可能是 TypeScript 语言服务内置的或者你装了其他格式化插件多个插件同时接管格式化会导致“改完 A 又改 B”的现象所以必须在项目级别明确指定。4.3 在 package.json 里固化工作流团队协作时配置文件只能保证“如果你跑了我就能生效”但没有人会主动记着在提交前手动跑两条命令。所以在package.json里固化 scripts 是很重要的一步{ scripts: { lint: eslint ., lint:fix: eslint . --fix, format: prettier --write ., format:check: prettier --check . } }lint和format:check是给 CI 在合并前跑的检查命令确保代码合并进主干之前已经全部符合规范。lint:fix和format是给开发者本地开发时手动跑的。这套命令组合是团队协作基线没有这两条命令后面配的 Husky 也没法用。5. Husky lint-staged提交前守住最后一道防线配置再完美总有人会绕过。有人会忘记配置编辑器有人会直接跳过 IDE 里的警告还有人会把提交信息写得乱七八糟。所以必须在 Git 提交这个唯一必经之路上设置关卡强制校验。5.1 为什么这个环节必不可少没有 Git 钩子之前代码风格检查属于“自觉行为”。你想查就查不查也没人知道你提交的是不是一堆格式垃圾。问题往往是这样出现的老同事跑过 lint 了提交没问题新同事提交了个格式半残的文件CI 只要不是配了检查就发现不了等 Code Review 阶段被指出来又是一轮改代码。Husky 的作用是把检查时机从“人想起来跑”变成“Git 操作自动触发”。当git commit命令执行时husky 会先去执行你配置的钩子脚本如果脚本返回非零状态码提交直接终止不规范的代码根本进不了 Git 仓库。这就是工程化的“强制力”所在。5.2 安装并初始化 huskyHusky 从 9.x 版本开始安装方式变得非常简洁npm install -D husky npx husky inithusky init会自动帮你做几件事在项目根目录创建.husky/目录、生成一个pre-commit示例钩子、在 package.json 里添加prepare: husky脚本。这个prepare脚本是关键它会让安装了 husky 的项目在npm install之后自动激活 Git 钩子新克隆项目的人跑一次 install钩子就自动装上了不需要手动做任何事。然后编辑.husky/pre-commit文件内容先简化为npm run lint npm run format:check但是这一步先别急直接这样写有个性能问题每次提交都要全量检查整个项目项目一大检查就要几十秒开发者会被逼疯的。这就是 lint-staged 登场的原因。5.3 配置 lint-staged 实现精准检查lint-staged 的思路很聪明只检查本次提交时发生变动的文件。你改了两个文件提交时就只 lint 和 format 这两个文件其他文件一概不动。这样既保证了提交内容都是干净的又不会让开发者等太久。安装npm install -D lint-staged然后在 package.json 里加一段配置{ lint-staged: { *.{ts,tsx,js,jsx}: [ prettier --write, eslint --fix, eslint ], *.{json,css,md}: [ prettier --write ] } }这个配置的含义是当 Git 暂存区里有.ts/.tsx/.js/.jsx文件时按顺序执行prettier --write格式化、eslint --fix自动修复、最后再跑一次eslint做最终校验。如果最后一步校验失败说明这个文件存在无法自动修复的问题提交会被阻止你需要手动处理。为什么最后要再跑一次eslint因为--fix只能修一部分问题修完之后可能还有残留问题需要人工处理。如果不加最后这个校验前面的--fix就只是“尽力而为”残留的问题照样能进到仓库里。然后修改.husky/pre-commitnpx lint-staged这样每次提交时只有暂存区内的文件会被执行格式化和代码检查。5.4 提交前检查的实战坑点lint-staged 第一次跑起来时会有几个小坑我帮你提前踩掉第一个坑lint-staged 原文件路径问题。如果你之前配置过 prettier 的--write时带了目录参数比如prettier --write src/在 lint-staged 里千万不要这么写。lint-staged 会自动把暂存的文件路径传递到命令末尾你只需要写命令名。如果你写了目录参数它会格式化整个目录等于没做“只检查改动文件”的优化。第二个坑Windows 用户的 husky 钩子不生效。Husky 的核心机制是向.git/hooks写入 shell 脚本Windows 上如果 Git 没有正确安装到系统 PATH或者你使用的是某些第三方终端应用没有继承环境变量钩子可能静默失败。遇到这种情况先用npx husky手动激活一次再看.git/hooks/pre-commit是否存在。如果还是没有效果检查 git 版本建议升级到 2.36 以上husky 9.x 对 Windows 的兼容性在这个版本之后才有保障。第三个坑不要在生产环境跑 lint-staged。husky 生成的prepare脚本会在每次npm install时执行如果你在 CI 里构建或者将来有人用npm install --production装依赖prepare 脚本也会执行但此时可能没有 devDependencies。建议在.npmrc或者 CI 配置里显式跳过 husky 安装或者至少在服务端安装时带上--productionfalse避免奇怪的报错。第四个坑也是我认为最重要的提交前只检查不自动改用户未暂存的文件。lint-staged 有个设计它会用临时快照来工作确保你已经git add过但还没提交的内容在检查时保持原样这样你只需要 add 一次。但如果 lint-staged 自动修复了格式修改后的文件会出现在工作区但没有重新 add此时提交会失败。怎么处理初始化提交时先git add一遍让 lint-staged 跑的时候所有改动都已经在暂存区里了如果它改了文件命令返回成功但你本地还有未提交的格式改动下一轮提交自然会带上。实际使用中体验稍微绕一点习惯了就还好。6. 常见问题与排查技巧实录配置一套工具链不遇到点问题是不现实的。我从自己项目和帮别人排障的经历里挑了几个高频问题整理成一份速查手册。6.1 规则冲突与配置不生效问ESLint 明明在配置里写了semi: false为什么执行后分号还是被加上去了大概率是eslint-config-prettier被放到了配置数组的中间后面的规则覆盖了前面的。上面已经强调过prettier必须放在数组最后一位这是 flat config 覆盖机制决定的。还有一个可能是你的 IDE 没有加载最新配置重启一下 ESLint 服务或者重启 VSCode 就有奇效。问配置了 Prettier 的singleQuote: true但 ESLint--fix后字符串全变成双引号了这说明你没有安装eslint-config-prettier或者它配置的顺序不对。ESLint 自带的quotes规则默认强制双引号Prettier 强制单引号二者冲突。装好eslint-config-prettier并放最后ESLint 的quotes规则就会关闭冲突自然消失。问eslint.config.js里写的规则在编辑器里完全没生效。先检查你打开的是不是项目根目录ESLint 在编辑器中默认只向上查找最近的eslint.config.js。如果你在src/子目录建了配置文件根目录没有编辑器可能会在项目根找到一份旧版配置文件并忽略你的新配置。最佳实践是在项目根目录只保留一份eslint.config.js不用建第二份。6.2 编辑器格式化结果与命令行不一致问我在 VSCode 里保存时格式化好的代码命令行跑prettier --write .后又被改了怎么回事这几乎可以肯定是 VSCode 里配置了不止一个格式化工具或者默认格式化工具被设置成了别的。比如你项目里装了 VolarVue 插件Volar 自己也能格式化容易抢占 Prettier 的工作。正确做法是保证editor.defaultFormatter明确指定为 Prettier同时在项目级 settings 里把其他格式化插件的 Auto Format 关掉。问团队里有人用 WebStorm有人用 VSCode格式化结果不一致。WebStorm 需要在设置里手动启用 Prettier 作为默认格式化器Setings - Languages Frameworks - JavaScript - Prettier选择Automatic Prettier configuration然后勾选Run on save。这个配置通常只需要每个人自己设置一次配合.editorconfig文件可以进一步统一缩进和换行行为。不过只要大家最终都是跑同样的.prettierrc结果就会一致编辑器只是触发方式不同而已。6.3 团队协作中的规则变更流程问项目做大了之后想改某个 lint 规则怎么操作才安全我的经验是宁可多花点时间做渐进式变更也不要一次性把整条规则打开然后全量修复。比如你的团队决定不再使用any类型想开启no-explicit-any这会瞬间爆出几千个错误。正确做法是先用warn级别跑一个月给大家时间逐步修复再升级成error强制禁止。如果项目实在历史包袱太重也可以在 ESLint 配置里用override只针对新文件开启新规则旧文件放行。这是非常实用的过渡策略{ files: [src/**/*.ts], rules: { typescript-eslint/no-explicit-any: off } }6.4 快速排查清单最后附上一份我自己排障时常用的检查清单遇到问题先过一遍症状排查方向解决思路配置不生效配置文件位置/命名确认是eslint.config.js且位于项目根目录冲突修复eslint-config-prettier顺序确保prettier配置在数组最后编辑器格式化不一致defaultFormatter设置项目级 settings.json 明确指定 PrettierGit 钩子不执行husky 是否激活重新运行npx husky initlint-staged 用了全量文件命令写法问题只写命令名不要带目录参数Windows 钩子不生效Git 环境变量升级 Git手动运行npx husky激活CI 里报 lint 错误scripts 漏配或忽略配置检查 package.json 的 scripts 和 ignores配置一套 ESLint Prettier 的说到底就是给项目立了一份“自动执行的规矩”前期花半小时搭好后面每天省下的都是跟同事扯皮的时间。我个人在实际操作中的体会是这套工具链真正磨合好的标志不是所有人都会配而是所有人都不需要知道它具体怎么配——打开项目改代码保存提交一切自动发生这才是工程化该有的样子。最后再分享一个小技巧如果你要管理多个项目别在每台新机器上重装一遍依赖把.vscode/settings.json、eslint.config.js、.prettierrc这几个文件放进你的个人代码模板里以后开新项目直接复制五分钟就能落地一套完整的规范体系。