首页
/ Mermaid 配置机制全解析:defaultConfig、siteConfig、Frontmatter 与 Directives 的三层配置体系

Mermaid 配置机制全解析:defaultConfig、siteConfig、Frontmatter 与 Directives 的三层配置体系

2026-09-06 15:08:45作者:晏闻田Solitary

本文以 Mermaid 仓库的 Configuration 文档为主线,系统讲解 Mermaid 启动时如何抽取并合并多来源配置,覆盖 frontmatter YAML 配置、initialize 站点级配置、configApi.reset 重置机制等文档核心内容,并结合 config.tspreprocess.ts 等源码,帮助读者掌握"默认配置 → 站点配置 → 图表级配置"的完整合并链路与安全边界(sanitize),能够独立完成站点级主题定制、单图级 frontmatter 覆盖以及渲染前的配置重置。

一、配置来源总览:三层配置与 render config

Mermaid 启动时,会先抽取配置(configuration)来确定渲染某张图所用的最终配置。根据 Configuration 文档,配置共有以下来源:

  • 默认配置(defaultConfig):所有图表的底层基础,代码中由 defaultConfig.ts 提供,并在 config.ts 中以 Object.freeze(config) 冻结导出,保证默认值不可被运行时意外篡改;
  • 站点级覆盖(siteConfig):由宿主站点/应用通过 initialize 调用设置,作用于该站点内的所有图表;
  • Frontmatter(v10.5.0+):图表作者可以在图表代码顶部的 YAML frontmatter 中更新选定的配置参数,应用到 render config 上;
  • Directives(已被 Frontmatter 取代,Deprecated):图表作者可以在图表代码中通过 %%{init: ...}%% 指令直接更新配置,同样应用到 render config。

最终,render config(渲染配置) 是把上述各层配置按优先级合并后、真正用于渲染的配置对象。

从源码结构看,这一"三层叠加"模型在 config.ts 中体现得非常清晰——模块内部维护了四个状态变量:

let siteConfig: MermaidConfig = assignWithDepth({}, defaultConfig);
let configFromInitialize: MermaidConfig;
let directives: MermaidConfig[] = [];
let currentConfig: MermaidConfig = assignWithDepth({}, defaultConfig);

siteConfig(站点配置,基于默认配置深合并而来)、configFromInitialize(initialize 传入的原始配置)、directives(累积的指令/frontmatter 配置队列)和 currentConfig(当前实际生效的渲染配置)。所有合并都通过深合并工具 assignWithDepth 完成,保证嵌套配置项(如 themeVariables)按深度逐项覆盖,而不是整体替换。

二、Frontmatter 配置:在图表代码内自包含地覆盖参数

Configuration 文档指出:除安全配置(secure configs)外,整个 mermaid 配置都可以由图表作者在 frontmatter 中覆盖。frontmatter 是位于图表顶部的一个 YAML 块。文档给出的示例如下:

---
title: Hello Title
config:
  theme: base
  themeVariables:
    primaryColor: "#00ff00"
---
flowchart
	Hello --> World

