ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Angular 文档流水线中的 docs-card 自定义 Markdown 扩展:从标记语法到 HTML 渲染全解析

Angular 文档流水线中的 docs-card 自定义 Markdown 扩展:从标记语法到 HTML 渲染全解析 Angular 文档流水线中的 docs-card 自定义 Markdown 扩展从标记语法到 HTML 渲染全解析【免费下载链接】angularDeliver web apps with confidence 项目地址: https://gitcode.com/GitHub_Trending/an/angular本文以 Angular 仓库中 adev开发者文档站点构建流水线内的测试夹具文档docs-card.md为主体完整拆解 Angular 官方文档站点如何基于 marked 扩展机制实现docs-card自定义块级标签包括标签属性语法、tokenizer 正则解析、渲染分支、防嵌套链接机制与测试验证方式。读完后你可以理解 adev 文档流水线中Markdown → 自定义 HTML 组件化结构的完整链路并掌握在同类文档系统中扩展自定义块级标记的方法。原始文档内容docs-card 的四种标准形态本文的主体文档位于 docs-card.md。它本身是 marked 扩展的测试夹具test fixture共 8 行却恰好覆盖了docs-card扩展的全部四种典型用法。原文完整内容如下docs-card titleNo Link CardCard Content/docs-card docs-card titleLink Card linkTry It Now hrefin/app/link Card Content with a symbol: CommonModule /docs-card docs-card titleImage Card imgSrc./angular.svg/docs-card docs-card title linkOpen on Playground href/playground The fastest way to play with an Angular app. No setup required. /docs-card逐条拆解这四种形态它们分别对应渲染器里的不同代码分支形态属性组合说明无链接卡片仅title纯展示卡片渲染为普通div classdocs-card不产生a标签链接卡片titlelinkhref整个卡片变为可点击的a卡片尾部显示link文案此处为 Try It Now卡片正文中还嵌入了行内代码CommonModule图片卡片titleimgSrc卡片头部内联一张 SVG 插图此处为同目录下的angular.svg无卡片正文空标题卡片titlelinkhref标题为空字符串时不生成h3仅渲染正文与链接文案指向/playground这个夹具文档虽然只有 8 行但它就是docs-card扩展的行为契约每一种写法都严格对应一个自动化测试用例后文详述。扩展注册docs-card 如何接入 marked 解析链adev 流水线中所有自定义文档标记都注册在 parse.mts 的扩展列表里。docsCardExtension与docsCardContainerExtension被加入 marked 的extensions数组并随marked.use({extensions, walkTokens})统一启用const extensions [ docsImageExtension, docsAlertExtension, // ... docsCardExtension, docsCardContainerExtension, // ... ]; export function parseMarkdown(markdownContent: string, context: PartialRendererContext): string { validatePairedTags(markdownContent, context.markdownFilePath); markedInstance ?? marked.use({extensions, walkTokens}); return markedInstance.parse(markdownContent, {renderer: new AdevDocsRenderer(context)}) as string; }值得注意的两个细节level: block在 docs-card.mts 中docsCardExtension声明level: block as const即它作为块级标记参与解析。这也解释了为什么夹具文档中的docs-card必须独占块级位置前后留空行且卡片内部正文会被单独再次按块级 token 解析。配对标签校验解析入口先执行validatePairedTags(markdownContent, context.markdownFilePath)来自 validate-paired-tags.mts在解析阶段就保证docs-card与/docs-card成对出现避免运行时才暴露书写错误。Tokenizer属性如何从开标签中被正则提取扩展的核心解析逻辑定义在 docs-card.mts。tokenizer 首先用一个正则整体匹配卡片// Capture group 1: all attributes on the opening tag // Capture group 2: all content between the open and close tags const cardRule /^\s*docs-card(?:\s([^]*))?((?:.(?!\/docs-card))*)\/docs-card/s;这个正则有两个关键设计捕获组 1([^]*)抓取开标签上的全部属性串title... link... href...等捕获组 2 使用否定前瞻(?!\/docs-card)逐字符消费内容直到遇到/docs-card配合/s修饰符使.可以跨行匹配——这正是夹具文档中第二、四个卡片能跨行书写正文的原因。随后每个属性都由独立的子正则从属性串中逐项提取const titleRule /title([^]*)/; const linkRule /link([^]*)/; const hrefRule /href([^]*)/; const imgSrcRule /imgSrc([^]*)/; const iconImgSrcRule /iconImgSrc([^]*)/; const titleInlineRule /(?:^|\s)titleInline(?\s|$|)/;对应地DocsCardToken结构承载解析结果interface DocsCardToken extends Tokens.Generic { type: docs-card; title: string; body: string; link?: string; href?: string; imgSrc?: string; iconImgSrc?: string; // Need image since icons are custom titleInline?: boolean; tokens: Token[]; }由此可以整理出docs-card的完整属性表比原始文档仅展示的title/link/href/imgSrc更完整属性类型作用是否影响标签结构title字符串卡片标题渲染为h3为空字符串时跳过h3生成否link字符串卡片底部的操作文案如 Try It Now有href时缺省文案为 Learn more否href字符串存在时整张卡片渲染为a外链以http开头自动追加target_blank是imgSrc字符串卡片头部的 SVG 插图路径走SVG 插图卡片渲染分支是iconImgSrc字符串卡片标题旁的自定义 SVG 图标须与href同用内联 SVG 以支持 CSS 变量换肤否titleInline布尔存在即真图标与标题同行排版包裹在docs-card-header-inline容器中否tokenizer 的最后一步是把卡片正文交给 lexer 递归解析成块级 token 子树const body match[2].trim(); // ... this.lexer.blockTokens(token.body, token.tokens);这一步解释了为什么夹具第二张卡片里的CommonModule会被正常解析为行内代码——卡片正文并非简单文本而是完整的 marked 文档片段。渲染器三个标准分支 一个 SVG 插图分支docsCardExtension.renderer根据 token 决定走哪条渲染路径renderer(this: RendererThis, token: DocsCardToken) { return token.imgSrc ? getCardWithSvgIllustration(this, token) : getStandardCard(this.parser.renderer as AdevDocsRenderer, token); }标准卡片getStandardCard按是否带图标和是否带链接划分为三个分支与夹具文档的四张卡片一一对应iconImgSrchref先用 helpers.mts 的loadWorkspaceRelativeFile把 SVG 文件内容从磁盘读进来内联源码注释说明了动机不渲染成img而是内联 SVG是为了用 CSS 变量支持深色/浅色主题切换再输出带标题头部的a classdocs-card。仅href对应夹具的 Link Card 与空标题的 Playground 卡片输出atitle为空时省略h3正文通过parseWithoutCreatingLinks解析底部span显示link文案或默认 Learn more。无href对应 No Link Card输出普通div classdocs-cardlink属性仅在存在时才渲染为span。SVG 插图卡片getCardWithSvgIllustration对应夹具第三张 Image Card。插图通过loadWorkspaceRelativeFile(token.imgSrc!)内联进 HTML卡片结构为a href... classdocs-card docs-card-with-svg {内联的 SVG} div classdocs-card-text-content h3标题/h3 {正文} span操作文案/span /div /a夹具中imgSrc./angular.svg指向的就是同目录下的测试资产 angular.svg。防嵌套链接机制HTML 规范不允许a内再嵌套a。当卡片本身是链接时正文里出现的行内链接或自动链接必须被降级。实现方式是借助渲染器上下文开关function parseWithoutCreatingLinks(renderer: AdevDocsRenderer, token: DocsCardToken) { renderer.context.disableAutoLinking true; const parsed renderer.parser.parse(token.tokens); renderer.context.disableAutoLinking false; return parsed; }disableAutoLinking是 renderer.mts 中RendererContext的一个布尔字段真正消费它的地方在 transformations/link.mts当该开关为真时链接转换逻辑会阻止生成新的a标签。这个模式同样被标题锚点转换复用见 transformations/heading.mts说明它是整条流水线中临时抑制链接生成的通用手段。外链 target 处理所有会生成a的分支都调用anchorTarget(token.href)该函数定义在 helpers.mts判断href是否以http开头是则追加target_blank保证外链在新标签页打开而站内链接在当前页跳转。测试契约四种形态逐一断言docs-card.spec.mts 用 JSDOM 加载夹具文档的解析结果四个测试用例与文档四行四张卡片严格一一对应it(creates cards with no links, () { const cardEl markdownDocument.querySelectorAll(.docs-card)[0]; expect(cardEl.querySelector(h3)?.textContent?.trim()).toBe(No Link Card); expect(cardEl.tagName).not.toBe(A); }); it(creates cards with links, () { const cardEl markdownDocument.querySelectorAll(.docs-card)[1]; expect(cardEl.tagName).toBe(A); expect(cardEl.getAttribute(href)).toBe(in/app/link); }); it(should not create nested links, () { const cardEl markdownDocument.querySelectorAll(.docs-card)[1]; expect(cardEl.querySelectorAll(a).length).toBe(0); }); it(creates cards with svg images, () { const cardEl markdownDocument.querySelectorAll(.docs-card)[2]; expect(cardEl.querySelector(svg)).toBeTruthy(); }); it(does not create empty h3 tags when title is empty, () { const cardEl markdownDocument.querySelectorAll(.docs-card)[3]; expect(cardEl.querySelector(h3)).toBeNull(); });这套断言恰好验证了本文前面讲解的全部关键行为卡片 0h3文案正确、根节点不是a——对应无链接分支卡片 1根节点是a且href原样透传in/app/link——对应链接分支卡片 1 附加断言卡片内部a数量为 0——验证parseWithoutCreatingLinks的防嵌套链接机制卡片 2内部存在内联svg元素——验证imgSrc走的是内联 SVG 而非img卡片 3title时querySelector(h3)为null——验证空标题不产生空标签。测试通过 Bazel 目标组织见 BUILD.bazel在仓库的 Bazel 测试体系中运行与整条 adev 流水线共用同一套构建入口。容器层docs-card-container 的网格布局单张卡片之外docs-card-container.mts 提供了外层容器标签其 tokenizer 结构整体正则 属性子正则 blockTokens递归解析与docs-card完全同构但只支持headerTitle与headerImgSrc两个属性无headerTitle卡片列表包在div classdocs-card-grid中有headerTitle额外生成.docs-card-container-wrapper内含.docs-card-container-header标题 可选头部 SVG与.docs-card-container-content.docs-card-grid两层结构。其测试夹具 docs-card-container.md 演示了容器中嵌套多张docs-card的完整文档流写法容器前后穿插普通标题、列表与段落与 docs-card-container.spec.mts 配套验证。样式层卡片网格如何呈现渲染产物中出现的docs-card-grid、docs-card、docs-card-container-wrapper等类名由 styles/docs/_card.scss 中的docs-card()mixin 定义外观.docs-card-grid采用display: grid; grid-template-columns: repeat(2, 1fr)的双列网格布局窄容器container docs-content (max-width: 450px)下自动降级为单列.docs-card-container-wrapper带边框、圆角与交替渐变背景奇数容器用白到浅蓝的斜向渐变偶数用白到浅粉的渐变头部 SVG 中的theme-fill-*/theme-stroke-*类通过 CSS 变量着色——这正是渲染器坚持内联 SVG 而非img的原因让插图可以随主题变量在明暗模式下换色。小结一条标签 → token → HTML的完整链路回到本文主体文档 docs-card.md 的 8 行内容它实际上定义了docs-card扩展的行为边界。完整链路可以概括为书写作者在 adev 文档 Markdown 中使用docs-card title... link... href... imgSrc...块级标签校验validatePairedTags保证开闭标签配对分词docsCardExtension.tokenizer用整体正则 属性子正则提取出DocsCardToken正文经blockTokens递归解析渲染按imgSrc/iconImgSrc/href的组合选择渲染分支外链自动加target_blank链接卡片正文经disableAutoLinking防嵌套呈现_card.scss提供双列网格、容器头部与主题化 SVG 着色验证docs-card.spec.mts 对四种形态逐张断言夹具文档即测试输入。这条正则分词 → token 树 → 分支渲染 → 样式类落地 → 测试契约的扩展范式同样适用于流水线中的docs-alert、docs-code、docs-workflow等其余自定义标记它们的实现与测试均位于 marked 扩展目录 和 marked 测试目录是理解 Angular 官方文档站点内容系统的一把钥匙。【免费下载链接】angularDeliver web apps with confidence 项目地址: https://gitcode.com/GitHub_Trending/an/angular创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表