news 2026/9/18 11:37:00

Prettier 编程式 API 详解:从 format 到插件化的完整实践指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Prettier 编程式 API 详解:从 format 到插件化的完整实践指南

Prettier 编程式 API 详解:从 format 到插件化的完整实践指南

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

Prettier 除了命令行之外,还暴露了一套完整的编程式 API,供编辑器插件、CI 工具、自定义格式引擎直接调用。本文基于仓库文档 docs/api.md 展开,完整覆盖formatcheckformatWithCursorresolveConfig等全部公开接口的用法与返回约定,并结合 src/index.js、src/main/core.js、src/config/resolve-config.js 等源码实现,解释每个 API 底层的执行路径,帮助你在自研工具链中正确、高效地集成 Prettier。

一、引入方式与 API 的整体形态

文档开头给出的标准引入方式:

import * as prettier from "prettier";

所有公开 API 均为异步函数,返回Promise。这是 Prettier v3 的重要约定:如果必须使用同步版本,文档推荐借助第三方包装层(如@prettier/sync包)来桥接,而不是在核心 API 中提供同步入口。

从 package.json 的exports字段可以确认模块解析关系:

{ "exports": { ".": { "types": "./src/index.d.ts", "require": "./src/index.cjs", "default": "./src/index.js" }, "./standalone": "./src/standalone.js", "./plugins/*": "./src/plugins/*.js", "./*": "./*" }, "engines": { "node": ">=22" } }
  • import "prettier"对应 src/index.js(ESM)或 src/index.cjs(CJS),完整功能入口;
  • "prettier/standalone"对应 src/standalone.js,面向浏览器/独立运行的裁剪版;
  • "prettier/plugins/*"对应 src/plugins/ 下按语言切分出的内置插件,供外部以插件形式显式加载;
  • 当前开发版本为3.10.0-dev,运行时要求 Node.js>=22

standalone 入口的 API 子集

对比 src/standalone.js 的导出列表:

export { debugApis as __debug, check, format, formatWithCursor, getSupportInfo, }; export * as doc from "./document/public.js"; export { default as version } from "./main/version.evaluate.js"; export * as util from "./utilities/public.js";

可以看到 standalone 版只导出checkformatformatWithCursorgetSupportInfo(外加docutilversion),并不包含resolveConfiggetFileInfo等依赖文件系统搜索的 API——这与其面向浏览器、无法自由搜索配置目录的定位一致。如果你的运行环境是 Node.js,则应使用主入口获取完整 API。

二、prettier.format(source, options)

format是最核心的接口,用于把一段文本格式化为 Prettier 风格。使用约定:

  • options.parser必须按照目标语言显式设置(可用解析器列表见 docs/options.md);
  • 或者改用options.filepath,让 Prettier 根据文件扩展名推断解析器;
  • 其余 options 均可传入以覆盖默认值。

文档示例:

await prettier.format("foo ( );", { semi: false, parser: "babel" }); // -> 'foo()\n'

源码实现

src/index.js 中format并不是独立实现,而是formatWithCursor的薄封装——内部强制把cursorOffset置为-1(表示不追踪光标),取出结果中的formatted字段返回:

const formatWithCursor = withPlugins(core.formatWithCursor); async function format(text, options) { const { formatted } = await formatWithCursor(text, { ...options, cursorOffset: -1, }); return formatted; }

值得注意的是withPlugins包装器(src/index.js):它会在调用真正格式化逻辑之前,把options.plugins中的字符串/URL 路径通过loadBuiltinPlugins()loadPlugins()加载为插件对象并注入选项。也就是说,plugins选项接受插件对象、文件路径或 URL,API 层会自动完成加载

三、prettier.check(source, options)

check用于判断文件是否已经是 Prettier 格式化后的产物,返回Promise<boolean>。它与 CLI 的--check/--list-different参数语义一致,非常适合在 CI 中做格式校验:格式化失败即阻断流水线。

从源码看(src/index.js),它的实现极其简洁——直接执行一次format并与原文全等比较

async function check(text, options) { return (await format(text, options)) === text; }

这意味着checkformat对配置的解读完全一致;若options中缺少parser,同样需要依赖filepath推断。

四、prettier.formatWithCursor(source, options):编辑器集成的关键

formatWithCursor在格式化代码的同时,把未格式化代码中的光标位置映射到格式化后的对应位置。这是编辑器集成的刚需:格式化后光标不能"跳走"。使用它时必须通过cursorOffset选项指明光标所在的字符偏移:

