Prettier 如何格式化 Markdown 代码块内的 CSS:以 @font-face 与 unicode-range 为例

发布时间:2026/9/19 17:17:37
Prettier 如何格式化 Markdown 代码块内的 CSS:以 @font-face 与 unicode-range 为例 开发工具格式化CLI【免费下载链接】prettierPrettier is an opinionated code formatter.项目地址https://gitcode.com/gh_mirrors/pr/prettier点击查看免费下载本文以 Prettier 仓库中的格式测试用例 mdn-unicode-range.md 为核心深入讲解 Prettier 对 Markdown 代码块code fence内嵌 CSS 的格式化机制从一段排版混乱的font-face规则尤其是unicode-range属性到规范化输出再到背后的语言推断inferParser、内嵌解析embed与围栏重建printCodeFences完整调用链。读完本文你将理解为什么在 Markdown 文档里写 CSS 代码块也能获得与独立.css文件一致的格式化效果并掌握如何在当前仓库中复现、验证这一行为。一、测试用例是什么一份取自 MDN 的真实 CSS 片段mdn-unicode-range.md 是 Prettier 的 Markdown 格式测试语料之一位于tests/format/markdown/code/目录。从目录内大量mdn-*命名如 mdn-auth-api.md、mdn-font-face-1.md、mdn-background-1.md可以推断这些用例均取材自 MDN Web Docs 的真实页面用于验证 Prettier 在真实世界输入下的表现而非仅覆盖理想化的最小样例。该文件本身只有一个带css语言标记的代码块内容是 Montserrat 字体子集声明中的font-face规则media (prefers-reduced-data: no-preference) { font-face { font-family: Montserrat; font-style: normal; font-weight: 400; font-display: swap; /* latin */ src: local(Montserrat Regular), local(Montserrat-Regular), url(fonts/montserrat-regular.woff2) format(woff2); unicode-range: U0000-00FF, U0131, U0152-0153, U02BB-02BC, U02C6, U02DA, U02DC, U2000-206F, U2074, U20AC, U2122, U2191, U2193, U2212, U2215, UFEFF, UFFFD; } }注意这份输入刻意包含大量真实世界的混乱特征font-face {开括号前存在多余空格各属性声明的缩进参差不齐有的 2 空格、有的 4 空格、有的顶格src:声明被手工拆成多行unicode-range的值列表被任意换行且换行位置毫无规律U0131,后直接顶格换行、U02C6,后以 12 空格缩进完全无视 80 列打印宽度。这正是 MDN 等文档站中常见的手工排版代码也是 Prettier 需要意见化opinionated地将其归一的典型场景。二、格式化结果从乱排版到规范输出该用例的期望输出记录在同目录的snapshots/format.test.js.snapmdn-unicode-range.md - {proseWrap:always} format 1条目中。格式化后代码块内部完全按照 Prettier CSS 打印器的规则重排media (prefers-reduced-data: no-preference) { font-face { font-family: Montserrat; font-style: normal; font-weight: 400; font-display: swap; /* latin */ src: local(Montserrat Regular), local(Montserrat-Regular), url(fonts/montserrat-regular.woff2) format(woff2); unicode-range: U0000-00FF, U0131, U0152-0153, U02BB-02BC, U02C6, U02DA, U02DC, U2000-206F, U2074, U20AC, U2122, U2191, U2193, U2212, U2215, UFEFF, UFFFD; } }对比输入可以归纳出四条关键格式化行为声明缩进统一font-style、font-weight等属性统一为 2 空格缩进font-face的{前多余空格被移除嵌套于media内保持层级缩进src列表规范化多个local(...)/url(...)资源按固定节奏缩进对齐unicode-range按 80 列折行unicode-range:值列表在超出printWidth快照头显示printWidth: 80 (default)时首行以冒号结尾后续每个值以 6 空格相对声明缩进再缩进一级续行且在同一行内尽量容纳更多项如U0000-00FF, U0131, U0152-0153, ...排满一行才换行media整体保持媒体查询条件prefers-reduced-data: no-preference原样保留仅统一内部结构。值得强调的是代码块本身围栏、语言标记不受影响——仍是三重反引号加css语言标记变化的只是围栏内部的内容。这正是 Prettier Markdown 打印器把代码块交给对应语言格式化、把围栏保留给 Markdown 层这一分工的直接体现。三、底层机制Markdown 代码块是如何被内嵌格式化的上述行为并非 CSS 打印器对 Markdown 的特殊分支而是 Prettier 的embed机制在起作用。核心实现在 src/language-markdown/embed.jsfunction embed(path, options) { const { node } path; switch (node.type) { case code: { const { isIndented, lang: language } node; if (isIndented || !language) { return; } let parser; if (language angular-ts) { parser inferParser(options, { language: typescript }); } else if (language angular-html) { parser angular; } else { parser inferParser(options, { language }); } if (!parser) { return; } return async (textToDoc) { /* ... */ }; } // ... } }完整调用链如下识别代码块节点Markdown AST 中带语言标记的围栏代码块节点类型为code其lang字段保存语言标识如cssvalue字段保存围栏内的原始文本跳过缩进代码块与无语言代码块isIndented4 空格缩进代码块或没有语言标记的代码块不会被内嵌格式化——这解释了为什么 simple.md 这类无语言代码块仅保持原样语言 → 解析器推断通过inferParser(options, { language })将css映射到 CSS 解析器postcss 系。该函数定义于 src/utilities/infer-parser.js是 Markdown 与各语言解析器之间的翻译层调用内嵌解析器返回的异步函数接收textToDoc把node.value以parser指定的解析器重新解析并打印为文档doc期间继承外层printWidth等格式化选项重建围栏通过printCodeFences(doc, options)根据内容重新计算围栏长度见下节最终markAsRoot将内嵌结果标记为独立根文档输出。值得注意的细节当语言为ts/typescript/tsx时embed.js 还会临时覆盖filepath为dummy.ts/dummy.tsx因为类型参数尾逗号是否打印取决于文件是*.ts还是*.tsxCSS 则无此需求直接复用外层选项。四、围栏重建为什么反引号数量可以动态变化内嵌格式化完成后Markdown 层需要重新输出围栏。这在 src/language-markdown/print/code.js 的printCodeFences中实现function printCodeFences(valueDoc, options) { const styleUnit options.__inJsTemplate ? ~ : ; const value /* 将 doc 序列化为字符串 */; return styleUnit.repeat( Math.max(3, getMaxContinuousCount(value, styleUnit) 1), ); }其逻辑为默认使用反引号在 JS 模板字符串内则改用波浪号~围栏长度取max(3, 内容中连续反引号的最大个数 1)。也就是说如果格式化后的代码内容里恰好出现了三个连续反引号围栏会自动升级为四个反引号避免内容与围栏冲突。getMaxContinuousCount来自 src/utilities/get-max-continuous-count.js用于统计连续字符的最大个数。对于本用例unicode-range值中不含反引号因此围栏保持标准的三重反引号输出结构为围栏 lang hardline 内容 hardline 围栏见printFencedCodeBlock。五、如何复现与验证快照测试与命令行1. 快照测试入口该用例由 format.test.js 驱动runFormatTest(import.meta, [markdown], { proseWrap: always });runFormatTest会遍历同目录下所有输入文件.md以markdown解析器逐项格式化并把输入 输出 选项整体写入snapshots/format.test.js.snap。快照头部还清晰记录了测试上下文parsers: [markdown]、proseWrap: always、printWidth: 80 (default)。proseWrap: always表示 Markdown 正文按打印宽度自动折行但不影响代码块内部的折行——代码块内部的折行由内嵌的 CSS 打印器依据printWidth决定这正是快照中unicode-range列表按 80 列整齐续行的原因同一目录下的 mdn-transform.md 等用例展示了相同机制对其他 CSS 属性如transform: rotate3d(...) matrix3d(...)的作用。2. 手动复现在仓库根目录安装依赖后yarn可以通过 Prettier CLI 直接对任意 Markdown 文件运行格式化观察代码块内 CSS 的归一效果yarn prettier --parser markdown --prose-wrap always path/to/your.md也可以运行整组 Markdown 代码块快照测试yarn jest tests/format/markdown/code/format.test.js3. 测试用例的价值这类mdn-*用例在仓库中承担回归保护角色一旦 Markdown 内嵌格式化或 CSS 打印器行为发生变更快照差异会立即暴露防止unicode-range这类真实写法在升级中退化。它们是验证 embed.js 与 print/code.js 行为稳定性的直接证据。六、小结从 mdn-unicode-range.md 这一个测试用例可以完整还原 Prettier 对Markdown 中的 CSS 代码块的处理全景Markdown 层负责结构围栏、语言标记、代码块在文档中的位置由 print/code.js 管理内嵌层负责内容带语言标记的代码块经 embed.js 交由inferParser推断出的 CSS 解析器格式化继承外层printWidth结果由快照固化通过 format.test.js 与 快照文件 锁定输入输出映射保证行为可回归验证。因此无论你的 Markdown 文档里是font-face的unicode-range长列表还是复杂的transform: matrix3d(...)调用参见 mdn-transform.md 的格式化结果Prettier 都会以与独立 CSS 文件完全一致的标准重排它们——这正是意见化代码格式化器在文档场景下的核心价值所在。赞分享开发工具格式化CLI【免费下载链接】prettierPrettier is an opinionated code formatter.项目地址https://gitcode.com/gh_mirrors/pr/prettier点击查看免费下载相关推荐Prettier 格式化 Markdown 代码块内的 CSS以 env() 与 padding 声明为例Prettier 格式化 Markdown 代码块内的 CSS以 env 与 padding 声明为例 本文以 Prettier 仓库中的真实测试用例 tes开发工具格式化CLIPrettier 如何格式化 Markdown 内嵌 CSS 代码块以 mdn-background-3 测试用例为引Prettier 如何格式化 Markdown 内嵌 CSS 代码块以 mdn background 3 测试用例为引 Markdown 文档中的 CSS 代开发工具格式化CLI思源宋体TTF5个理由让你告别中文排版烦恼的终极方案思源宋体TTF5个理由让你告别中文排版烦恼的终极方案 还在为中文排版设计而烦恼吗面对商业字体高昂的授权费用或者免费字体质量参差不齐的困境你需要的是一款真开发工具格式化CLI创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考