为什么选择 Prettier:一份“固执己见“的代码格式化器采纳指南
2026/9/19 6:16:26 网站建设 项目流程

为什么选择 Prettier:一份"固执己见"的代码格式化器采纳指南

【免费下载链接】prettierPrettier is an opinionated code formatter.项目地址: https://gitcode.com/gh_mirrors/pr/prettier

Prettier 是一个"固执己见"(opinionated)的代码格式化器:它解析你的代码、剥离原有排版,再按自身规则重新打印。本文基于仓库官方文档《Why Prettier?》,系统梳理团队采纳 Prettier 的六大核心理由、背后的打印算法与"选项冻结"哲学,并给出从 CLI、配置文件到 pre-commit 钩子的完整落地路径。读完本文,你将理解"为什么要用 Prettier"以及"如何低成本地把 Prettier 引入团队与存量代码库"。

先理解核心前提:什么是"固执己见"的格式化器

Prettier 不是那种"你想怎么排版就怎么排版"的万能工具。正如 docs/index.md 所述,它"移除所有原始样式,并确保所有输出代码符合一致的风格",支持 JavaScript(含实验特性)、JSX、Angular、Vue、Flow、TypeScript、CSS/Less/SCSS、HTML、Ember/Handlebars、JSON、GraphQL、Markdown(含 GFM 与 MDX v1)、YAML、Lightning Web Components 与 MJML 等众多语言。

它的工作方式与一般"调整缩进"的工具截然不同:解析代码得到 AST,忽略原有排版,再按自己的规则、以行宽为约束重新打印。例如下面这行代码因为能放进一行而保持不变:

foo(arg1, arg2, arg3, arg4);

而当调用过长、单行放不下时:

foo(reallyLongArg(), omgSoManyParameters(), IShouldRefactorThis(), isThereSeriouslyAnotherOne());

Prettier 会"不辞辛劳"地为你重排成:

foo( reallyLongArg(), omgSoManyParameters(), IShouldRefactorThis(), isThereSeriouslyAnotherOne(), );

这种"解析—重印"模型,正是官方文档中所有"为什么值得采纳"的论证的技术底座:风格由工具一次性、确定性地给出,讨论空间被压缩到最小。那么,团队到底为什么要引入它?官方文档给出了六大理由。

理由一:终结风格之争,建立并强制执行风格指南

官方文档将"采纳 Prettier 的最大理由"归结为一句话:停止没完没了的代码风格争论。普遍公认"团队拥有统一风格指南是有价值的",但达成这一目标的过程痛苦且毫无成就感——人们会为具体的写法动情绪,没人喜欢花时间写下或接收风格 nit(吹毛求疵的修改意见)。

那么,为什么选择"Prettier 风格指南"而不是其他任意一套风格指南?答案是:Prettier 是唯一"全自动"的风格指南。即使它不能 100% 按你的喜好格式化代码,考虑到其独一无二的收益,这种"牺牲"也是值得的。文档记录了当时社区的真实声音:

  • "我们希望解放大脑线程,结束围绕风格的讨论。这类讨论偶尔有产出,但绝大多数时候是浪费。"
  • "真的有一位工程师花了巨大精力清理我们全部代码,因为我们争论三元表达式的写法争论了很久,而且执行得并不一致。这很蠢,但这就是一场持续的'伟大辩论',消耗了大量来回扯皮的时间。现在达成一致容易多了:直接跑 Prettier,跟随它的风格。"
  • "受够了告诉别人怎么排版他们的产品代码。"
  • "我们的首要理由就是停止浪费时间争论风格 nit。"
  • "配好 githook 之后,PR 里因为 ESLint 规则导致的风格问题、以及我需要事后 nit 或清理的东西都变少了。"
  • "我不想再让任何人对任何人吹毛求疵。"
  • "这让我想起史蒂夫·乔布斯每天穿同样的衣服——他有上百万个决策要做,不想被挑衣服这种琐事打扰。我觉得 Prettier 就是这样的存在。"