await prettier.formatWithCursor(" 1", { cursorOffset: 2, parser: "babel" }); // -> { formatted: '1;\n', cursorOffset: 1 }

光标迁移算法

底层实现在 src/main/core.js 的formatWithCursor中,注释里完整描述了三步策略:

  1. 格式化前:基于 AST 找出包含光标的最小区域(叶子节点、两节点之间的空隙、或文档首尾);
  2. 格式化中:记录该区域被写到了新文本的什么位置;
  3. 格式化后:把光标当作一个特殊字符(Symbol("cursor"))插入旧区域文本,对新旧区域做仅含插入/删除的 diff(src/main/core.js 使用diff库的diffArrays),从 diff 结果中反推出光标在新文本中的偏移:
const oldCursorNodeCharArray = oldCursorRegionText.split(""); oldCursorNodeCharArray.splice(cursorOffsetRelativeToOldCursorRegionStart, 0, CURSOR); const cursorNodeDiff = diffArrays(oldCursorNodeCharArray, newCursorNodeCharArray); let cursorOffset = newCursorRegionStart; for (const entry of cursorNodeDiff) { if (entry.removed) { if (entry.value.includes(CURSOR)) break; } else { cursorOffset += entry.count; } }

此外还有几个值得了解的行为细节(见 src/main/core.js):

  • BOM 处理:输入若以 BOM 开头会先剥离、格式化后再补回,光标偏移相应调整;
  • 换行归一endOfLine: "auto"时按内容猜测换行符;CRLF输入会先归一为LF参与计算,输出时再转回;
  • Range 格式化:设置了rangeStart/rangeEnd时走formatRange分支(src/main/core.js),只格式化片段并恢复原始缩进,光标在范围外时保持不动;
  • PragmasrequirePragmainsertPragmacheckIgnorePragma均在此层统一裁决,不满足条件时原样返回输入。

五、prettier.resolveConfig(fileUrlOrPath, options)

resolveConfig为某个源文件解析 Prettier 配置:从文件所在目录开始向上搜索配置文件;也可以直接把配置文件路径作为options.config传入以跳过搜索。返回 Promise:

  • 找到配置时,resolve 为一个选项对象;
  • 未找到时,resolve 为null
  • 配置文件解析出错时,Promise 被 reject。

文档给出的典型用法(读取文件 → 解析配置 → 格式化):

const text = await fs.readFile(filePath, "utf8"); const options = await prettier.resolveConfig(filePath); const formatted = await prettier.format(text, { ...options, filepath: filePath, });

参数与底层行为

  • options.useCache:默认true,缓存目录结构以加速重复查询;设为false时完全绕过缓存(见 src/config/resolve-config.js 中options = { useCache: true, ...options })。
  • options.editorconfig:设为true且项目存在.editorconfig时,Prettier 会解析它并转换为对应配置,但优先级低于.prettierrc等 Prettier 配置文件。目前支持的 EditorConfig 属性:
    • end_of_line
    • indent_style
    • indent_size/tab_width
    • max_line_length

实现上,resolveConfig并行加载 Prettier 配置与 EditorConfig(src/config/resolve-config.js 中的Promise.all([loadPrettierConfig(...), loadEditorconfig(...)])),再合并:

