TinaCMS MDX 序列化管线解析从 imageCallback 到测试夹具验证markdown-basic-image-json-as-top-level 深度解读【免费下载链接】tinacmsTinaCMS is the leading open-source headless CMS that supports Markdown and Visual Editing. Your content is stored in your own GitHub repo ❤️项目地址: https://gitcode.com/GitHub_Trending/ti/tinacmsTinaCMS 的 MDX 解析器采用「Markdown 文本 → 结构化 JSON ASTPlate/Slate 风格→ Markdown 文本」的双向转换架构本篇文章围绕测试夹具 out.md 展开深入讲解序列化stringify阶段imageCallback的作用机制、调用链路与测试验证方式。读完本文你将掌握 TinaCMS MDX 中图像 URL 的变换与持久化原理理解这类夹具在回归测试中的定位并能在自己的 rich-text 字段上正确接入图像回调。一、测试夹具是什么out.md 的三行内容与它的任务该目录下的 out.md 是 TinaCMS MDX 包中一个测试夹具的「期望输出文件」全文仅三行Image callback should persist 它由两个关键部分组成首行文本Image callback should persist——这是测试用例的自述直接点明本夹具要验证的核心行为图像回调image callback在序列化后应当被持久化保留即回调对图像 URL 的变换结果必须真实写入最终的 Markdown 输出而不是被丢弃或忽略。Markdown 图片语法——这是经过序列化后期望得到的最终 Markdown 图片节点其中/my-pic.jpg是回调处理之后的 URL。也就是说这份out.md不是给人阅读的手册而是 vitest 测试中断言匹配的黄金文件golden file。它属于tinacms/mdx包在next/目录下的 MDX 转换实现测试体系与 index.test.ts、node.json、field.ts 共同组成一个完整的测试用例。二、输入侧拆解node.json 中的图像 AST 节点序列化stringify的输入是一棵结构化的 JSON AST。本夹具的输入 node.json 如下{ type: root, children: [ { type: p, children: [ { type: text, text: Image callback should persist } ] }, { type: img, url: http://some-url/my-pic.jpg, caption: null, children: [ { type: text, text: } ] }, { type: p, children: [ { type: text, text: } ] } ] }几个值得注意的结构事实顶层类型为root子节点包含段落p、图像img与一个内容为空的段落。这正是该用例命名为markdown-basic-image-json-as-top-level的原因——输入直接是顶层 JSON而非先经过 parse 再 stringify 的完整链路从而可以单独、隔离地验证序列化分支。图像节点是块级元素img与p平级位于root.children中其 URL 为http://some-url/my-pic.jpg并带有caption: null。这与 TinaCMS 富文本编辑器基于 Plate/Slate对图像的块级处理方式一致。尾部存在一个空段落它的 children 只有一个text且text为空字符串。这个节点在输出中没有出现因为序列化器会主动忽略空段落详见下文第三节。字段定义 field.ts 声明了序列化所用的富文本字段import { RichTextField } from tinacms/schema-tools; export const field: RichTextField { name: body, type: rich-text, };字段名为body、类型为rich-text它是 TinaCMS 集合collectionschema 中富文本字段的典型写法也是stringifyMDX在遍历 AST 时用于判断上下文例如段落、列表、表格内嵌结构的输入参数。三、测试逻辑stringifyImageCallback 如何把 URL 还原成期望值测试用例 index.test.ts 的完整逻辑import { expect, it } from vitest; import { stringifyMDX } from ../../stringify; import * as util from ../util; import { field } from ./field; import node from ./node.json; it(matches input, () { const stringifyImageCallback (v: string) v.replace(http://some-url, ); // ts-ignore const string stringifyMDX(node, field, stringifyImageCallback); expect(string).toMatchFile(util.mdPath(__dirname)); });关键点逐一解读回调的定义stringifyImageCallback (v: string) v.replace(http://some-url, )接收一个 URL 字符串返回去掉http://some-url前缀后的新字符串。也就是说输入 AST 中的http://some-url/my-pic.jpg经回调处理后应变为/my-pic.jpg。调用方式stringifyMDX(node, field, stringifyImageCallback)接受三个参数——ASTnode.json解析出的 JSON、字段定义field、图像回调第三个参数。回调以第三个参数传入而非散落在 AST 各处。断言方式expect(string).toMatchFile(util.mdPath(__dirname))是 vitest 的快照式断言将序列化结果与同目录下的 out.md 逐字比对。测试通过即证明回调对图像 URL 的变换结果被完整写入了最终 Markdown回调行为持久化成功。对比同目录下的兄弟用例可以看到完整回环。例如 markdown-basic-image-callback/index.test.ts 同时定义了parseImageCallback与stringifyImageCallbackconst parseImageCallback (v: string) http://some-url${v}; const stringifyImageCallback (v: string) v.replace(http://some-url, ); const tree parseMDX(input, field, parseImageCallback); const string stringifyMDX(tree, field, stringifyImageCallback);这展示了完整的双向契约解析parse时回调给相对路径补上http://some-url前缀存入 AST序列化stringify时回调再把前缀去掉还原为 Markdown 中的相对路径。而本用例json-as-top-level跳过了 parse 步骤直接以 JSON 为输入专门验证 stringify 一侧的行为因此是更聚焦的单元级回归测试。四、源码级原理imageCallback 在 stringify 管线中的传递链路序列化入口定义在 stringify/index.tsexport const stringifyMDX ( value: Plate.RootElement, field: RichTextField, imageCallback: (url: string) string ) { if (!value) { return; } const mdTree normalizeMarkWhitespace( preProcess(value, field, imageCallback) ); return toTinaMarkdown(mdTree, field); };整个管线分三步preProcess(value, field, imageCallback)把 Plate/Slate 风格的输入 AST 转换为 mdastMarkdown Abstract Syntax Tree风格的中间树。imageCallback在这一步被逐层传入rootElement→blockElement→eat最终在图像节点处被实际调用。normalizeMarkWhitespace(...)对中间树做空白归一化保证输出的 Markdown 空白格式稳定该实现位于 stringify/mark-whitespace 相关模块。toTinaMarkdown(mdTree, field)将 mdast 中间树最终渲染为 Markdown 字符串实现在 to-markdown.ts。4.1 图像节点的核心处理分支回调真正生效的位置在 pre-processing.ts 的blockElement函数中针对img类型的 casecase img: // Slate editor treats img as a block-level element, wrap // it in an empty paragraph return { type: paragraph, children: [ { type: image, url: imageCallback(content.url), alt: content.alt, title: content.caption, }, ], };这里可以观察到三个实现事实URL 变换点url: imageCallback(content.url)——AST 中图像节点的url字段被传入回调回调返回值作为 mdast 图片节点的新url。本用例中输入http://some-url/my-pic.jpg经replace(http://some-url, )后得到/my-pic.jpg与out.md一致。块级包装注释明确说明 Slate editor treatsimgas a block-level element, wrap it in an empty paragraph——由于编辑器把img视为块级元素序列化时会被包进一个paragraph最终产出 Markdown 的图片语法行。alt 与 caption 的映射alt直接透传而caption被映射为 mdast 图片节点的title字段。node.json中caption: null因此最终输出的 Markdown 图片没有 alt 与 title。4.2 空段落如何被丢弃node.json末尾的空段落p → [text ]未出现在out.md中其依据同样在blockElement的case p分支case p: // Ignore empty blocks if (content.children.length 1) { const onlyChild content.children[0]; if ( onlyChild // Slate text nodes dont get a type property for text nodes (onlyChild.type text || !onlyChild.type) (onlyChild as { text: string }).text ) { return null; } } return { type: paragraph, children: eat(content.children, field, imageCallback), };当段落只有一个子节点、且该子节点是空文本时blockElement返回nullrootElement中的if (value)判断会将其过滤掉。这正是输出中看不到尾部空行的原因也体现了序列化器对编辑器残留空块的容错处理。五、从夹具到实践如何在自己的 rich-text 字段中使用 imageCallback综合以上分析imageCallback是 TinaCMS MDX 序列化 API 的强制第三参数签名为(url: string) string。在实际项目中你可以这样理解与使用回调的定位它负责在 AST 与 Markdown 文本之间转换图像 URL 的形态。典型场景包括去掉/补全域名前缀、统一路径规范如相对路径转绝对路径、针对不同媒体存储如 Cloudinary、S3做 URL 改写。必须保持可逆从兄弟用例可见TinaCMS 的 parse 与 stringify 两侧各自持有回调。若 parse 侧给 URL 加了前缀stringify 侧通常要提供对称的还原逻辑否则会出现「编辑一次后 URL 形态漂移」的问题。本用例json-as-top-level之所以被单独保留正是为了锁定 stringify 侧行为不被破坏。字段定义不可省略field如{ name: body, type: rich-text }参与序列化过程中的上下文判断因此在调用stringifyMDX时需传入与集合 schema 一致的字段定义。回归测试的范式如果你在自己的项目中扩展了 MDX 序列化行为可以仿照本夹具——准备一份最小node.json输入、一个字段定义、一段带断言的回调再用toMatchFile锁定期望输出防止后续改动悄悄改变序列化结果。六、延伸阅读序列化入口与三步管线stringify/index.ts块级元素转换与img/空段落分支pre-processing.tsmdast 中间树转 Markdown 文本to-markdown.ts同主题兄弟用例含 parse 回环markdown-basic-image-callback/index.test.ts、markdown-basic-image-in-link/index.test.ts总而言之这份三行的out.md并非表面看起来的简单文本而是 TinaCMS MDX 序列化管线中图像回调持久化这一关键行为的契约载体它以 JSON 为输入、以回调为变换器、以黄金文件为断言精确锁定了img块级节点从 AST 到 Markdown 的转换语义是理解stringifyMDX内部机制最直接、最精简的入口。【免费下载链接】tinacmsTinaCMS is the leading open-source headless CMS that supports Markdown and Visual Editing. Your content is stored in your own GitHub repo ❤️项目地址: https://gitcode.com/GitHub_Trending/ti/tinacms创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考 SEO 优化官网定制响应式建站教育培训建站