ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

remark-docz:docz 文档引擎的 Remark 插件深度解析——从图片解析到 JSX 节点合并的完整实现

remark-docz:docz 文档引擎的 Remark 插件深度解析——从图片解析到 JSX 节点合并的完整实现 文档静态站点开发工具【免费下载链接】docz✍ It has never been so easy to document your things!项目地址https://gitcode.com/gh_mirrors/do/docz点击查看免费下载remark-docz 是 docz 文档生成体系内部使用的 remark 插件包core/remark-docz在 Markdown/MDX 源码被编译为 JSX 的过程中负责两项核心工作将 Markdown 图片语法转换为 Webpack 可解析的require()调用以及在 MDX 语法解析不完整时合并组件开闭标签之间的多行内容。本文基于仓库源码与测试用例完整梳理该插件的实现原理、接入方式与版本演进帮助读者理解 docz 是如何把.mdx文档无缝编译为 React 组件的。插件在 docz 架构中的定位remark-docz 是一个内部包internal package只服务于 docz 的编译管线并不面向终端用户直接使用。其包信息定义在 core/remark-docz/package.json包名remark-docz当前版本2.4.0License 为 MIT入口dist/index.jsCommonJS、dist/index.esm.jsES Module类型声明为dist/index.d.ts运行时依赖极小仅包含四个包babel/generator将 Babel AST 重新生成为代码字符串babel/types以编程方式构建 Babel ASTJSX 节点unist-util-visit遍历 Markdown 语法树unist/MDAST节点unist-util-remove从语法树中删除已合并的冗余节点。从源码结构看插件入口 core/remark-docz/src/index.ts 导出的是一个默认函数该函数返回一个标准 remark 插件形态的转换器对传入的语法树执行两步访问visit这正符合 remark 插件“接收tree并原地修改”的约定。编译管线中的真实调用位置remark-docz 被 docz 的 Gatsby 主题以硬编码方式注册为默认 remark 插件。查看 core/gatsby-theme-docz/gatsby-config.js 中的getRemarkPlugins()const getRemarkPlugins () { let plugins [] try { plugins [ [require(remark-frontmatter), { type: yaml, marker: - }], require(remark-docz), ] } catch (err) { plugins [] } return plugins }该插件数组随后通过gatsby-plugin-mdx的remarkPlugins选项注入编译管线同文件 gatsby-config.js。这里有一个值得注意的细节当用户通过doczrc.js配置了自定义mdPlugins时用户插件会排在前面docz 的内置插件含 remark-docz追加在末尾remarkPlugins: config config.mdPlugins ? config.mdPlugins.concat(mdPlugins) : mdPlugins,这意味着 remark-docz 总是在用户自定义插件之后执行从而保证对图片别名、JSX 合并的处理是最后一环不会干扰用户自己的语法转换。功能一将 Markdown 图片节点改写为 JSX可 require 的 srcMDX 语法中alt这样的 Markdown 图片最终会被编译为img。但在 docz 的打包场景下图片路径往往指向源码目录中的资源例如src/components/foo.png必须经过 Webpack 的require()才能被解析、打包并得到正确的 URL。实现分析插件首先对语法树中所有image类型节点发起visitsrc/index.tsvisit(tree, image, (node: any): void { node.type jsx node.value imageToJsx(node) })每个image节点被原地改写为jsx类型节点值由imageToJsx()生成。imageToJsx()使用babel/types构造一个自闭合的 JSXimg元素并通过babel/generator生成代码字符串alt属性直接取节点上的node.altsrc属性交由createImgSrc()决定是直接使用字符串还是包装成require()调用。createImgSrc的路径判定逻辑createImgSrc()src/index.ts是图片处理的核心逻辑分为三档const createImgSrc (src: string) { const parsed url.parse(src) if (parsed.protocol) { return t.stringLiteral(src) } let { pathname } parsed as { pathname: string } if (!/^(?:\.[./]|)/.test(pathname)) { pathname ./${pathname} } return t.jsxExpressionContainer( t.callExpression(t.identifier(require), [t.stringLiteral(pathname)]) ) }带协议的绝对 URLhttps://...、data:等直接作为字符串字面量写入src不经过打包器以./、../或别名开头的相对路径原样保留包装为require(./path)其余裸路径如images/foo.png自动补上前缀./再包装为require(./images/foo.png)。第三种情况对应 CHANGELOG 中记录的一个历史缺陷修复2.0.0-rc.1 中修复了fix alias in the src of a mdxs image对应 issue #897。同样的能力在 1.2.0 版本中以 Feature 形式引入CHANGELOG 记录为resolve markdown imagesissue #851。改写后的src是 JSX 表达式容器包含一个require()调用。Webpack 在处理.mdx模块时能识别这类调用从而把图片加入依赖图输出为经过 hash 命名的静态资源——这正是 docz 站点中文档里直接引用源码目录图片能正常工作的底层原因。功能二合并缺少闭合标签的 JSX 节点MDX 中常出现开闭标签跨多个 Markdown 段落的写法最典型的是docz提供的Playground组件——它以函数作为子节点children-as-function承载可交互示例。例如 core/remark-docz/src/index.test.ts 中的测试输入import { Playground } from docz Playground {() { const foo foo return ( div{foo}/div ) }} /Playground这段内容中Playground与/Playground之间存在空行与多行代码。当 MDX 解析器将其切成多个语法树节点时会得到只有开标签的节点与只有闭标签的节点两者之间夹杂普通文本/代码节点。若不加处理这些节点会被当作孤立 JSX 片段导致编译结果错乱或渲染失败。合并算法详解插件对jsx节点发起第二轮visit并对每个节点调用mergeNodeWithoutCloseTag()src/index.ts。算法分四步第一步提取组件名并构造开/闭标签正则。const component componentName(node.value) // /^\\\?(\w)/ 匹配首个单词 const tagOpen new RegExp(^\\${component}) const tagClose new RegExp(\\\\/${component}\\$)第二步判断节点形态。若节点已同时包含开标签与闭标签或无法识别组件名则直接返回、不做处理这种节点本身就是完整合法的 JSX。第三步向后查找恰好只有闭标签的兄弟节点。通过tree.children.findIndex()找到第一个满足hasJustCloseTag只有闭标签、没有开标签的节点索引同时兼容目标节点自身带子节点children的情况。第四步合并区间内的全部值并删除中间节点。命中后调用valuesFromNodes(tree)(idx, tagCloseIdx)src/index.tsconst valuesFromNodes (tree: any) (first: number, last: number) { const values [] if (first ! last) { for (let i last; i first; i--) { const found tree.children[i] if (found.children found.children.length 0) { values.push(...found.children.map((child: any) child.value)) } if (found.value found.value.length 0) { values.push(found.value) } if (i ! first) remove(tree, found) } } return values }实现细节值得注意循环采用倒序遍历for (let i last; i first; i--)先收集区间内所有文本值再在遍历的同时用unist-util-remove删除中间节点保留开标签节点本身i first最终由调用方执行node.value values.reverse().join(\n)把收集到的内容拼接回开标签节点——倒序收集配合反转保证多行内容的原始顺序不丢失。这一合并后删除冗余节点的模式正是为了避免生成重复、嵌套的错误 JSX。测试与快照验证core/remark-docz/src/index.test.ts 使用mdx-js/mdx真实跑一遍 MDX 编译并断言快照。快照文件 core/remark-docz/src/snapshots/index.test.ts.snap 展示了最终产物中Playground以完整、正确的嵌套结构出现Playground mdxTypePlayground {() { const foo foo; return div{foo}/div; }} /Playground函数子节点被完整保留在组件内部正是 docz 文档中示例代码即渲染代码能力的编译层保障——最终这些内容会交由 core/docz/src/components/Playground.tsx 中的Playground组件消费该组件读取children并将__code等元数据透传给可交互的 Playground 渲染器。与 rehype-docz 的分工docz 的编译管线中remark 阶段与 rehype 阶段由两个独立的内部包承担remark-docz本篇主题作用于 Markdown/MDX 抽象语法树MDAST阶段负责图片→JSX 改写与缺失闭标签节点的合并rehype-doczcore/rehype-docz/src/index.ts作用于 HTML 抽象语法树HAST阶段负责后续的 HTML 节点处理。在 core/gatsby-theme-docz/gatsby-config.js 中两者先后注册remarkPlugins接收remark-frontmatter与remark-doczrehypePlugins接收rehype-docz与rehype-slug。这种先 remark 后 rehype的流水线是 MDX 编译的标准模型remark 先把 Markdown 转成 MDAST 并做语义改写随后再转换为 HAST 交给 rehype 插件进一步加工。remark-docz 的产出jsx类型节点恰好是这两个阶段之间的衔接点。构建、测试与本地开发remark-docz 的工程化配置同样体现了 monorepo 内包的标准做法构建使用docz-rollup提供的统一配置core/remark-docz/rollup.config.js入口为./src/index.ts由yarn build内部执行rollup -c产出dist/目录见 package.json 中的 scripts测试yarn test执行 Jest测试内容直接引用mdx-js/mdx走完整编译链路快照断言输出类型声明core/remark-docz/src/types.d.ts 为未携带类型声明的三个依赖unist-util-visit、unist-util-remove、mdx-js/mdx补充了模块声明保证 TypeScript 编译通过。版本演进脉络从 CHANGELOG 看能力沉淀core/remark-docz/CHANGELOG.md 记录了该插件从 2018 年至今的演进按时间线可归纳为三条主线版本时间关键变更意义0.13.02018-12-17底层由webpack-serve切换到webpack-dev-server间接影响插件打包环境1.0.0-alpha.02019-03-19修复 visit correct nodes接入 Gatsbyissue #630改用自定义 rollup 配置节点遍历与构建基础设施定型1.0.2~1.1.02019-04~05仅版本号 bump随 docz 1.x 版本线发布1.2.02019-05-08Featureresolve markdown imagesissue #851图片解析能力正式落地2.0.0-rc.12019-07-18修复 MDX 图片 src 中的别名问题issue #897bump 版本eslint 与 rollup 配置修正图片别名能力完善2.1.02019-11-27仅版本号 bump随 docz 2.x 发布2.3.3-alpha.0 / 2.4.02021-09-10 / 2022-02-11修复依赖问题issue #1647依赖稳定性收尾从中可以读出清晰的开发策略功能集中在早期版本落地图片解析、节点合并、Gatsby 接入后期版本以依赖维护与版本同步为主——这与其作为内部基础设施包的定位高度一致。小结remark-docz 虽然只有约 110 行核心源码却是 docz 编译管线中不可或缺的一环它把 Markdown 图片无缝接入 Webpack 的模块解析体系并修复了 MDX 解析器对多行 JSX 组件切分不完整的固有缺陷让Playground这类以函数为子节点的组件能够在文档中自由书写。理解它的实现也就理解了 docz 文档中图片直引源码目录与示例代码跨多行书写这两大体验的编译层根基。后续若需深入可继续阅读 core/remark-docz/src/index.ts 的完整实现、core/remark-docz/src/index.test.ts 的测试用例以及它在 core/gatsby-theme-docz/gatsby-config.js 中的接入方式。赞分享文档静态站点开发工具【免费下载链接】docz✍ It has never been so easy to document your things!项目地址https://gitcode.com/gh_mirrors/do/docz点击查看免费下载相关推荐从用户到贡献者Codewars.com开源项目参与路径全解析从用户到贡献者Codewars.com开源项目参与路径全解析 Codewars.com是一个专注于编程技能提升的开源平台其Issue tracker为开发者文档静态站点开发工具BDD测试框架完整指南awesome-testing中Cucumber与Behave的对比教程BDD测试框架完整指南awesome testing中Cucumber与Behave的对比教程 想要掌握行为驱动开发BDD测试框架吗本文将为您提供终极指Python调试革命Better Exceptions如何重塑异常处理体验Python调试革命Better Exceptions如何重塑异常处理体验 在Python开发实践中异常调试是每个开发者必须面对的挑战。传统的Python异开发工具调试器上一篇终极指南SwaggerUI配置同步全解析 - 从核心设置到React组件的无缝衔接下一篇5分钟搞定API测试Swagger UI操作验证完全指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表