Mermaid 配置机制深读:getEffectiveHtmlLabels 如何裁决 htmlLabels 标签渲染开关
getEffectiveHtmlLabels 是 Mermaid 配置层中一个看似不起眼、却横跨整个渲染管线的关键辅助函数。它负责在「根级全局配置」「已废弃的图表级配置」与「默认值」之间裁决出最终生效的 htmlLabels 布尔值,并顺带发出弃用警告。读懂它,不仅能理解 Mermaid 中「节点/边标签到底用 HTML 还是纯 SVG 文本渲染」这一行为的决定机制,还能掌握 Mermaid 配置合并(default → initialize → directive)与弃用迁移设计的完整套路。本文以该函数为轴心,结合 config.ts 源码、config.spec.ts 测试与渲染层调用点,逐层展开。
函数签名与文档定位
官方 API 文档页 getEffectiveHtmlLabels.md(由 Typedoc 自动生成,源文件在 packages/mermaid/src/docs/config/setup/config/functions/getEffectiveHtmlLabels.md)对该函数的定义非常精炼:
getEffectiveHtmlLabels(
config):booleanHelper function to handle deprecated flowchart.htmlLabels
Defined in:
packages/mermaid/src/config.ts:246
- 参数
config:MermaidConfig— The configuration object (merged config with defaults)- 返回
boolean— The effective htmlLabels value based on precedence: root > flowchart > default
三点信息值得抓住:
- 输入是「合并后的配置」——即已经过 defaultConfig 与用户配置深合并的对象,而不是原始用户输入;
- 返回值是布尔量,语义上等价于「本次渲染是否启用 HTML 标签」;
- 优先级链:根级
htmlLabels> 图表级flowchart.htmlLabels(已废弃)> 默认值true。
源码实现逐行拆解
函数本体位于 packages/mermaid/src/config.ts#L241-L252:
/**
* Helper function to handle deprecated flowchart.htmlLabels
* @param config - The configuration object (merged config with defaults)
* @returns The effective htmlLabels value based on precedence: root flowchart default
*/
export const getEffectiveHtmlLabels = (config: MermaidConfig): boolean => {
// != instead of !== handles null case
if (config.flowchart?.htmlLabels != undefined) {
issueWarning('FLOWCHART_HTML_LABELS_DEPRECATED');
}
return evaluate(config.htmlLabels ?? config.flowchart?.htmlLabels ?? true);
};
三个实现细节各有讲究:
1. 用 != 而非 !== 判断「用户是否显式设置了 flowchart.htmlLabels」
config.flowchart?.htmlLabels != undefined 是宽松比较,能同时捕获 null 和 undefined。这意味着:只要合并后的配置里 flowchart.htmlLabels 不是 undefined——哪怕值是 null——都会触发弃用警告。注释中 // != instead of !== handles null case 明确了这一意图。
警告文案定义在 packages/mermaid/src/config.ts#L200-L205:
const ConfigWarning = {
LAZY_LOAD_DEPRECATED:
'The configuration options lazyLoadedDiagrams and loadExternalDiagramsAtStartup are deprecated. Please use registerExternalDiagrams instead.',
FLOWCHART_HTML_LABELS_DEPRECATED:
'flowchart.htmlLabels is deprecated. Please use global htmlLabels instead.',
} as const;
2. 警告只发一次:issuedWarnings 去重机制
packages/mermaid/src/config.ts#L207-L215 实现了一个模块级单发警告器:
type ConfigWarningStrings = keyof typeof ConfigWarning;
const issuedWarnings: Partial<Record<ConfigWarningStrings, boolean>> = {};
const issueWarning = (warning: ConfigWarningStrings) => {
if (issuedWarnings[warning]) {
return;
}
log.warn(ConfigWarning[warning]);
issuedWarnings[warning] = true;
};
getEffectiveHtmlLabels 在渲染管线中被高频调用(每个节点标签、每条边标签都可能触发一次),如果没有去重,控制台会被同一条弃用警告刷屏。issuedWarnings 用键值记录「该警告是否已发过」,保证整个进程生命周期内 flowchart.htmlLabels is deprecated... 只出现一次。
3. 用 evaluate 把「字符串型布尔」归一化
最终返回值经过 packages/mermaid/src/config.ts#L16-L17 的 evaluate 函数:
export const evaluate = (val?: string | boolean | null): boolean =>
val === false || ['false', 'null', '0'].includes(String(val).trim().toLowerCase()) ? false : true;
它把 false、'false'、'null'、'0'(忽略大小写与首尾空白)都归一为 false,其余一切(包括 true、'true'、'1'、空值)归一为 true。这样处理的原因在于:配置值可能来自 mermaid.initialize 的 JS 对象,也可能来自 HTML 属性或配置文件中以字符串形式传入的布尔值,evaluate 保证了取值语义的一致性。
完整优先级链
把上面三处组合起来,决策过程为:
| 步骤 | 表达式 | 语义 |
|---|---|---|
| ① 弃用探测 | config.flowchart?.htmlLabels != undefined |
图表级值存在(含 null)→ 发一次弃用警告,但不影响取值 |
| ② 取值 | config.htmlLabels ?? config.flowchart?.htmlLabels ?? true |
根级优先;其次是(已废弃的)flowchart 级;两者皆无则回退 true |
| ③ 归一 | evaluate(...) |
字符串/布尔 → 标准布尔 |
注意步骤②使用的是 ??(空值合并),null 同样会触发回退——即 htmlLabels: null 不占位,会继续看下一级。这与步骤①中刻意用宽松比较探测 null 形成对照:警告按「存在即警告」,取值按「空即回退」。
类型定义中的弃用标记
packages/mermaid/src/config.type.ts#L158-L166 对根级 htmlLabels 的 JSDoc 明确记录了迁移规则:
/**
* Flag for setting whether or not a html tag should be used for rendering labels on nodes and edges.
* **Note:** Diagram-specific `htmlLabels` settings (e.g., `flowchart.htmlLabels`) are deprecated.
* Use this root-level `htmlLabels` setting instead. The root-level `htmlLabels` takes precedence
* over any diagram-specific settings.
*/
htmlLabels?: boolean;
而 flowchart 配置块内的同名属性在 packages/mermaid/src/config.type.ts#L302-L310 被标注为:
/**
* @deprecated
* **DEPRECATED: Use global `htmlLabels` instead.**
* Flag for setting whether or not a html tag should be used for rendering labels on nodes and edges.
* This property is deprecated.
* Please use the global `htmlLabels` configuration instead.
*/
htmlLabels?: boolean | null;
类型签名上 boolean | null 的写法(允许 null)正是与实现中「null 也触发警告」相对应的。另外 packages/mermaid/src/config.type.ts#L860 附近还保留了 classDiagram 自己的 htmlLabels?: boolean 字段,但 getEffectiveHtmlLabels 的裁决链只涉及根级与 flowchart 级两处。
测试用例:优先级矩阵全覆盖
packages/mermaid/src/config.spec.ts#L302-L369 中 describe('getEffectiveHtmlLabels', ...) 用 8 个用例覆盖了完整的优先级与指令(directive)场景,是理解该函数行为的最佳权威依据:
| 场景 | 输入 | 期望 |
|---|---|---|
| 根级 true | setSiteConfig({ htmlLabels: true }) |
true |
| 根级 false | setSiteConfig({ htmlLabels: false }) |
false |
| 仅 flowchart true(根级未设) | { flowchart: { htmlLabels: true } } |
true |
| 仅 flowchart false(根级未设) | { flowchart: { htmlLabels: false } } |
false |
| 冲突时根级胜出 | htmlLabels: false + flowchart.htmlLabels: true |
false |
| 两者都未设 | setSiteConfig({}) |
true(默认值回退) |
| directive 设置 flowchart.htmlLabels=false | 站点默认 + addDirective({ flowchart: { htmlLabels: false } }) |
false |
| directive 设根级 htmlLabels 压过站点 flowchart 值 | 站点 flowchart.htmlLabels: true + addDirective({ htmlLabels: false }) |
false |
| directive 根级 true 压过站点 flowchart false | 站点 flowchart.htmlLabels: false + addDirective({ htmlLabels: true }) |
true |
后三个用例揭示了它与 Mermaid 指令体系的联动:mermaid 代码块内的 %%{init: ...}%% 指令会通过 packages/mermaid/src/config.ts#L173-L186 的 addDirective 进入 updateCurrentConfig,与 siteConfig 按 assignWithDepth 深合并后才成为 getConfig() 返回的对象——也就是 getEffectiveHtmlLabels 拿到的输入。因此「directive 覆盖站点配置」本质上发生在配置合并阶段,函数本身只做纯函数式的裁决。
渲染管线中的消费点:谁在调用它
getEffectiveHtmlLabels 并非孤立存在,它是渲染层判断「用 HTML 标签还是 SVG 文本」的统一入口。从源码调用面看,主要消费点包括:
- packages/mermaid/src/rendering-util/rendering-elements/clusters.js:subgraph 标题(
cluster-label)的创建与 bbox 计算,getEffectiveHtmlLabels(siteConfig)决定用getBoundingClientRect()还是 SVGgetBBox()度量标题尺寸,并影响标题的 Y 向偏移; - packages/mermaid/src/rendering-util/rendering-elements/edges.js:
insertEdgeLabel用其决定边标签的渲染方式;setTerminalWidth中仅当启用 HTML 标签时才按字符数估算<foreignObject>的宽度(value.length * 9 + 'px'); - packages/mermaid/src/rendering-util/rendering-elements/createLabel.js:节点标签创建的公共工具,其 JSDoc 还特别提醒:当
getEffectiveHtmlLabels(getConfig())为true时,创建标签后必须重置其宽高; - packages/mermaid/src/rendering-util/rendering-elements/shapes/note.ts:笔记形状中
node.useHtmlLabels || getEffectiveHtmlLabels(getConfig())——节点级属性可进一步覆盖全局裁决,关闭 HTML 标签时还会置node.centerLabel = true以修正纯文本下的居中对齐; - packages/mermaid/src/rendering-util/rendering-elements/shapes/rectWithTitle.ts、shapes/collapsedGroup.ts、shapes/util.ts:带标题矩形、折叠分组等形状的标签度量与位移逻辑同样依赖该函数。
一个值得注意的模式是 node.useHtmlLabels ?? getEffectiveHtmlLabels(getConfig())(见 collapsedGroup.ts#L60 与 util.ts#L126):节点级 useHtmlLabels 提供第三层细粒度覆盖,而 getEffectiveHtmlLabels 始终是它的全局兜底。
除了直接渲染,还有两个「跨领域」的消费点:
1. classDef 样式生成。 packages/mermaid/src/mermaidAPI.ts#L153-L156 中,getEffectiveHtmlLabels(config) 决定了 classDef 的 CSS 选择器策略:启用 HTML 标签时 CSS 作用于 ['> *', 'span'](DOM 元素),否则作用于 ['rect', 'polygon', 'ellipse', 'circle', 'path'](SVG 形状元素)。这就是为什么同一份 classDef 在两种模式下要生成两套选择器。
2. 文本安全清洗。 packages/mermaid/src/diagrams/common/common.ts#L70-L83 的 sanitizeMore 直接以 getEffectiveHtmlLabels(config) 为闸门:
const sanitizeMore = (text: string, config: MermaidConfig) => {
if (getEffectiveHtmlLabels(config)) {
const level = config.securityLevel;
if (level === 'antiscript' || level === 'strict' || level === 'sandbox') {
text = removeScript(text);
} else if (level !== 'loose') {
text = breakToPlaceholder(text);
text = text.replace(/</g, '<').replace(/>/g, '>');
text = text.replace(/=/g, '=');
text = placeholderToBreak(text);
}
}
return text;
};
即:只有当 HTML 标签模式生效时,才按 securityLevel 决定用 DOMPurify 移除脚本(removeScript,见同文件 L62-L68)还是做实体转义。这意味着 htmlLabels 的取值不仅影响外观,还参与安全策略的分叉——修改该开关时要同时理解它对 XSS 防护路径的影响。
配置实践:如何正确设置与迁移
推荐写法:使用根级 htmlLabels
无论通过 mermaid.initialize 还是代码块内的 directive,迁移后的规范写法是把开关提到根级:
<script>
mermaid.initialize({
startOnLoad: true,
htmlLabels: false, // 全局禁用 HTML 标签(所有图表类型统一生效)
});
</script>
%%{init: {"htmlLabels": false}}%%
flowchart LR
A[普通文本标签] --> B
从 config.type.ts 的 JSDoc 与 config.spec.ts 的「冲突时根级胜出」用例可确认:一旦设置了根级 htmlLabels,任何图表级旧配置都被忽略。
旧写法仍然可用,但会收到一次警告
<script>
mermaid.initialize({
startOnLoad: true,
flowchart: { htmlLabels: false }, // 已废弃:控制台会输出
// "flowchart.htmlLabels is deprecated. Please use global htmlLabels instead."
});
</script>
行为上完全等价于旧语义(测试用例 3、4 保证向后兼容),代价是控制台出现一条弃用警告——且如前所述,该警告每个进程只出现一次。
默认行为
两者都不设时,config.ts#L251 的 ?? true 兜底使 htmlLabels 默认为启用,这与「HTML 标签可支持富文本、换行与图片」的默认产品行为一致;config.spec.ts 中「should default to true when neither root nor flowchart htmlLabels is explicitly set」用例对此做了锁定。
字符串值的坑
由于 evaluate 把 'false'、'null'、'0' 都解析为 false,通过字符串渠道(如模板字符串拼接配置)传值时要留意引号形式;而 'yes'、'1'、true 之外的任意值都会落到 true 分支。
小结与延伸阅读
getEffectiveHtmlLabels 浓缩了 Mermaid 配置系统的三个典型设计:深合并后统一裁决(default → initialize → directive 分层在 updateCurrentConfig 中完成,函数只读合并结果)、弃用而非删除(flowchart.htmlLabels 保留取值能力、叠加单发警告)、归一化输入(evaluate 兼容字符串型布尔)。它同时是渲染管线中 HTML 标签模式的唯一裁决入口,下游覆盖了节点/边/子图标题的度量、classDef CSS 选择器策略与 securityLevel 下的文本清洗分叉。
继续深入可参考:
- packages/mermaid/src/config.ts — 配置存储、合并、指令与警告机制全貌
- packages/mermaid/src/config.type.ts —
MermaidConfig及各图表配置接口的完整类型定义 - packages/mermaid/src/config.spec.ts — 含
getEffectiveHtmlLabels全部 8 个优先级用例 - packages/mermaid/src/diagrams/common/common.ts —
sanitizeText/sanitizeMore的清洗链路 - docs/config/setup/config/functions/getEffectiveHtmlLabels.md — 该函数的自动生成 API 文档页
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00