const merged = { ...editorConfigured, // EditorConfig 垫底 ...mergeOverrides(result, filePath), // .prettierrc(含 overrides)覆盖 };

其中mergeOverrides(src/config/resolve-config.js)会用micromatch匹配overrides[].files/excludeFiles(基于配置文件所在目录的相对路径),命中则用override.options覆盖基础选项。另外,配置里声明的plugins若为相对路径(以.开头),会被解析为相对于配置文件目录的绝对路径。

六、prettier.resolveConfigFile([fileUrlOrPath])

resolveConfigFile只回答一个问题:最终会使用哪个配置文件。返回 Promise:

  • 找到时 resolve 为配置文件的路径字符串;
  • 未找到时 resolve 为null
  • 解析出错时 reject。

搜索起点是process.cwd();若提供了fileUrlOrPath参数,则从该文件所在目录开始。对应实现见 src/config/resolve-config.js——它始终用shouldCache: false调用searchPrettierConfig,即本 API 自身不参与缓存复用。

const configFile = await prettier.resolveConfigFile(filePath); // you got the path of the configuration file

它与resolveConfig是配套关系:resolveConfigFile给出路径,resolveConfig给出解析后的选项。

七、prettier.clearConfigCache()

当 Prettier 反复读取配置文件与插件时,会为性能缓存文件系统结构(resolveConfig系列默认启用缓存)。clearConfigCache用于主动清空该缓存——典型场景是编辑器集成方已知文件系统在两次格式化之间发生了变化(新增了.prettierrc、改动忽略规则等)。

从主入口的实现看(src/index.js),它实际清空的不仅是配置缓存:

async function clearCache() { clearConfigCache(); // 清 .prettierrc / .editorconfig 的缓存 clearPluginCache(); // 同时清插件加载缓存 }

八、prettier.getFileInfo(fileUrlOrPath, options)

getFileInfo面向编辑器扩展:在真正格式化之前先判断某个文件"要不要格式化、用哪个解析器"。它返回一个 Promise,resolve 为:

{ ignored: boolean; inferredParser: string | null; }

约束与选项:

  • 第一个参数必须是stringURL,否则 Promise 被 reject(见 src/common/get-file-info.js 的TypeError抛出);
  • options.ignorePathstring | URL | (string | URL)[]):指定.prettierignore之类的忽略文件;
  • options.withNodeModulesboolean):是否把node_modules也视为可忽略对象;二者共同影响ignored的取值;
  • 若文件被忽略,inferredParser恒为null
  • options.plugins(string | URL | Plugin)[]):提供插件路径有助于为 Prettier 核心不直接支持的文件类型推断出inferredParser
  • options.resolveConfigboolean,默认true):设为false时跳过配置文件搜索,适合"只关心是否被忽略"的低成本调用。

从源码看(src/common/get-file-info.js),推断解析器的优先级是:显式options.parser→ 配置文件中声明的parser→ 根据插件的语言描述与文件扩展名推断inferParser)。另外源码注释特别提到:本 API 的plugins期望是路径数组,与format等接口的插件加载方式有意区分(涉及 VS Code 扩展的兼容历史)。

九、prettier.getSupportInfo()

getSupportInfo()返回一个 Promise,resolve 为描述 Prettier 当前支持范围的SupportInfo对象:

{ languages: Array<{ name: string; parsers: string[]; group?: string; tmScope?: string; aceMode?: string; codemirrorMode?: string; codemirrorMimeType?: string; aliases?: string[]; extensions?: string[]; filenames?: string[]; linguistLanguageId?: number; vscodeLanguageIds?: string[]; isSupported?(options: { filepath: string }): boolean; }>; options: SupportOption[]; }

options部分的结构定义见 src/index.d.ts 的SupportInfo。)它的典型用途是给编辑器补全 Prettier 选项、为语言动态推荐parser。实现位于 src/main/support.js:从所有插件聚合languages,合并插件options与核心选项定义,并可通过showDeprecated参数决定是否保留已废弃的选项与选项值。

一个来自文档的注意点:Prettier 无法保证filepath在磁盘上真实存在;若通过 API(如prettier.format())使用,连路径是否有效都无法保证——因此依赖isSupported(options: { filepath })这类回调时,调用方需自行兜底。

十、Custom Parser API(已移除)与插件迁移

文档明确标记:Custom Parser API 已在 v3.0.0 中移除,被 Plugin API 取代。在插件出现之前,parser选项可以直接传一个函数:

// ❌ Custom parser API (removed) import { format } from "prettier"; format("lodash ( )", { parser(text, { babel }) { const ast = babel(text); ast.program.body[0].expression.callee.name = "_"; return ast; }, }); // -> "_();\n"

等价迁移到 Plugin API 后,需要定义一个含parsers的插件对象,并在选项里通过plugins显式传入:

// ✔️ Plugin API import { format } from "prettier"; import * as prettierPluginBabel from "prettier/plugins/babel"; const myCustomPlugin = { parsers: { "my-custom-parser": { async parse(text) { const ast = await prettierPluginBabel.parsers.babel.parse(text); ast.program.body[0].expression.callee.name = "_"; return ast; }, astFormat: "estree", }, }, }; await format("lodash ( )", { parser: "my-custom-parser", plugins: [myCustomPlugin], }); // -> "_();\n"

