Angular 文档流水线 `<docs-code>` 元素解析:从测试夹具到 marked 扩展的完整实现
2026/9/7 14:23:10 网站建设 项目流程

Angular 文档流水线<docs-code>元素解析:从测试夹具到 marked 扩展的完整实现

【免费下载链接】angularDeliver web apps with confidence 🚀项目地址: https://gitcode.com/GitHub_Trending/an/angular

Angular 官方文档站(angular.dev)的内容渲染并非直接依赖原生 Markdown,而是基于adev/shared-docs/pipeline下的一条 Markdown 预处理流水线,其中<docs-code>是最核心的自定义代码块元素。本文以仓库中的测试夹具 docs-code.md 为主体,逐一剖析其 7 种用法的语法与渲染行为,并结合 docs-code 扩展源码、格式化模块 与 单元测试,讲清每个属性(pathheaderlanguageregionhideDollar等)在流水线中的解析路径与底层原理。

一、测试夹具文档:<docs-code>的完整语法面

docs-code.md 是docs-code扩展的"活文档"——它本身既是语法示例,又是 docs-code.spec.mts 的测试输入。文档中的 7 段示例按出现顺序对应 spec 中querySelectorAll('code')索引 0~4 及.docs-code索引 5~6 的断言,覆盖如下能力:

  1. 内联代码体<docs-code>this is code</docs-code>,直接以标签内容作为代码;
  2. path引用 + ESLint 注释剥离<docs-code path="./example-with-eslint-comment.ts" />
  3. path引用 + region 提取<docs-code path="./example-with-region.ts" />
  4. 自定义header标题<docs-code header="src/locale/messages.fr.xlf (<trans-unit>)" path="./messages.fr.xlf" />
  5. API 符号链接控制language="ts"的内联对象字面量,验证属性名不会被误链;
  6. hideDollar属性<docs-code hideDollar code="echo 'hello world'" />
  7. language属性 + 内联去缩进language="typescript"的多行代码块。

其中示例 2、3、4 引用的资源文件均位于同一测试目录下:

  • example-with-eslint-comment.ts:仅含一行// eslint-disable-next-lineconst x = 1;
  • example-with-region.ts:含一对// #docregion something标记包裹const x = 'within the region';
  • messages.fr.xlf:一个含多层嵌套#docregion注释的 XLIFF 翻译文件。

二、tokenizer 层:属性如何被解析

docs-code.mts 实现了 marked 的 block 级扩展。触发规则为start(src)匹配^<docs-code\s,即标签必须独占行首且后跟空白;整体结构由正则singleFileCodeRule捕获:

const singleFileCodeRule = /^\s*<docs-code((?:\s+[\w-]+(?:="[^"]*"|='[^']*'|=[^\s>]*)?)*)\s*(?:\/>|>(.*?)<\/docs-code>)/s;

捕获组 1 是开标签上的全部属性串,捕获组 2 是开闭标签之间的内容。随后 tokenizer 用一组独立正则逐一提取属性(见pathRuleheaderRulelinenumsRulehighlightRulelanguageRulevisibleLinesRuleregionRulepreviewRulehideCodeRulehideDollarRuleclassRulepreferRule),组装成DocsCodeToken

属性类型说明(对照源码注释)
pathstring示例文件路径,按工作区相对路径加载,覆盖标签内容
headerstring代码块上方显示的<h3>标题
linenumsbool渲染时插入shiki-ln-number行号
highlightstring需高亮的行,支持区间字符串,经expandRangeStringValues展开
languagestring语法高亮语言;mermaid/shell/bash有特殊处理
visibleLinesstring折叠视图中可见的行;region互斥
regionstring只显示源码中指定#docregion区域
preview/hideCode/hideCopy/hideDollarbool渲染行为开关,hideDollar用于隐藏 shell 代码中的$前缀
classstring附加 CSS 类,按空格切分
prefer/avoidstring代码风格标记,容器附加docs-code-prefer类并在 header 显示 "Prefer"/"Avoid"

两个关键行为值得注意:

  • path优先:若指定了path且非空,tokenizer 调用loadWorkspaceRelativeFile(path[1])读取文件,忽略标签内联内容;并立即按文件扩展名调用removeEslintComments(code, fileType)剥离 ESLint 指令注释(见第四节);
  • 互斥校验前置:format/index.mts 的formatCode第一行即throw Error('Cannot define visible lines and region at the same time')visibleLinesregion不允许同时出现——因为 region 提取本质上就是"按注释计算 visibleLines"。

三、渲染层:deindent、region 提取、Shiki 高亮与 API 链接

renderer 阶段由formatCode(token, context)驱动,处理顺序是:extractRegionsdeindenttrimhighlightCode→ 构建<div class="docs-code">容器 → 应用属性与类 →processForApiLinks

3.1 region 提取:从注释到行区间

format/region.mts 中extractRegions调用 regions/region-parser.mts 的regionParser

  • token.path的扩展名选择匹配器(REGION_MATCHERS映射:ts/js/mjs/es6inlineC// #docregion风格)、html/svghtml匹配器(<!-- #docregion -->)、conf/yaml/shinlineHash等);
  • 逐行扫描,遇到#docregion name打开区域、#enddocregion name关闭,支持嵌套区域逗号分隔的多区域名getRegionNames按逗号拆分);
  • 区域标记行本身会被从内容中过滤掉(countOfRegionLines++return false),因此输出 HTML 中不会出现docregion字样——这正是 spec 中not.toContain('docregion')断言的保证;
  • 若 token 指定了region,取regionMap[token.region]并以其lines.join('\n')替换整段代码;找不到则抛Cannot find ${token.region} in ${token.path}!
  • 无区域名的整文件内容会落入名为空字符串的WHOLE_FILE_REGION_NAME,使region=""也能工作。

夹具 example-with-region.ts 正验证了这一点:spec 断言渲染结果含const x = 'within the region';而不含docregion。而 messages.fr.xlf 中大量translated-hellocustom-idgenerated-id等嵌套区域,展示了 HTML 注释匹配器处理多层#docregion/#enddocregion的能力(如translated-hello内再嵌custom-id,可分别引用)。

3.2 deindent:内联代码块的正确缩进

format/index.mts 的deindent计算所有非空行的最小公共前导空白并整体裁掉。夹具末尾的示例:

<docs-code language="typescript"> if (foo) { // bar } </docs-code>

渲染后// bar仍保留 2 个缩进空格(相对缩进不变),对应 spec 断言codeBlock?.textContent toMatch(/^ \/\/ bar/m)——这就是"should deindent inline code blocks correctly"要验证的行为:裁掉的是公共前缀,而非全部缩进。

3.3 高亮与语言推断

format/highlight.mts 中highlightCode的要点:

  • language === 'none''file'时跳过高亮;
  • 未写language时按guessLanguageFromPath推断:.ts/.jstypescript.htmlangular-html.csscss.jsonjson,其余回退angular-ts——这解释了夹具中messages.fr.xlf不带language也能获得合理高亮;
  • highlight属性经expandRangeStringValues展开为行集合后传入 Shiki(codeToHtml),并携带apiEntries上下文;
  • linenums为真时,用 JSDOM 遍历 Shiki 输出的.line元素,在每行前插入<span class="shiki-ln-number">N</span>
  • languagemermaid时容器设置mermaid="true"属性,为shell/bash时追加shell类(供前端渲染$提示符,与hideDollar联动)。

3.4 API 符号自动链接,但排除对象属性名

format/index.mts 的processForApiLinks遍历 Shiki 生成的叶子<span>,用getSymbolUrl(symbol, apiEntries)匹配 API 词条表,命中则把符号替换为指向 API 文档的<a>。夹具中的反例专门防止误伤:

<docs-code header="Property names should not be linked" language="ts"> const form = { state: [''] }; </docs-code>

spec 断言渲染后的innerHTML存在<a href="/api/animations/state">state</a>——即state虽是 API 词条,但作为对象字面量的属性键不应生成链接。这保证了文档中的代码块既保留符号可点击性,又不破坏语法语义。

3.5 容器属性回写

applyContainerAttributesAndClasses会把pathvisibleLines(展开后的数字数组)、header回写为容器属性,布尔开关preview/hideCode/hideCopy/hideDollar写为"true"属性——hideDollar的 spec 断言getAttribute('hideDollar') === 'true'正是验证这一步,前端脚本据此决定是否为 shell 代码渲染$提示符。

四、ESLint 注释剥离:path加载的隐藏清洗步骤

regions/remove-eslint-comments.mts 只对ts/js/html生效,用合并正则删除eslint-disableeslint-disable-next-lineeslint-disable-lineeslint-enable四类指令(ts/js的正则同时包含 HTML 注释形式,以覆盖@Component内联模板)。注意 TS 正则不覆盖块注释/* ... */之外的其他代码注释,仅精准命中 eslint 前缀行。

夹具 example-with-eslint-comment.ts 首行// eslint-disable-next-line因此被剥离,spec 断言渲染文本not.toContain('// eslint')。设计意图很直接:示例源码里的 lint 抑制指令对读者毫无价值,流水线统一清洗。

五、端到端验证与真实使用场景

5.1 测试如何接线

docs-code.spec.mts 的执行流程:setHighlighter()初始化 Shiki 高亮器 → 读取./docs-code.mdparseMarkdown(content, rendererContext)(入口在 marked/parse.mts,先validatePairedTags校验标签配对,再经marked.use({extensions, walkTokens})注册全部扩展,docsCodeExtension是其中之一)→ JSDOM 片段化后用 7 条断言逐段验证上述行为。path为相对路径(./example-with-region.ts),说明loadWorkspaceRelativeFile以 Markdown 文件所在目录为基准解析示例文件,文档与示例同目录共置。

5.2 正式文档中的用法

该夹具展示的语法在 angular.dev 内容库中大量复用,例如:

  • service worker 通信指南 中<docs-code header="log-update.service.ts" path="adev/src/content/examples/service-worker-getting-started/src/app/log-update.service.ts" region="sw-update"/>——header给出面向读者的文件名,path指向示例工程源码,region只截取sw-update片段;
  • 动画复合序列指南 与 CSS 动画指南 中同样以header+path+region三件套引用adev/src/content/examples/animations下的示例源码。

从源码结构看,正式文档使用仓库绝对相对路径 +region,而测试夹具使用同目录短路径,二者走的是同一条docsCodeExtension解析链路。

六、小结:属性速查与适用边界

需求写法流水线落点
展示代码片段(带语言)<docs-code language="ts">…</docs-code>tokenizer 取捕获组 2 → Shiki 高亮
引用仓库/示例文件path="…"loadWorkspaceRelativeFile+ ESLint 清洗
只展示文件片段region="name"regionParser提取,标记行被过滤
指定显示标题header="file.ts (excerpt)"容器内<h3>
行号linenums插入shiki-ln-numberspan
高亮特定行highlight="3,7-9"区间展开后传入 Shiki
折叠默认视图可见行visibleLines="1-5"region互斥
隐藏 shell$hideDollar容器属性"true"
好/坏示例对比prefer/avoiddocs-code-prefer/avoid类 + 头部标签

适用前提:该流水线服务于 Angular 官方文档站构建(Bazel 下的adev工程),<docs-code>仅在此 Markdown 渲染链中生效,普通 Markdown 阅读器不识别这些标签;path解析依赖工作区文件布局,region提取依赖示例源码中按 region-parser.mts 规范书写的#docregion/#enddocregion配对注释。理解这套机制后,无论是阅读 angular.dev 文档背后的生成逻辑,还是在仓库内新增/修改文档示例,都能准确预测<docs-code>的最终渲染结果。

【免费下载链接】angularDeliver web apps with confidence 🚀项目地址: https://gitcode.com/GitHub_Trending/an/angular

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询