- 开发工具
- 格式化
- CLI
【免费下载链接】prettier
Prettier is an opinionated code formatter.
本文以 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: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } }注意这份输入刻意包含大量"真实世界"的混乱特征:
@font-face {:开括号前存在多余空格;- 各属性声明的缩进参差不齐(有的 2 空格、有的 4 空格、有的顶格);
src:声明被手工拆成多行;unicode-range的值列表被任意换行,且换行位置毫无规律(U+0131,后直接顶格换行、U+02C6,后以 12 空格缩进),完全无视 80 列打印宽度。
这正是 MDN 等文档站中常见的"手工排版"代码,也是 Prettier 需要"意见化"(opinionated)地将其归一的典型场景。
二、格式化结果:从乱排版到规范输出
该用例的期望输出记录在同目录的snapshots/format.test.js.snap(mdn-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: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } }对比输入,可以归纳出四条关键格式化行为:
- 声明缩进统一:
font-style、font-weight等属性统一为 2 空格缩进,@font-face的{前多余空格被移除,嵌套于@media内保持层级缩进; src列表规范化:多个local(...)/url(...)资源按固定节奏缩进对齐;unicode-range按 80 列折行:unicode-range:值列表在超出printWidth(快照头显示printWidth: 80 (default))时,首行以冒号结尾,后续每个值以 6 空格(相对声明缩进再缩进一级)续行,且在同一行内尽量容纳更多项(如U+0000-00FF, U+0131, U+0152-0153, ...排满一行才换行);@media整体保持:媒体查询条件prefers-reduced-data: no-preference原样保留,仅统一内部结构。
值得强调的是:代码块本身(围栏、语言标记)不受影响——仍是三重反引号加css语言标记,变化的只是围栏内部的内容。这正是 Prettier Markdown 打印器"把代码块交给对应语言格式化、把围栏保留给 Markdown 层"这一分工的直接体现。
三、底层机制:Markdown 代码块是如何被"内嵌格式化"的
上述行为并非 CSS 打印器对 Markdown 的特殊分支,而是 Prettier 的embed机制在起作用。核心实现在 src/language-markdown/embed.js:
function 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字段保存语言标识(如css),value字段保存围栏内的原始文本; - 跳过缩进代码块与无语言代码块:
isIndented(4 空格缩进代码块)或没有语言标记的代码块不会被内嵌格式化——这解释了为什么 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还是*.tsx;CSS 则无此需求,直接复用外层选项。
四、围栏重建:为什么反引号数量可以动态变化
内嵌格式化完成后,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
【免费下载链接】prettier
Prettier is an opinionated code formatter.
相关推荐
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思源宋体TTF:5个理由让你告别中文排版烦恼的终极方案
思源宋体TTF:5个理由让你告别中文排版烦恼的终极方案 还在为中文排版设计而烦恼吗?面对商业字体高昂的授权费用,或者免费字体质量参差不齐的困境,你需要的是一款真
开发工具格式化CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考