迁移要点:

  • 解析器变成具名函数,通过parser: "my-custom-parser"引用;astFormat声明产物 AST 的格式(此处复用内置的estree打印器);
  • parse可以是asyncparse的入参是(text, options)
  • 插件对象可内联传入plugins数组,也可通过文件路径加载(prettier/plugins/*导出见 package.json 的exports,各语言内置插件见 src/plugins/)。

文档同时警告:用这种方式做 codemod 并不推荐。Prettier 依赖 AST 节点上的位置信息(locStart/locEnd)来保留空行、挂载注释等;在解析后再修改 AST,位置信息很容易与新结构失配,导致不可预测的输出。文档建议:需要 codemod 请考虑专用工具(如 jscodeshift),而不是借道 Prettier 的自定义解析器。

此外,旧的--parser选项允许传入"导出parse函数的模块路径",现已统一改为用--pluginCLI 选项或 API 的plugins选项来加载插件,详见 docs/plugins.md。

十一、API 速查与选型建议

API返回典型场景实现入口
format(source, options)Promise<string>任何"给我格式化结果"的调用src/index.js
check(source, options)Promise<boolean>CI 格式校验(等价--checksrc/index.js
formatWithCursor(source, options)Promise<{formatted, cursorOffset}>编辑器 LSP/扩展集成src/main/core.js
resolveConfig(file, options)Promise<Options \| null>按文件解析配置(含 overrides / EditorConfig)src/config/resolve-config.js
resolveConfigFile([file])Promise<string \| null>只查配置文件路径src/config/resolve-config.js
clearConfigCache()Promise<void>文件系统变化后清理缓存src/index.js
getFileInfo(file, options)Promise<{ignored, inferredParser}>编辑器决定"是否格式化"src/common/get-file-info.js
getSupportInfo([options])Promise<SupportInfo>选项/语言元数据、自动补全src/main/support.js

选型建议(依据上文源码行为归纳):

  • 纯格式化:用format;需要保持光标则换成formatWithCursor并传cursorOffset
  • CI 校验:用check,失败即非零退出;
  • 需要尊重项目配置:先resolveConfig,再与filepath一起传给format;配置文件会热更新时记得在合适时机clearConfigCache
  • 编辑器"要不要管这个文件":先getFileInfo(可设resolveConfig: false提速),ignoredfalseinferredParser非空才进入格式化;
  • 浏览器/无文件系统环境:只能用standalone入口暴露的format/check/formatWithCursor/getSupportInfo子集,配置需要由宿主自行解析后作为options传入。

配套的完整选项定义(RequiredOptionsPluginSupportInfo等类型)可参阅仓库自带的 src/index.d.ts,各选项的取值与默认值说明见 docs/options.md,配置文件的搜索与overrides规则见 docs/configuration.md。

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

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 11:36:55

多模态AI编目系统架构设计与高并发实践

做编目系统做了好几年&#xff0c;早些年一提“编目”&#xff0c;大家默认就是人工给视频、图片、文档打标签、写著录项。一条素材从入库到可检索&#xff0c;少则几分钟&#xff0c;多则一两天。后来接了中启联信时空智影这个项目&#xff0c;才算把AI、多模态处理、高并发这…

作者头像 李华
网站建设 2026/9/18 11:36:04

泛微OA需求问卷:从业务语义建模到系统配置落地

简介&#xff1a;本资源是一份面向企业信息化建设人员、OA系统实施顾问及IT项目管理者的标准化需求调研工具&#xff0c;专为泛微OA系统上线前的需求采集场景设计。文档以结构化问卷形式覆盖员工基础信息、信息门户使用诉求、工作流程审批痛点及知识文档共享习惯四大维度&#…

作者头像 李华
网站建设 2026/9/18 11:35:56

Aider 实战:TaoToken 跑通 pytest 全红仓库的修复

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 11:33:44

基于GitHub API的自动化代码助手OpenClaw设计与实践

1. 项目背景与核心需求作为一名长期与代码打交道的开发者&#xff0c;我经常遇到这样的场景&#xff1a;正在外出差或休假时&#xff0c;突然收到同事消息说项目有个紧急bug需要修复&#xff0c;或者自己突然想到某个功能模块可以优化。传统做法要么是临时找台电脑远程连接&…

作者头像 李华
网站建设 2026/9/18 11:31:51

抖音无水印视频批量下载:douyin-downloader快速上手完整指南

抖音无水印视频批量下载&#xff1a;douyin-downloader快速上手完整指南 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback…

作者头像 李华
网站建设 2026/9/18 11:30:09

算电协同:数据中心算力与电力双向联动的技术路径与落地实践

数据中心的电费单&#xff0c;可能从来没有像今天这样牵动这么多人的神经。不仅仅是运维工程师在盯着PUE&#xff0c;连做算力调度的、做电网规划的、做新能源投资的&#xff0c;甚至搞金融分析的&#xff0c;都在盯着同一组数字&#xff1a;算力用在哪、电从哪来、什么时候用、…

作者头像 李华