这个示例同时演示了三个 frontmatter 能力:title(图表标题)、config.theme(切换主题为 base)和 config.themeVariables.primaryColor(覆盖主题变量主色为 #00ff00)。

2.1 frontmatter 的解析实现

frontmatter 的提取逻辑在 frontmatter.ts 中。关键实现要点:

  • 使用正则 frontMatterRegex 匹配开头的 --- 块,若无匹配则原样返回,图表保持无 metadata;
  • 解析前会对 YAML 体做去缩进处理(js-yaml 拒绝 tab 缩进的文档);
  • 使用 js-yamlJSON_SCHEMA 模式解析,以支持完整 JSON 类型的配置值;
  • 仅提取显式支持的三个字段:titledisplayMode(当前用于甘特图的紧凑显示模式)、config,其余字段会被丢弃。
// frontmatter.ts 简化逻辑
let parsed = yaml.load(yamlBody, { schema: yaml.JSON_SCHEMA }) ?? {};
const metadata = {};
if (parsed.displayMode) metadata.displayMode = parsed.displayMode.toString();
if (parsed.title) metadata.title = parsed.title.toString();
if (parsed.config) metadata.config = parsed.config;

2.2 frontmatter 与 directives 的合并链路

frontmatter 解析完成后进入预处理流程 preprocess.tspreprocessDiagram 函数按固定顺序完成四件事:

  1. cleanupText:统一换行符(CRLF → LF)、把 HTML 属性的双引号统一为单引号;
  2. processFrontmatter:提取 frontmatter。注意一个细节——若设置了 displayMode,会自动把它映射到 config.gantt.displayMode(为兼容旧版 gantt 紧凑模式);
  3. processDirectives:检测 init 指令和 wrap 指令,并从代码中移除指令文本;
  4. cleanAndMerge(frontMatter.config, directive.directive):将 frontmatter 配置与指令配置清洗合并,得到最终的图表级配置。

也就是说,同一张图里 frontmatter 与 directives 并存时,二者会先合并再叠加到站点配置之上——这正是 Configuration 文档所说"两者都作用于 render config"的具体实现。

三、Mermaid 的启动时序与站点级 Initialize

Configuration 文档给出了一张启动时序图,说明站点如何把配置注入 mermaid:

sequenceDiagram
	Site->>mermaid: initialize
	Site->>mermaid: content loaded
	mermaid->>mermaidAPI: init

时序含义:站点先调用 initialize 完成站点级配置,随后内容加载完毕,mermaid 入口再调用 mermaidAPI 完成内部初始化。

3.1 initialize:只应用一次的站点级覆盖

文档强调:initialize 调用只生效一次,由站点集成方调用来在站点层面覆盖默认配置。在 mermaid.ts 中可以看到其实现就是薄薄一层转发:

const initialize = function (config: MermaidConfig) {
  mermaidAPI.initialize(config);
};

对应的底层 API 在 mermaidAPI.ts 中,initialize 会调用 config.tssetSiteConfig

export const setSiteConfig = (conf: MermaidConfig): MermaidConfig => {
  siteConfig = assignWithDepth({}, defaultConfig);
  siteConfig = assignWithDepth(siteConfig, conf);

  if (conf.theme && theme[conf.theme]) {
    siteConfig.themeVariables = theme[conf.theme].getThemeVariables(conf.themeVariables);
  }

  updateCurrentConfig(siteConfig, directives);
  return siteConfig;
};

这段源码印证了文档中"覆盖默认配置"的说法:siteConfig 总是先以冻结的 defaultConfig 打底,再深合并站点传入的 conf;若站点指定了 theme,还会把该主题预置的主题变量与站点自定义的 themeVariables 合并,保证换主题后变量取值完整。另外 config.ts 中的 saveConfigFromInitialize 会把 initialize 的原始配置单独保存,供主题变量回退合并使用。

3.2 updateSiteConfig:initialize 之外的另一种更新方式

除了一次的 initializeconfig.ts 还提供 updateSiteConfig,它不做"打底重置",而是在现有 siteConfig 之上增量深合并后重新计算 currentConfig。从源码结构看,适合站点在运行期间(例如用户切换主题面板)追加更新站点配置的场景。生成的 API 参考文档也收录了这两个函数,可参见 setSiteConfigupdateSiteConfig

四、render config 的合并算法:defaultConfig → siteConfig → directives

最终生效的 render config 由 config.tsupdateCurrentConfig 计算,合并优先级与文档描述完全一致:

const updateCurrentConfig = (siteCfg: MermaidConfig, _directives: MermaidConfig[]) => {
  // start with config being the siteConfig
  let cfg: MermaidConfig = assignWithDepth({}, siteCfg);

  // Join directives
  let sumOfDirectives: MermaidConfig = {};
  for (const d of _directives) {
    sanitize(d);
    sumOfDirectives = assignWithDepth(sumOfDirectives, d);
  }

  cfg = assignWithDepth(cfg, sumOfDirectives);

  if (sumOfDirectives.theme && sumOfDirectives.theme in theme) {
    // 用 initialize 保存的主题变量打底,再叠加指令的主题变量
    const themeVariables = assignWithDepth(
      configFromInitialize?.themeVariables || {},
      sumOfDirectives.themeVariables
    );
    if (cfg.theme && cfg.theme in theme) {
      cfg.themeVariables = theme[cfg.theme].getThemeVariables(themeVariables);
    }
  }

  currentConfig = cfg;
  checkConfig(currentConfig);
  return currentConfig;
};

可以归纳出四点:

  1. 基底是 siteConfigcfgsiteConfig 深拷贝起步,因此 defaultConfig 的每一项都是"最终兜底值";
  2. directives(含 frontmatter 配置)最后叠加:多条指令按入队顺序依次 sanitize 后深合并,再整体覆盖到 cfg 上;
  3. 主题变量有专门的回退合并:当指令指定了 theme 时,themeVariables 会以 initialize 时保存的主题变量为底、叠加指令中的主题变量,最后交给对应主题的 getThemeVariables 生成完整变量集——这解释了为何 frontmatter 里只写 primaryColor 一个变量,其余主题变量仍能正确取值;
  4. 每次合并后都会执行 checkConfig:目前用于对已废弃配置项(如 lazyLoadedDiagramsloadExternalDiagramsAtStartup)打印一次性警告(见 config.ts)。

此外,getEffectiveHtmlLabels 展示了配置读取端的兼容处理:优先取全局 htmlLabels,回退到已废弃的 flowchart.htmlLabels(并提示用全局 htmlLabels 替代),这属于 v9→v10 配置迁移的典型模式。

五、安全边界:secure 配置与 sanitize

Configuration 文档特别提到 frontmatter "除 secure configs 外"均可覆盖。这条边界在 sanitize 中落地,它在每次指令加入合并前被调用(updateCurrentConfig 循环内的 sanitize(d)):

export const sanitize = (options: any) => {
  // 1. 禁止覆盖 siteConfig 声明的 secure 键(始终包含 'secure' 本身)
  ['secure', ...(siteConfig.secure ?? [])].forEach((key) => {
    if (Object.hasOwn(options, key)) {
      log.debug(`Denied attempt to modify a secure key ${key}`, options[key]);
      delete options[key];
    }
  });
  // 2. 删除以 __ 开头的键,防原型污染
  // 3. 删除包含 '<'、'>' 或 'url(data:' 的字符串值,防 XSS
  // 4. 递归处理对象值
};

三层防护的意图在源码注释中写得很明白:注释特别提醒不要在日志中用模板字符串打印被拦截的值,因为恶意脚本可以利用日志器对值的序列化执行任意代码。因此:

  • 站点可以把敏感键(例如 securityLevel 相关的覆盖)列入 siteConfig.secure,图表作者即使用 frontmatter 或 init 指令也无法改写;
  • 指令/frontmatter 中的任何 HTML 片段、data: URL 都会被静默删除,防止通过配置通道注入脚本。

生成的 API 参考 sanitize 同样对应这段实现。

六、configApi.reset:每张图渲染前的配置归位

Configuration 文档对 configApi.reset 的定义:把某张图的配置重置回站点整体配置(即站点集成方提供的配置),并且在每次渲染图表之前,reset 会在最开始被调用

对应实现是 config.tsreset

export const reset = (config = siteConfig): void => {
  // Replace current config with siteConfig
  directives = [];
  updateCurrentConfig(config, directives);
};

两个关键行为:

  1. 清空 directives 队列:上一张图的 frontmatter/init 指令不会泄漏到下一张图——这正是"每张图渲染前调用 reset"的意义所在,保证图表级配置的隔离性;
  2. 以 siteConfig 为新基底重算 currentConfig:站点配置本身不受影响,被重置掉的只是叠加在站点层之上的图表级覆盖。

reset 还支持传入自定义基底配置(默认值为 siteConfig),配合 getSiteConfig(返回 siteConfig 的深拷贝)可以只读地获取站点配置做比对。对应的生成文档见 resetgetSiteConfig

值得一提的是,旧式 setConfig(直接改 currentConfig)已被标记为 @deprecated:文档注释明确说明对 currentConfig 的修改会被下一次 addDirectivereset 覆盖——这进一步说明当前推荐的图表级定制路径就是 frontmatter(或 legacy 的 directives),而站点级路径是 initialize

七、实用建议与小结

结合 Configuration 文档与源码实现,实际集成时可以遵循以下实践:

  • 站点级主题与全局参数:在应用启动时调用一次 mermaid.initialize({ theme, themeVariables, flowchart: {...} }),让 setSiteConfig 完成 defaultConfig 打底与主题变量补全;
  • 单图级定制:优先使用 frontmatter(v10.5.0+,YAML 更直观、支持任意嵌套配置),例如文档示例中的 config.theme + config.themeVariables.primaryColor%%{init: ...}%% directives 属于被取代的旧方案,新图不建议再写;
  • 安全敏感场景:在 initialize 时通过 secure 数组声明受保护键,sanitize 会拦截任何来自图表作者的同名覆盖;
  • 不要手动改 currentConfigsetConfig 已弃用,图表级配置请走 frontmatter,跨图状态靠"渲染前自动 reset"保证隔离。

小结:Mermaid 的配置体系是一个"默认配置冻结打底 → initialize 生成 siteConfig → frontmatter/directives 逐图叠加 → sanitize 安全过滤 → 渲染前 reset 归位"的闭环。理解 config.tssiteConfig / configFromInitialize / directives / currentConfig 四个状态变量的流转,再对照 frontmatter.tspreprocess.ts 的提取顺序,就能准确预测任意一张图最终拿到的 render config,也能在集成方与图表作者两个角色之间划清配置权限的边界。

登录后查看全文
热门项目推荐
相关项目推荐