从仓库源码看,这种"全自动"承诺由 src/main/core.js 中的coreFormat流水线兑现:parseText解析文本 →printAstToDoc将 AST 转为中间表示 Doc →printDocToString最终打印为字符串。用户提交什么排版都无所谓,因为原始样式在解析阶段就被剥离了。

理由二:让新人更快上手

Prettier 通常由对代码库和 JavaScript 有经验的人引入,但从中获益不成比例的人群恰恰是代码库的新人。有人可能以为它只对编程经验很少的人有用,但官方文档指出:经验丰富的工程师刚加入公司时(他们此前可能用着完全不同的编码风格)、以及从其他编程语言转来的开发者,都能明显加快上手速度。文档引用的真实反馈:

  • "我使用 Prettier 的动机是:让自己看起来懂怎么把 JavaScript 写好。"
  • "我总是把空格放错位置,现在我不用再担心这个了。"
  • "初学者会犯大量由语法引起的错误。有了 Prettier,你可以减少这类错误,省下大量时间专注真正重要的事。"
  • "作为老师,我也会让我的学生安装 Prettier,帮助他们学习 JS 语法并拥有可读的文件。"

这一理由呼应了 docs/rationale.md 中"正确性优先"的原则:Prettier 的第一要求是输出与格式化前行为完全一致的合法代码,任何违背该原则的输出都被视为需要修复的 bug。新人不用担心"这样写会不会破坏格式",只需关注语义正确性。

理由三:把心智留给写代码,而不是排版

一旦人们开始使用 Prettier,往往会意识到:过去自己其实花费了大量时间和脑力在手动排版上。配合编辑器集成,只需按下那个"魔法快捷键",代码瞬间被格式化——这是极具冲击力的体验。文档中的声音:

  • "我想写代码,而不是把时间花在排版上。"
  • "它移除了我们日常生活中那 5% 让人不爽的部分——也就是排版。"
  • "都 2017 年了,当你多加一个参数导致调用超过 80 列、还得手动把调用拆成多行时,依然很痛苦。"

编辑器集成之所以能实现"保存即格式化"且不打扰光标位置,源于 src/main/core.js 中formatWithCursor的实现:格式化前先从 AST 定位光标所在的最小区域,格式化时记录该区域被打印到何处,再通过一次仅允许插入与删除的 diff 把光标符号"放回"格式化后的正确位置。这也是为什么"按下快捷键代码就整齐了、光标还不乱跑"这种体验是工程上被认真设计过的。

理由四:风格低争议,易于被团队接受

Prettier 团队在选型上刻意使用了"最不具争议性"的编码风格,经过多轮修复所有边缘情况、打磨上手体验。官方文档承诺:当你准备把 Prettier 推入代码库时,不仅技术层面应该毫无痛苦,新格式化的代码库也不应引发大规模争议,能被同事顺利接受。当时的社区反馈:

  • "开销很低。我们能不费什么功夫就把 Prettier 扔到各种非常不同的仓库里。"
  • "基本没有 bug。如果在实施过程中出现大的风格问题,我们会对把它扔到 JS 代码库上心存顾虑。我很高兴地说并没有。"
  • "每个人都把它跑在 pre-commit 脚本里,我们中还有几个人也用保存时格式化的编辑器扩展。"
  • "它很快,对一个我们较大的 JS 代码库跑 Prettier 只要 13 秒以内。"
  • "对我们来说,Prettier 最大的好处是能一次性格式化整个代码库。"

注意这些引用是文档撰写年代的记录,"13 秒"针对的是当时某个特定仓库,不应理解为普适性能指标;但它佐证了"一次性全量格式化"在设计上的可行性。

理由五:清理存量代码库,快速见效

制定并强制执行一套编码风格是巨大的工程,因此它常常被搁置,最终团队面对的往往是风格混乱的历史代码。这种场景下运行 Prettier 是"快速取胜":几乎不用花时间,代码库就变得统一、易读。文档引述:

  • "看看这些代码吧——我只是想恢复理智。"
  • "我们接手了一个约 2000 个模块的 ES6 代码库,由 20 位开发者历时 18 个月、在全球团队中开发完成。没做多少调研就感觉赢麻了。"

