代码规范体系(EditorConfig、ESLint、Prettier、Husky)

发布时间:2026/8/3 20:35:33
代码规范体系(EditorConfig、ESLint、Prettier、Husky) 代码规范体系EditorConfig、ESLint、Prettier、HuskyEditorConfig属于编辑器层在最底层统一编辑器行为解决跨编辑器的格式差异。当你用 VS Code 打开一个文件缩进是空格还是 Tab、宽度是2还是4这些基础行为由 EditorConfig 说了算。EditorConfig 是一个跨编辑器的代码风格配置文件。它的设计理念是无论你用 VS Code、IntelliJ IDEA 还是 Vim打开同一个项目时基础编辑风格都自动保持一致。这个文件被大多数主流编辑器VS Code 需要安装插件和 IDE 原生支持。ESLint TSLint属于代码质量层检测代码质量和类型规范发现 bug 风格的写法。比如使用了 而不是 、使用了被废弃的 API、变量未声明就使用、import 语句没有排序等。Prettier属于格式统一层强制统一代码风格消除团队内对格式的争议。缩进用空格还是 Tab、引号用单还是双、行尾要不要逗号——这些视觉层面的问题由 Prettier 说了算。Husky lint-staged 自定义脚本属于提交管控层在代码进入仓库前执行全面检查阻断不符合规范的提交确保仓库代码质量始终如一。项目配置的优先级对比1.Git hooksHusky。这是最后一道防线优先级最高。即使你的编辑器配置完美git commit 时如果 ESLint 报 Error整个提交仍然会被阻断。这一层的意义是强制约束不能绕过。2.项目配置文件.eslintrc.js、tslint.json、.prettierrc、tsconfig.json。这些文件在特定工具运行时生效覆盖编辑器的格式化行为。比如你的 VS Code 设置了 tabSize: 4但 .prettierrc 配置了 tabWidth: 2那么 Prettier 格式化时输出的仍然是 2 空格。触发方式可以选择格式化文档(选使用prettier)或者.vscodesetting.json中配置editor.formatOnSave: true 保存代码自动格式化3.EditorConfig VS Code 用户设置。安装了 EditorConfig 插件后项目的 .editorconfig 会覆盖 VS Code 的默认设置但不会覆盖你手动在 VS Code settings.json 中配置的同名项。4.VS Code 默认设置。当没有任何配置文件时VS Code 使用自己的默认行为通常 tab 宽度 4、空格缩进等。EditorConfig 与 VSCode代码规范的生效始于你打开一个文件的那一刻。在这一刻决定代码长什么样的设置来自两个层面VSCode 原生设置settings.json和 EditorConfig.editorconfigVSCode 提供了两套配置体系UI 界面配置和 JSON 文件配置。UI 配置就是通过 Cmd , 打开的设置面板点一点滑块和勾选框就能改。这种方式直观但无法批量共享给团队。JSON 配置则是通过打开设置 JSON入口直接编辑 settings.json 文件好处是可以提交到 Git团队成员 clone 后直接使用。注意VS Code 不会自动读取 .editorconfig 文件须安装 EditorConfig插件才能生效。很多人因为没有安装插件误以为项目的 .editorconfig 配置有问题。那么既然 VS Code settings.json 也能配置这些选项为什么还要用 .editorconfig.editorconfig 与编辑器无关。如果团队中有人用 WebStorm、有人用 VS Code、有人用 Vim通过 .editorconfig 就能统一管理不需要每个人都去改自己的编辑器设置.editorconfig 支持目录层级的差异化配置。你可以在根目录设置全局规则同时在子目录中用不同的 .editorconfig 覆盖父级规则这种继承和覆盖机制是 settings.json 不支持的VSCode配置与EditorConfig 的关系VSCode 不会自动读取和应用 .editorconfig配置须安装EditorConfig插件才会生效。安装 EditorConfig 插件后项目的 .editorconfig 会覆盖 VSCode默认设置和用户设置团队成员不需要手动调整 VS Code 设置只要 clone 项目、安装插件、规范就自动生效.editorconfig 配置取自实际项目中使用root true[*]indent_style spaceindent_size 2end_of_line lfcharset utf-8trim_trailing_whitespace trueinsert_final_newline trueroot true 表示这是最顶层的配置EditorConfig 不会继续向上级目录查找配置。所有文件[*] 表示匹配所有文件类型统一使用 2 空格缩进、LF 换行符、UTF-8 编码settings.json 配置{eslint.validate: [javascript,javascriptreact,typescriptreact,typescript],typescript.tsdk: node_modules/typescript/lib}配置说明settings.json.editorconfigINI 格式解释editor.insertSpacestrueindent_style控制按 Tab 键是插入空格,不是是制表符editor.tabSizeindent_size控制缩进的宽度editor.trimTrailingWhitespacetruetrim_trailing_whitespace保存文件会自动删除每行末尾的多余空格editor.insertFinalNewline: trueinsert_final_newline保存文件自动在文件末尾确保有一个空行( POSIX 标准)files.eol // 控制换行符样式。\n 表示 LFUnix/macOS 标准\r\n 表示 CRLFWindows 标准。团队必须统一否则同一个文件在不同人的机器上换行符不同git diff 会变成全文件变更。 项目统一要求 LFfiles.encodingcharset设置默认文件编码为 utf8保持跨平台一致性。ESLintParser、Plugin、Config、extendsESLint 是一个代码质量检测工具它会在你写代码时或提交代码前检查代码是否符合预设的规则。ESLint 的检查范围很广既包括代码格式问题比如缩进、引号也包括代码质量问题比如使用了 而不是 、使用了被废弃的 API、变量未声明就使用等。ESLint 的工作方式可以类比为老师批改作业。老师手里有一份评分标准规则集学生交作业代码后老师对照标准逐条检查发现问题就打叉并标注原因。ESLint 就是这个老师规则集就是 ESLint 的配置文件。ESLint 在项目中运行时机开发时实时检测在使用 VS Code 编写代码时编辑器实时调用 ESLint 对当前文件进行检测发现问题会在代码中直接标出红色或黄色下划线让你编码的同时就知道哪里有问题提交前git pre-commit hook当执行 git commit时Husky 触发 pre-commit hooklint-staged 只对暂存的代码文件运行 ESLint 检测。如果检测到 Error 级别的问题提交被阻断ESLint 由四个关键组件构成理解它们是掌握 ESLint 的基础Parser解析器ESLint 本身只能处理标准的 JavaScript 代码但现代前端项目通常使用 TypeScript、Vue 的 SFC 等非标准语法。Parser 的作用是把这些非标准代码翻译成 ESLint 能理解的抽象语法树AST。常见的Parser 是 typescript-eslint/parser它专门负责把 TypeScript 代码解析成 ESLint 能处理的格式。Plugin插件ESLint 本身只包含最基础的规则。Plugin 是规则的扩展包每个插件都包含一组针对特定场景的规则。比如项目使用了两个插件typescript-eslint 插件提供了 TypeScript 相关的规则mtfe/video-base 插件则是团队自研的规则包包含该团队特有的规范要求。Config配置/规则插件提供的是规则素材库配置从这个素材库中选择启用哪些规则、每个规则的严格程度如何。ESLint规则有三种级别off不检查、warn标出但不阻断、error标出且阻断提交。extends继承在团队项目中通常会有一份公司级或团队级的 ESLint 配置作为基础项目在此之上进行个性化调整。本项目通过 extends 继承了三个配置plugin:mrn/eslint-plugin/recommended 来自 MRN 平台团队plugin:mtfe/mtvideo-base/recommended 来自美团短视频团队prettier 来自 eslint-config-prettier 用来关闭与 Prettier 冲突的规则。.eslintrc.jsESLint配置文件){root: true, // 表示这是项目的根配置文件ESLint 不会向上级目录寻找parser: typescript-eslint/parser, // parser指定使用什么工具来解析代码,如果项目用TypeScript必须用它对应的解析器plugins: [typescript-eslint, mtfe/mtvideo-base], // plugins声明要加载哪些规则插件extends: [ // extends继承已有的配置按顺序加载后面的会覆盖前面的plugin:mrn/eslint-plugin/recommended,plugin:mtfe/mtvideo-base/recommended,prettier, // prettier 配置用于关闭与 Prettier 冲突的 ESLint 规则],// rules在继承的基础上自定义调整规则rules: {no-restricted-properties: [ //这是一个ERROR 规则会阻断提交(禁止使用JSON.parse)error,{object: JSON,property: parse,message: 请使用 mrn/mtvideo-base-utils的jsonParseSafe代替JSON.parse。直接使用 JSON.parse可能抛错导致崩溃或异常行为,},],no-eq-null: off, // 关闭 no-eq-null 检查因为 TypeScript 的严格模式已经覆盖了这个场景react/prop-types: off, // React 的 prop-types 在 TypeScript 下不需要typescript-eslint/member-ordering: off, // 关闭成员排序因为业务代码的成员顺序可能需要按功能组织simple-import-sort/imports: error, // import 语句按字母顺序排序typescript-eslint/prefer-readonly: error, //prefer-readonly类成员能用readonly就用readonly// 以下规则从 error 降级为 warn代码中有遗留场景短期内无法全部修复react/no-access-state-in-setstate: warn,no-nested-ternary: warn,react/jsx-key: warn,no-unsafe-finally: warn,typescript-eslint/consistent-type-assertions: [warn,{// 使用 as 断言不用 Type 断言语法assertionStyle: as,// 禁止对象字面量的类型断言objectLiteralTypeAssertions: never,},],no-shadow: off, //关闭 shadow规则与 TS 的类型系统可能有冲突},overrides: [// overrides对特定文件做不同的配置优先级高于外层 rules// *.d.ts 类型声明文件关闭未使用变量检查{files: [*.d.ts],rules: {no-unused-vars: off,typescript-eslint/no-unused-vars: off,},},// *.js 文件不需要 TypeScript 类型检查{files: [*.js],parserOptions: {project: null,},extends: [plugin:mtfe/mtvideo-base/disableTs],},],// ignorePatterns告诉 ESLint 忽略这些文件和目录ignorePatterns: [node_modules/**/*,dist/**/*,**/Models/**/*, // 自动生成的代码cli/**/*, // 命令行脚本plugin/**/*,src/**/*, // src 目录由 TSLint 负责],globals: { // globals在代码中可以使用的全局变量不会报未定义错误__filename: readonly,__BUILD_TIME__: readonly,__CODE_LINE__: readonly,__dirname: readonly,__jsiExecutorDescription: readonly,},}PrettierPrettier 是一个代码格式化工具Formatter它的职责与 ESLint 有部分重叠但定位不同。ESLint 更偏向代码质量关注的是代码是否正确、是否安全、是否有潜在bug。Prettier 更偏向代码风格关注的是缩进是 tab 还是空格、字符串用单引号还是双引号、行尾是否加逗号等视觉层面的统一。Prettier 的设计哲学是做一个固执己见的代码格式化工具。它只提供少量配置项强制团队接受统一的格式化风格不留讨论余地。你不需要纠结我们团队应该用单引号还是双引号Prettier 说用单引号就都用单引号不需要 code review 时因为格式问题争论不休。为什么ESLint和Prettier需要配合使用ESLint 本身也有一部分格式化规则比如 indent、quotes、semi 等这些规则和 Prettier 的功能是重叠的。如果两个工具的规则不一致比如 ESLint 要求加分号Prettier 要求不加分号那就会产生冲突——ESLint 检测到不加分号会报错Prettier 格式化后恰好不加分号然后 ESLint 又报错形成死循环。解决方案是使用 eslint-config-prettier 这个包它的作用是关闭ESLint中所有与 Prettier 冲突的格式化规则让ESLint专注于代码质量检查Prettier 专注于代码格式化。各司其职互不干扰。Prettier配置(.prettierrc){semi: true,singleQuote: true,arrowParens: always,trailingComma: all,preferConst: true,tabWidth: 2}trailingComma: all 表示在 ES5 合法的地方都加尾逗号。这个配置在现代前端项目中非常常见因为它可以让 git diff 更加清晰——如果一行只添加了一个字段但原行没有逗号git diff 只会显示新增的那一行而如果原行末尾没有逗号则会显示修改了原行末尾并新增了一行导致 diff 变得冗长。arrowParens: always 表示箭头函数的参数无论有多少个都加括号。比如 (x) x 1 而不是 x x 1。这样做的好处是代码风格统一减少视觉干扰。preferConst的实际执行由 ESLint 的prefer-const规则负责而非 Prettier。Husky 与 Git Hooks在理解 Husky 之前需要先理解 Git Hooks 是什么。Git 允许你在特定的时机自动执行自定义脚本这些时机包括提交之前、提交之后、推送之前、推送之后等。Git Hooks 就是这些时机点上挂载的自定义脚本。Git 自带了一些默认的 Hooks 脚本模板位于 .git/hooks/ 目录下比如 pre-commit.sample、commit-msg.sample、post-commit.sample 等。这些文件默认是禁用的因为带 .sample 后缀只需要把 .sample 后缀去掉Git 就会在对应时机执行这些脚本。Husky 的作用是通过 package.json 配置 Git Hooks而不需要手动编辑 .git/hooks/ 目录下的脚本文件。在没有 Husky 的时代如果你想实现每次 git commit 前运行 ESLint你需要手动编辑 .git/hooks/pre-commit 文件写入 shell 脚本。这有几个问题这些脚本文件通常不会被 git 跟踪团队成员每次 clone 项目后都需要手动配置Windows 系统的 shell 脚本语法不同兼容性差不同项目可能有不同的 Hooks 配置不方便管理。Husky 通过在package.json中声明式地配置 Git Hooks 解决了这些问题。当运行 npx husky install 时Husky 会自动在 .git/hooks/ 目录下生成对应的 hook 脚本并将团队的配置存储在 .husky/ 目录下可以被 git 跟踪。团队其他成员 clone 项目后运行 yarn依赖 postinstall 脚本会自动安装 Husky Hooks。package.jsonz中配置huskyhusky: {hooks: {pre-commit: yarn lint-staged node ./cli/check.js,post-merge: yarn}}当开发者执行 git commit 时Git 检测到 pre-commit hook 存在并执行它。Husky 生成的 pre-commit 脚本运行 yarn lint-staged node ./cli/check.js。lint-staged 找出本次 commit 涉及的源代码文件并对每个文件执行 ESLint 检测和自动修复。如果有任何 Error 级别的问题lint-staged 以非零状态码退出git commit 被阻断。如果所有检查通过git commit 继续执行。post-merge: yarn 表示当执行 git pull 或 git merge 合并别人的代码后Git 自动运行 yarn 来安装可能发生变化的依赖。这个 hook 非常重要因为如果其他人修改了 package.json你需要立即安装新的依赖否则项目可能因为依赖不一致而无法运行。lint-staged的作用就是只对本次 commit 涉及的即 staged 的文件运行 ESLint而不是全量检查整个项目。这大大提高了检查速度也避免了别人的代码有问题但你被迫无法提交的情况。package.json中配置lint-stagedlint-staged: {Reward/**/*.{tsx,ts,jsx,js}: eslint --quiet,src/Message/Publish/**/*.{tsx,ts}: eslint --quiet,src/Components/VideoUgcCreation/**/*.{tsx,ts}: eslint --quiet,src/TagAggregation/**/*.{tsx,ts}: eslint --quiet,src/Message/NewFocus/**/*.{tsx,ts}: eslint --quiet,src/**/*.{tsx,ts}: eslint --quiet,Models/**/*.{js,ts,tsx}: node cli/check_model.js}配置格式是glob 模式: 命令。每行表示匹配该模式的 staged 文件将执行对应的命令。例如src/**/*.tsx 表示 src 目录下所有 .tsx 文件执行 eslint --quiet 检测。--quiet 参数表示只显示 Error 级别的问题不显示 Warning这样可以减少噪音同时确保严重的格式问题不被放过。ESLint和TSLint部分项目中同时存在 ESLint.eslintrc.js和 TSLinttslint.json两套工具。这是因为 TSLint 是 TypeScript 官方曾经推荐的检测工具后来 TSLint 团队宣布停止维护并推荐用户迁移到 ESLint 的 typescript-eslint 插件。美团短视频团队在迁移过程中采用了双轨制过渡策略TSLint 继续承担对 src 目录的代码检测ESLint 逐步接管更多场景。从 gamevideo 项目的配置中可以看到ESLint 的 ignorePatterns 里排除了 src/**/*表示 src 目录暂时由 TSLint 负责。TSLinttslint.json配置{extends: [hfe/mrn-tslint],linterOptions: {exclude: [node_modules/**,Models/**,TSApis/**,src/Message/Publish/**,src/Components/VideoUgcCreation/**,src/TagAggregation/**,src/Message/NewFocus/**,**/*.bak]},rules: {semicolon: [true, always, ignore-bound-class-methods],quotemark: [true, single, jsx-double],no-var-keyword: true,prefer-const: true,render-with-short-circuit-calculations: false,no-channel-in-params: false,disallow-null-header: false,test-style: false,render-function-no-setstate: false,endup-with-separator: false,no-arrowfunction-in-renderfunction: false,avoid-onpress-in-view: false,ter-indent: false}}自定义检查脚本 cli/check.jscli/check.js 不是 ESLint 本身而是一个Node.js 脚本串联了多个专项检查工具。当执行 git commit 时这个脚本串行执行以下检查tsconfig.jsonTypeScript 类型检查配置{compilerOptions: {allowJs: false,checkJs: false,declaration: true,target: esnext,module: esnext,sourceMap: true,experimentalDecorators: true,jsx: react-native,allowSyntheticDefaultImports: true,moduleResolution: node,strict: true,skipLibCheck: true,noEmit: true,baseUrl: .,paths: {assets/*: [src/assets/*],src/*: [src/*],reward/*: [Reward/*]}},include: [types/**/*,src/**/*.ts,src/**/*.tsx,Models/**/*.tsx,Reward/**/*.ts,Reward/**/*.tsx,lib/**/*.ts,lib/**/*.tsx,declarations.d.ts]}strict: true 开启了 TypeScript 的所有严格类型检查相当于同时设置了 strictNullChecks、strictFunctionTypes、noImplicitAny 等多个检查开关会大幅提升类型安全性。skipLibCheck: true 跳过对 node_modules 中 .d.ts 类型声明文件的检查可以显著加快类型检查速度。noEmit: true 表示只做类型检查不输出任何文件因为构建产物由专门的构建工具metro bundler处理。paths 配置了路径别名assets/* 会被解析为 src/assets/*这样就可以用 import img from assets/images/logo.png 代替相对路径。优先级冲突的常见场景最常见的冲突场景是 VS Code 格式化与 Prettier 格式化结果不一致。原因通常是 VS Code 的 formatter 设置的不是 Prettier或者没有安装 Prettier 插件。解决方案是确保 VS Code 配置了 editor.defaultFormatter: esbenp.prettier-vscode 并安装 Prettier 插件。ESLint 检测报错但编辑器没有标红。这通常是因为没有安装 ESLint VS Code 插件或者 ESLint 插件没有正常工作。可以尝试在 VS Code 中按 CmdShiftP输入 ESLint: Restart ESLint Server然后回车重启 ESLint 服务。第三个常见场景是 git commit 被阻断但本地检测没有报错。可能是因为 lint-staged 只检测 staged 的文件而完整的 check.js 检测的是整个项目。可以手动运行 node ./cli/check.js 查看具体是什么检查失败了。常用命令参考yarn lint // 运行 ESLint 检测yarn fix // 自动修复可修复的 ESLint 问题yarn cacheClean // 清理缓存