ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Prettier 语言支持底层机制与配置插件详解:从解析器到工程落地

Prettier 语言支持底层机制与配置插件详解:从解析器到工程落地 前阵子帮一个团队搭前端工程化环境代码仓库里塞了 Vue 2、Vue 3、还有几个老掉牙的 .php 文件。我把 Prettier 装好、跑了npx prettier --write .结果发现有的文件被格式得整整齐齐有的文件纹丝不动还有的直接报SyntaxError。排查了半天才意识到Prettier 的语言支持并不是“装了就能格式化一切”它有一套自己的解析器机制、插件体系和支持边界。如果你也遇到过“Prettier 明明装了怎么这个文件就是不理我”或者“为什么同一个配置JS 对了但 Markdown 没反应”这类问题那这篇就是为你写的。我会从 Prettier 的语言支持底层逻辑讲起把常见语言的使用体验、配置文件里跟语言相关的关键项、插件扩展方式以及我踩过的坑一次性说清楚。内容适合所有用过 Prettier 但没深究过“它到底怎么判断该用什么规则”的开发者。1. 从一次“文件没被格式化”说起Prettier 语言支持的底层机制1.1 Prettier 的工作方式解析器加打印机两步走很多人对 Prettier 的理解是“一个会把代码变好看的格式化工具”但对它内部机制并不清楚。Prettier 处理一个文件其实分两步先用解析器parser把源码读成抽象语法树AST再用自己的打印机printer把 AST 按固定规则重新输出成文本。理解这一步你就明白为什么 Prettier 对语言的“支持”会有差别。它不像正则替换那样看几个关键字就粗暴格式化而是要求每个语言都有一个能完整解析该语言语法的解析器。AST 解析得越完整格式化就越准确解析器覆盖不了的语法直接就会报SyntaxError。所以 Prettier 支持的语言列表背后是它内置了对应语言的解析器实现。这也是为什么 Prettier 虽然支持 PHP、Ruby、Swift 这些语言但体验上和 JavaScript 差得很远。JS 系语法它从诞生起就在打磨而冷门语言只是接入了社区或官方解析器格式化和 AST 还原的精度都受限于解析器本身。1.2 Prettier 开箱即用的语言支持名单Prettier 官网维护了一份官方支持语言列表这里我按类别给你盘一下类别语言默认解析器名称JavaScript 系JavaScript、JSX、TypeScript、TSX、Flowbabel、flow、typescript、espree样式系列CSS、Less、SCSScss、less、scss标记语言HTML、XML、Vue、Angularhtml、vue、angular数据配置JSON、JSON5、JSONC、YAML、TOMLjson、json5、yaml、toml文档类Markdown、MDXmarkdown、mdx接口协议GraphQLgraphql服务端语言PHP、Ruby、Swift、Java、C/C、C#、Go、Rust对应语言解析器脚本类Shellsh/bash、PowerShellsh、powershell这个名单已经相当广了但要注意“支持”不等于“效果完美”。以我的经验日常前端项目里最值得放心用的是 JS/TS/CSS/HTML/JSON/Markdown 这几类因为它们在 Prettier 内部被维护得最勤、测试最多。PHP、Ruby、Java 这些能用但遇到边界语法时相对容易出问题。1.3 支持程度分三档亲儿子、干儿子、野生插件我把 Prettier 的语言支持粗浅分成三档方便你建立预期第一档是核心语言也就是 JavaScript、TypeScript、JSX、CSS、HTML、JSON、Markdown。这些语言属于 Prettier 自己的核心测试覆盖范围版本迭代时会有专门的快照测试保证格式输出稳定。第二档是“内置但较独立”的语言像 PHP、Ruby、Swift、Java、C 等。它们确实在 Prettier 仓库里有实现但很多维护工作并不活跃可能停留在语法子集。比如早期版本的 PHP 解析器不支持某些新特性遇到复杂 attribute 或 match 表达式时容易翻车。第三档是需要插件支持的语言比如 SQL、Svelte、Astro、Solidity 等。这属于 Prettier 官方的插件生态或者社区插件。后面我会专门讲插件怎么选。你只有知道文件落在哪一档才能判断出了问题该升级 Prettier、装插件还是换工具。2. 逐个语言盘一遍哪些该放心用哪些要留个心眼2.1 JavaScript 和 TypeScript最亲的“亲儿子”JavaScript 系是 Prettier 的立身之本所以它对 JS 的支持最全面。你不需要手动指定 parserPrettier 会根据文件扩展名自动选.js、.mjs、.cjs用babel.ts、.tsx用typescript.jsx用babel且开 JSX 模式。平时项目里常见的箭头函数、可选链、空值合并、装饰器、class 私有字段它基本都能解析。关于 JS 系我提醒三个点第一TypeScript 的解析器typescript虽然底层用的是 TypeScript 编译器自己的 API但它毕竟不是 Prettier 自己写的编译器。遇到极新的 TS 语法比如刚发布的 beta 版特性Prettier 还没来得及适配时也会报错。这时候不要怀疑自己的代码有问题先查 Prettier 版本升到最新。第二babel解析器的容错能力比typescript强。同一个文件如果既有 JS 又有 Flow 类型注释你用babel解析能过用typescript解析可能挂。所以有些项目会用overrides把.js文件强制指定成babel为了兼容 Flow。第三JSX 属性和 HTML 属性格式化逻辑不同。Prettier 对 JSX 标签有单独的规则比如bracketSameLine只影响 JSXsingleAttributePerLine则在 HTML、Vue、JSX 下分别生效。这些配置项我会在第 3 节细讲。2.2 CSS、SCSS、Less格式化效果被低估很多人只把 Prettier 当 JS 格式化工具忽略了它对样式文件的支持其实很成熟。CSS 系解析器统一叫cssSCSS 和 Less 各有独立解析器。Prettier 对样式文件的处理不仅是换行缩进它会把属性排序、引号、空格、数值单位等全部统一规范。一个我自己常用的配置是搭配stylelintPrettier 负责格式Stylelint 负责规则检查。只要 Stylelint 里关掉indentation、string-quotes这类跟 Prettier 职责重叠的规则就不会打架。我在第 5.4 节会继续展开。样式文件上有个容易踩的坑CSS 里的自定义属性CSS Variables内联值Prettier 不会帮你做语义分析它只看语法。所以calc(var(--x) 1px)这种写法它只会规整空格不会帮你优化表达式这符合“格式化而非修复”的定位。另外 SCSS 的嵌套写法、include混入这些Prettier 都认得但有个细节SCSS 里的某些插值语法例如#{$variable}在旧版本解析器里偶发断行异常。我建议使用 SCSS 的项目把 Prettier 固定在 3.0 以上版本稳定很多。2.3 HTML、Vue、Angular模板语法和空白敏感性HTML 系是另一个日常高频场景。Prettier 对 HTML 的处理不仅涉及标签缩进还会管标签之间的空白、属性换行、class属性引号等。这里有一个核心概念叫htmlWhitespaceSensitivity它决定 Prettier 在 HTML / Vue / Angular 里对空白的处理策略。默认值是css意思是“如果空白对 CSS 渲染有影响就保留不影响就删掉”。但实际项目中你经常发现页面里多个空格被压缩了视觉上不对那是因为你用了 inline 布局或white-space: pre。这时候就得把htmlWhitespaceSensitivity改成strict或ignore。后面我会专门讲这个配置它几乎是 HTML 系列必备的调优项。Vue 单文件组件的格式化是靠vue解析器把 template、script、style 三块拆开再分别调用相应解析器处理。所以 Vue 文件里如果写了script setup langtsPrettier 内部会自动用 TS 解析器去处理脚本块。体验上很顺。Angular 的模板语法因为没有 Vue 那么模板友好Prettier 对 Angular 模板的支持略弱。特别是重度使用自定义 structural directive 和复杂表达式的时候偶发无法解析。遇到这种我通常的做法是核心模板用 Prettier个别复杂指令区域用!-- prettier-ignore --注释跳过。2.4 Markdown 和 MDX文档格式化的隐藏主力Markdown 被很多人忽略但它其实是 Prettier 里收益极高的一个语言。Prettier 会统一 Markdown 的标题层级、列表符号、代码块换行、表格对齐、链接格式等。即使你只是写 README跑一遍 Prettier 也能让文档排版瞬间干净。Markdown 格式化有两个重要配置proseWrap和embeddedLanguageFormatting。proseWrap决定正文段落是否按printWidth折行默认是preserve不折行。如果你希望 README 每行都控制在 80 字符就改成always。但说实话我通常不开always因为对中文文档来说折行反而让 diff 变乱开着proseWrap: preserve就够。embeddedLanguageFormatting控制 Markdown 里嵌入式代码块是否也格式化。默认auto即识别到代码块语言就格式化如果某个代码块不想被格式化可以写js prettier-ignore。这在文档里贴输出样例时特别有用。MDX 是 Markdown 和 JSX 的混合体Prettier 的mdx解析器会同时处理文档和组件语法。不过 MDX 对复杂表达式支持也有边界比如顶级export里的某些复杂逻辑偶发解析失败。没事prettier-ignore注释同样适用于 MDX 块级内容。2.5 YAML、JSON、GraphQL数据类格式的细节JSON 和 YAML 看起来简单实际上也有不少细节。JSON 格式化最大的作用是统一缩进和引号但注意 Prettier 不会给 JSON 加注释JSONC 才会也不会自动补全缺失的逗号。如果项目里的.json文件带注释你要么用.jsonc后缀要么在overrides里指定parser: jsonc。YAML 的格式化相对保守Prettier 主要处理缩进、引号、锚点换行等但不会修改你的键顺序。yaml解析器对tab键缩进非常敏感我遇到过因为 YAML 里混入 tab 导致 Prettier 直接报invalid indentation的情况。所以如果你的项目有.yaml文件建议全局排除 tab统一空格。GraphQL 解析器用于.graphql、.gql文件。它会把 query 里的字段按字母序重新排列吗不会。Prettier 保持你写的字段顺序只做换行和缩进规范化。想自动排序字段得靠 ESLint 插件或者graphql-sort配合。3. Prettier 配置项里跟语言相关的关键设置3.1 如何判断和指定 parser自动推断与手动覆盖Prettier 判断文件用什么解析器主要看扩展名。.js对应babel.ts对应typescript.css对应css以此类推。这套推断规则在绝大多数场景下没问题但总有例外文件扩展名很怪、同一文件里混用多种语法、或者你想用某个解析器处理非标准后缀这时候就要手动干预。在.prettierrc文件里直接加{ parser: typescript }这种全局手动指定的方式极少见因为会让所有文件都走同一个解析器。更合理的是用overrides针对特定目录或文件名指定{ overrides: [ { files: *.component.ts, options: { parser: babel-ts } } ] }顺带一提Prettier 官方推荐在新写法里用配置文件导出一个对象。我个人的建议是不要全局指定parser尽量依赖自动推断只在非标准场景用overrides。3.2 overrides混合技术栈项目里最实用的手段overrides是 Prettier 里最贴近“语言支持”的配置入口。它允许你按照 glob 规则匹配文件然后单独应用一组选项。在同一个项目里要同时格式化前端和后端代码这个功能必不可少。举个例子一个项目同时有.js、.php、.vue、.md文件而你的printWidth全局设 80但 PHP 团队希望一行 120 字符Markdown 文档希望不折行。可以这样写{ printWidth: 80, overrides: [ { files: *.php, options: { printWidth: 120 } }, { files: *.md, options: { proseWrap: preserve } }, { files: [*.vue, *.html], options: { htmlWhitespaceSensitivity: ignore } } ] }overrides里的files模式用的是 micromatch glob支持**、{}、!等。你甚至可以对同一文件多次覆盖后面规则在前面的基础上叠加。这里要注意overrides只影响配置选项不影响 Prettier 的解析器选择。解析器选择由扩展名推断和parser字段共同决定而parser也支持写进overrides里。如果你有一个.custom后缀但内容其实是 JSON 的文件就可以在 overrides 里写parser: json强制解析。3.3 不同语言共同关注的全局配置项Prettier 里有几个全局配置看起来对所有语言生效但实际行为有差异。我把常用的几个和它们在不同语言中的表现列出来配置项JS/TSCSS/SCSSHTML/VueMarkdownprintWidth影响一切换行点影响选择器和值换行影响标签属性和文本换行仅在proseWrap: always时影响正文tabWidth缩进缩进缩进if 代码块缩进useTabs缩进用 tab同左同左同上semi控制分号无影响无影响无影响singleQuoteJS 字符串 / JSX 属性CSS 无影响用双引号会更稳HTML 无影响无影响bracketSameLine仅 JSX无影响无影响无影响singleAttributePerLineJSX 标签无影响HTML 标签无影响htmlWhitespaceSensitivity无影响无影响HTML/Vue/Angular无影响proseWrap无影响无影响无影响Markdown/MDX这张表特别重要因为很多人把singleQuote: true配好就以为全局单引号统一了结果 CSS 或 HTML 里没生效就觉得是 bug。其实不是 bug是 Prettier 语言边界划分得清楚JS 选项只对 JS 语言生效样式和模板另有规则。CSS 文件里的引号策略其实是固定使用双引号这个由 Prettier 内部决定不受singleQuote控制。HTML 属性引号默认也是双引号并且不跟随singleQuote变化。这些容易被误解所以我建议团队内部把这些“失效”项提前写进文档里。4. 插件体系官方不直接支持的语言怎么补齐4.1 Prettier 插件机制是怎么运作的Prettier 的扩展能力建立在插件机制上。一个插件本质上是一个parse和print的实现Prettier 只是提供了调度框架。也就是说插件要自备解析器把一个语言源码转成 AST再交给 Prettier 的核心打印逻辑。对使用者来说安装插件只需要两步先用包管理器安装再在配置文件的plugins字段里声明。比如npm i -D prettier-plugin-tailwindcss{ plugins: [prettier-plugin-tailwindcss] }plugins字段支持包名、绝对路径或者内联对象。Prettier 3.0 之后插件机制变成了默认方式不少内置语言也逐步插件化。所以你在配置文件里看到一堆插件不需要慌这是 Prettier 演进的方向。有一点要提醒插件顺序会影响解析器冲突时的胜负。多个插件同时声明能处理同一扩展名时Prettier 按配置里plugins数组的顺序决定优先级。如果你把prettier-plugin-tailwindcss放在最后可能它的 class 排序逻辑会被前面的插件覆盖掉。遇到插件不生效先查顺序。4.2 常用语言插件的安装与配置经验官方维护的第二档语言插件基本都以prettier/plugin-xxx命名。比如 PHP 用prettier/plugin-phpRuby 用prettier/plugin-rubyXML 用prettier/plugin-xml。安装这些以后对应的语言文件才能被 Prettier 正常格式化。不过这些插件大多不是实时同步最新版本遇到新的语法糖解析不了是常态。社区活跃插件又是另一批prettier-plugin-svelte用于 Svelte 组件prettier-plugin-astro用于 Astro 文件prettier-plugin-sql用于 SQLprettier-plugin-java用于 Java虽然现在 Java 已内置prettier-plugin-organize-imports或trivago/prettier-plugin-sort-imports用于 import 排序。拿 SQL 举例装上prettier-plugin-sql后plugins字段加它再针对.sql文件设置parser: sql。SQL 格式化里有个重要配置keywordCase和dataTypeCase分别控制关键字和数据类型的大小写。比如{ plugins: [prettier-plugin-sql], overrides: [ { files: *.sql, options: { parser: sql, keywordCase: upper, dataTypeCase: upper, language: postgresql } } ] }社区插件的维护质量参差不齐装之前建议去 npm 页面看最后发布时间和维护活跃度。我的标准是三个月内没更新、下载量又低的插件不放进正式项目。4.3 插件与 Prettier 版本兼容的大坑插件机制最痛苦的问题就是版本兼容。Prettier 3.x 对插件 API 做了调整一些遗留插件当初为 Prettier 2.x 写的装上去要么直接报错要么静默失效。如果你在运行prettier --write .时看到类似Cannot find package prettier-plugin-xxx的错误第一步不是重装而是确认 plugin 和 Prettier 的主版本是否匹配。通常插件 README 里会写支持哪个主版本。项目升级 Prettier 主版本之前先把所有插件查一遍兼容性。选择插件的另一个原则能不用就不用优先等官方内置。像 Svelte、Astro 这类文件格式如果你非要用 Prettier插件基本绕不开但如果你只是需要格式化少量 SQL可能用 IDE 自带功能更省事。这些没有对错关键是别让插件引入的复杂度高于它带来的收益。5. 常见问题与排查实录5.1 文件没有被格式化可能不是配置问题最常收到的反馈是“我跑了 Prettier这个文件没动”。绝大多数时候不是 Prettier 不识别而是你的增量检查命令里忽略了该文件。常见原因有三个。第一个是.prettierignore里写入了匹配规则。比如有些人全局忽略了*.min.*、dist、build却不小心把目录写成了**/static/**导致某个要格式化的文件也被忽略。排查方法很简单命令行执行npx prettier --check 文件路径看它是否跳过。第二个是文件不在 CLI 爬取范围内。prettier --write .只会处理项目目录下的文件并且受.gitignore影响Prettier 默认忽略.gitignore里的路径。如果你把某个文件加进了.gitignorePrettier 默认也看不见它。想强行格式化用--with-node-modules不行得显式指定文件路径。第三个是文件扩展名官方根本不认识。以前端举例.njkNunjucks、.hbsHandlebars这俩模板引擎默认不在支持列表。不在列表 不格式化不会报错。解决办法就是插件或者放弃格式化。遇到“文件没被格式化”别急着怪 Prettier先用--debug-check或--check定位。5.2 无法解析的语法与“prettier-ignore”的正确用法Prettier 的报错SyntaxError: Unexpected token通常意味着你的代码里出现了当前解析器不认识的语法。这种情况最常见的诱因是Prettier 版本太老不支持新语法或者文件被错误匹配到了解析器。比如.js文件里用了script langts的模板语法而 Prettier 用babel解析导致了错误。你是可以写// ts-check这种注释误导 Babel 的最好的办法是分开处理或者用overrides指定文件类型。还有一种情况是某个第三方 DSL 嵌在模板字符串或者注释里Prettier 根本不关心这些内容的格式化。如果你希望保留某段内容原样就在这段代码前一行写上// prettier-ignore// prettier-ignore const uglyObject { a: 1,b :2 }加了prettier-ignore之后Prettier 会跳过下一节点结构。对 HTML 来说是!-- prettier-ignore --对 Markdown 是!-- prettier-ignore --放在段落前对 CSS 是/* prettier-ignore */注释。它不止能跳过一行也能跳过下一个语法块。我的经验是prettier-ignore是救命稻草但不能滥用。项目里如果到处都是 ignore说明格式化规则和代码实际形态分歧太大应该先调整配置而不是逐个屏蔽。5.3 编辑器里保存自动格式化不生效的排查清单大多数人是通过 VS Code 的 Prettier 插件来用格式化功能的。我遇到过好多次“CLI 没问题但编辑器保存不格式化”的情况原因通常是这几个VS Code 默认格式化器不一定是 Prettier。需要手动打开设置在“Default Formatter”里选择 Prettier。如果安装过多个格式化扩展右键“Format Document With”时更要注意。编辑器插件自动读取的是项目根目录的配置文件如果你把.prettierrc放到了子目录或者用了意外命名比如.prettierrc.yml内容写错格式插件会静默回退到默认配置。建议统一使用.prettierrc.json或prettier.config.js并且放在项目根目录。还有一个经常被忽略的点VS Code 插件默认只对已识别的语言启用。如果你打开的文件类型没被插件列入“启用”列表保存格式化就不生效。打开设置搜索prettier.enableLanguages或者干脆把editor.formatOnSave和prettier.requireConfig都设好再手动触发一次格式化验证。5.4 与 ESLint 和 Stylelint 的边界谁该管什么Prettier 只做格式不做 lint。ESLint 会管潜在 bug、未定义变量、禁止的 APIStylelint 会管样式规则、选择器合法性等。两者分工如果不清就会出现“Prettier 格式化完ESLint 又报格式错”的内耗。业界通用的做法是安装eslint-config-prettier和stylelint-config-prettier把 eslint/prettier 重叠的规则关掉。注意eslint-config-prettier是“关闭那些与 Prettier 冲突的规则”不是“Prettier 的 ESLint 规则”。另外尚未内置的 TS lint 版本有专门的 eslint-plugin-prettier但我不推荐在正式项目里用因为它的性能比eslint --fix差很多还会让责任边界更模糊。ESLint 这边最核心的执行顺序是先跑 Prettier 格式化再跑 ESLint 的规则检查。如果反过来ESLint 可能会因为代码格式问题报错然后再格式化一遍产生多余 diff。在 CI 流程里我的建议是把prettier --check .放在eslint .前面格式挂了先失败再查 lint 逻辑。Stylelint 的边界更明显Prettier 管缩进换行引号Stylelint 管属性值合法性、命名规则、Selector 嵌套限制。两者不冲突的前提是 stylelint 配置里关掉所有indentation、string-quotes相关规则。如果你用的是stylelint-config-standard它已经在兼容 Prettier 的方向做了调整但还不完全够建议仍然安装stylelint-config-prettier。5.5 升级 Prettier 主版本后格式变化怎么处理Prettier 一直有“版本升级后同一份代码格式化结果不同”的问题这是故意的——每个主版本都会调整一部分格式决策。团队如果哪天升级 Prettier 主版本最稳妥的流程是先在一个分支升级跑全量prettier --write .查看 diff 有多少再决定是否接受。这个 diff 有可能很大别慌。一般先合并格式化改动再合并功能改动避免功能 diff 和格式 diff 混在一起导致 code review 困难。我在实际项目里踩过的坑是有人直接在主分支升级后全量格式化瞬间几百个文件 change后面团队 code review 根本无法定位逻辑改动。另外因为 Prettier 3 不再支持 Node 14 以下的版本升级前还要检查 CI 容器和团队成员本地的 Node 版本。有一个冷门但常见的报错ERR_REQUIRE_ESM一般是 Prettier 3 配旧式prettier.config.js且项目本身是 CommonJS 导致的。这时候把配置文件改成.prettierrc.json就能解决。实操总结如何快速判断语言支持并落地配置很多五六个人的前端团队Prettier 配置其实就一行{}其他全靠默认。这么做也不至于出错但语言支持的收益没吃满。我的建议是按项目实际技术栈把语言分两组一组是“核心格式语言”直接手动确认配置JS/TS 走babel或typescript、CSS/SCSS、HTML/Vue、JSON、Markdown。一组是“扩展格式语言”先看官方是否内置再决定是否要插件PHP、Ruby、SQL、Svelte、Astro按需加载。然后建一份.prettierrc.json至少写清printWidth、singleQuote、semi、trailingComma再用overrides把不统一的语言单独拆分。最后套上一个prettier --check的 CI 阶段问题就能在合并前暴露。我个人在实际使用中还发现一个不成文的判断标准如果某个语言在 Prettier 格式化后你还得手动再去调位置说明这个语言的支持度并不足以让你省心。这时候宁可不管它也不要在prettier-ignore里层层打补丁。格式化工具的底线是“可预测、可复现”如果你维护的项目里充满了例外规则那还不如直接在.prettierignore里把这个语言整个排除让 Prettier 把精力放在真正擅长的地方。
返回列表