仓库中的 prettier.config.js 展示了 Prettier 项目自身如何"以身作则":它通过overrides**/*.{js,mjs,cjs}指定meriyah解析器、对bin/prettier.cjs关闭尾逗号、对tsconfig.json等 JSON 文件使用jsonc解析器——存量代码库正是通过这类可提交的配置文件获得确定性的统一风格。

理由六:社区势能(历史视角)

文档指出,人们选择 Prettier 时并不只看纯技术因素:谁构建了它、谁在使用它、它在社区中传播得多快,都会产生不小的影响。当时的反馈包括"发布两个月就被几乎所有主流 JS 项目采用""由 React 与 React Native 的同班人马构建"等。

需要说明的是,这些引用保留的是文档撰写年代的社区观感(如"7000 stars""100,000 npm downloads/mo"),属于历史证言而非当前数据。今天判断 Prettier 的地位,更可靠的依据是仓库自身生态:Prettier 拥有成熟的插件体系,本仓库即包含 plugin-hermes、plugin-oxc、plugin-yuku 等多个实验性解析器插件,这本身就是"社区围绕它持续共建"的持续证据。

原理纵深:为什么它能"全自动"打印

要理解"全自动风格指南"为何可行,需要看 Prettier 的打印算法。如 docs/technical-details.md 所述,其打印器以 Wadler 论文《A prettier printer》描述的算法为内核:打印机接收 AST,返回输出的中间表示(IR),再据此生成字符串。关键优势是打印机可以"丈量"IR,判断输出是否放得下一行,放不下就在指定位置换行。

["(", line, arg, line, ")"]为例:它表示"左括号、参数、右括号"的拼接;如果整体放不进一行,打印机就会在标记了line的地方断行。AST 到 Doc 的递归转换由 src/main/ast-to-doc.js 完成,它沿着AstPath栈递归下降,调用插件提供的print()函数生成 Doc。

Doc 是 Prettier 的中间表示,其完整命令族见 commands.md:

  • group:标记一组"尽量放在一行"的内容;放不下时先打破最外层 group,逐层尝试直至全部放下;
  • line/softline/hardline:分别表示"放得下就变空格""放得下就消失""无论如何都换行"三种断行语义;
  • fill:文本排版式换行,只在行尾放不下的地方断开(用于 Markdown 等场景);
  • indent/align/dedent:控制缩进层级与对齐;
  • lineSuffix:实现行尾注释的缓冲,确保注释永远贴在所属行尾而不是被代码挤开;
  • ifBreak/indentIfBreak/conditionalGroup:根据 group 是否断行选择不同内容,其中conditionalGroup因嵌套时会指数级复杂化而被标注为"最后手段"。

ArrayExpression的打印实现就是一个经典例子:

group([ "[", indent([line, join([",", line], path.map(print, "elements"))]), line, "]", ]);

它是包裹着方括号和缩进内容的group:只要任何子表达式被迫断行,整个数组就会随之展开成多行——这正是文档"数组会尽量单行、内含硬换行则必然展开"行为背后的机制。整条链路(解析 → Doc → 字符串)可追溯至 src/main/core.js 中的coreFormat函数,你也可以用仓库 Playground 的doc-explorer特殊 parser 直观观察 IR 的打印过程。

选项哲学:风格争论不该换个形式卷土重来

既然要"终结风格争论",选项越多就越背离这一目标——争论只是从"该用哪种风格"变成"该用哪些 Prettier 选项"。正如 docs/option-philosophy.md 明确宣告的:Prettier 因历史原因保留了少数选项,但不会再增加新选项

文档承认,少数选项有其合理动机:

  • --trailing-comma=es5:在无需转译的大多数环境中使用尾逗号(函数尾逗号 ES2017 才加入);
  • --prose-wrap:兼容各种特性各异的 Markdown 渲染器;
  • --html-whitespace-sensitivity:应对 HTML 糟糕的空白规则;
  • --end-of-line:方便团队把 CRLF 挡在 git 仓库之外;
  • --quote-props:支撑 Google Closure Compiler 的高级用法。

但另一些选项(如--arrow-parens--jsx-single-quote--bracket-same-line--no-bracket-spacing)则被坦承为"历史遗留物",容易在团队中引发无谓的琐碎争论。文档明确:格式化相关的选项请求将不再被接受,请求保留输入格式(如保留换行)本质上也是"变相的新选项",同样不予受理;只有在技术必要性(如兼容性)面前才可能破例。

从 docs/options.md 可以看到这套"克制"的选项集的代表性成员:printWidth(默认 80,表示"希望的理想行长"而非硬性上限,与 ESLintmax-len语义不同)、tabWidth(默认 2)、useTabs(默认 false,且遵循 SmartTabs——缩进用 tab、对齐仍用空格)、semi(默认 true,关闭时仅在可能引发 ASI 失败的行首补分号)、singleQuote(默认 false,且总是选择转义次数最少的引号风格)。它们全部可以通过 CLI 覆盖或 API 配置,但官方强烈建议通过配置文件固化,以便 CLI、编辑器集成与其他工具读取同一套设置。

落地实践:从 CLI 到 pre-commit 钩子

把"为什么要用"落到"怎么用",官方推荐在 docs/configuration.md(配置)、docs/editors.md(编辑器)与 docs/precommit.md(提交前检查)三份文档之间配合推进:

1. 命令行快速验证:直接对文件或 stdin 运行 Prettier,先看--check模式的差异,再用--write落盘;对混合类型目录建议加--ignore-unknown,避免误伤不支持的文件。

2. 配置文件固化选项:在仓库根目录放置prettier.config.js(或.prettierrc),并通过.prettierignore声明无需格式化的路径。本仓库自身的 prettier.config.js 即是一个可复制的模板。

3. 接入 pre-commit 钩子:docs/precommit.md 提供了多种方案:

  • lint-staged(配合 husky):适合与 ESLint、Stylelint 等工具并存,也支持git add --patch部分暂存;npx mrm@2 lint-staged可一键安装配置;
  • pretty-quick(配合 simple-git-hooks):专注于对变更/暂存文件做整文件格式化;
  • git-format-staged:直接作用于 git 对象库中的对象,保证"提交中的变更永远被格式化、未暂存改动绝不会被误暂存、冲突时不覆盖工作区";
  • Lefthook:多语言钩子管理器,可并行跑多种工具的钩子,stage_fixed: true会把 Prettier 改过的文件重新加入暂存;
  • Shell 脚本:手写.git/hooks/pre-commit,用git diff --cached --name-only --diff-filter=ACMR收集暂存文件、交给prettier --ignore-unknown --write,再git add回暂存区。

其中git-format-staged-f 'prettier --ignore-unknown --stdin --stdin-filepath "{}"'展示了 Prettier 通过 stdin 接受内容、配合--stdin-filepath推断解析器的能力,这也是"钩子只格式化暂存文件、不碰未暂存改动"这一强保证得以成立的基础。

结语:一次投入,永久摆脱风格争论

回到官方文档的核心论点:采纳 Prettier 的首要理由是终止无休止的风格争论,而 Prettier 是唯一全自动、因此也是唯一能让"风格指南"零维护成本的方案。它让新人更快融入、让老手把心智留给真正的编码、让存量代码库一次清理到位,并通过克制到"冻结"的选项集避免争论换个形式复活。其底层"解析 AST → 生成 Doc 中间表示 → 按行宽智能断行"的打印算法(见 commands.md 与 src/main/core.js),保证了这一切是全自动且可预测的。

如果你正在为"该不该引入 Prettier"或"如何让团队接受它"发愁,答案很简单:先跑一次--check看看差异,把它写进 pre-commit,然后——停止争论,跟随它的风格。

【免费下载链接】prettierPrettier is an opinionated code formatter.项目地址: https://gitcode.com/gh_mirrors/pr/prettier

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询