Skip to content

双引擎架构

Incremark 采用双引擎解析系统,这是我们在开发过程中做出的一个重要架构决策。我们希望为用户提供选择的自由:在追求极致性能和追求完美兼容性之间找到最适合自己的平衡点。

为什么需要双引擎?

在开发 Incremark 的过程中,我们面临一个核心问题:如何在流式 AI 场景中实现最佳性能?

经过大量调研和测试,我们发现:

  • Marked 解析速度极快,但原生不支持脚注、数学公式等高级特性
  • Micromark 规范兼容性完美,插件生态丰富,但包体积较大

最终我们决定:两个都要

通过双引擎架构,用户可以根据实际场景灵活选择:

  • 对性能敏感的 AI 聊天场景 → 使用 Marked 引擎
  • 需要严格规范兼容的文档场景 → 使用 Micromark 引擎

引擎概览

引擎速度特性包体积最佳场景
Marked(默认)⚡⚡⚡⚡⚡标准 + 增强扩展较小实时流式、AI 对话
Micromark⚡⚡⚡完整 CommonMark + 插件较大复杂文档、严格规范

Marked 引擎(默认)

Marked 引擎是我们的默认选择,专为流式 AI 场景深度优化。

为什么选择 Marked 作为默认引擎?

  1. 极致的解析速度:Marked 是 JavaScript 生态中最快的 Markdown 解析器之一
  2. 成熟稳定:拥有超过 10 年的历史,被无数项目验证
  3. 易于扩展:提供了灵活的扩展机制,我们可以按需增加功能
  4. 小巧的包体积:有利于前端项目的 tree-shaking 优化

我们为 Marked 做了什么增强?

原生 Marked 是一个"够用就好"的解析器,它专注于标准 Markdown 语法,不包含很多高级特性。但在 AI 场景中,我们经常需要这些特性。

因此,Incremark 通过自定义扩展为 Marked 增加了以下能力:

功能原生 MarkedIncremark 增强说明
脚注❌ 不支持✅ 完整 GFM 脚注[^1] 引用和 [^1]: 内容 定义
数学公式❌ 不支持✅ 行内和块级公式$E=mc^2$$$...$$
自定义容器❌ 不支持✅ 指令语法:::tip:::warning:::danger
内联 HTML 解析⚠️ 仅保留原文✅ 结构化解析将 HTML 解析为可操作的 AST 节点
乐观引用处理❌ 不支持✅ 流式友好在流式输入时优雅处理未完成的链接/图片
脚注定义块❌ 不支持✅ 多行内容支持包含代码块、列表的复杂脚注

💡 这些扩展是我们针对 AI 场景精心设计的。它们在提供完整功能的同时,尽可能减少性能开销。

使用方式

Marked 引擎是默认的,无需特殊配置:

vue
<script setup>
import { ref } from 'vue'
import { IncremarkContent } from '@incremark/vue'

const content = ref('')
const isFinished = ref(false)
</script>

<template>
  <!-- 默认使用 Marked 引擎 -->
  <IncremarkContent 
    :content="content" 
    :is-finished="isFinished"
  />
</template>

启用/禁用特定功能

vue
<template>
  <IncremarkContent 
    :content="content" 
    :is-finished="isFinished"
    :incremark-options="{
      gfm: true,        // GFM 扩展(表格、删除线等)
      math: true,       // 数学公式
      containers: true, // 自定义容器
      htmlTree: true    // HTML 结构化解析
    }"
  />
</template>

Micromark 引擎

Micromark 引擎是追求完美规范兼容性的选择。

为什么提供 Micromark 选项?

尽管 Marked 引擎已经能满足大多数场景,但某些用户可能有更严格的需求:

  1. 严格的 CommonMark 规范兼容:Micromark 是目前最符合 CommonMark 规范的解析器
  2. 丰富的插件生态:GFM、Math、Directive 等插件都经过社区长期打磨
  3. 精确的位置信息:AST 节点包含准确的行列位置,便于错误定位
  4. 更好的边界情况处理:在一些复杂嵌套场景下表现更稳定

使用方式

要使用 Micromark 引擎,需要导入 MicromarkAstBuilder 并通过 astBuilder 选项传入:

ts
// 在你的 composable 或 setup 中
import { createIncremarkParser } from '@incremark/core'
import { MicromarkAstBuilder } from '@incremark/core/engines/micromark'

const parser = createIncremarkParser({
  astBuilder: MicromarkAstBuilder,
  gfm: true,
  math: true
})

注意IncremarkContent 组件默认使用 Marked 引擎。如需使用 Micromark,你需要直接使用 useIncremark 配合自定义 parser。

何时应该使用 Micromark?

  • 你的内容包含复杂的嵌套结构
  • 你需要处理一些 Marked 无法正确解析的边界情况
  • 你的应用对 CommonMark 规范兼容性有严格要求
  • 你需要使用我们内置扩展之外的 Micromark 插件

完整基准测试数据

我们对 38 个真实的 Markdown 文件进行了基准测试,以下是完整的测试结果:

测试环境

  • 测试文件:38 个文件,共 6,484 行,128.55 KB
  • 测试方式:模拟流式输入,逐字符 append
  • 对比方案:Streamdown、markstream-vue、ant-design-x

完整测试结果

文件名行数大小(KB)IncremarkStreamdownmarkstreamant-design-xvs Streamdownvs markstreamvs ant-design-x
test-footnotes-simple.md150.090.3 ms0.0 ms1.4 ms0.2 ms0.1x4.7x0.6x
simple-paragraphs.md160.410.9 ms0.9 ms5.9 ms1.0 ms1.1x6.7x1.2x
test-footnotes-multiline.md210.180.6 ms0.0 ms2.2 ms0.4 ms0.1x3.5x0.6x
test-footnotes-edge-cases.md270.250.8 ms0.0 ms4.2 ms1.2 ms0.0x5.3x1.5x
test-footnotes-complex.md280.242.1 ms0.0 ms4.8 ms1.0 ms0.0x2.3x0.5x
introduction.md341.575.6 ms12.6 ms75.6 ms12.8 ms2.2x13.4x2.3x
devtools.md510.921.2 ms0.9 ms6.1 ms1.1 ms0.8x5.0x0.9x
footnotes.md520.941.7 ms0.2 ms10.6 ms1.9 ms0.1x6.3x1.2x
html-elements.md551.021.6 ms2.2 ms12.6 ms2.8 ms1.4x7.8x1.7x
themes.md580.961.9 ms1.3 ms8.6 ms1.8 ms0.7x4.4x0.9x
test-footnotes-comprehensive.md630.665.6 ms0.1 ms25.8 ms7.7 ms0.0x4.6x1.4x
auto-scroll.md721.683.9 ms3.5 ms39.9 ms4.9 ms0.9x10.1x1.2x
custom-codeblocks.md721.443.4 ms2.0 ms14.9 ms2.5 ms0.6x4.4x0.7x
custom-components.md731.404.0 ms2.0 ms32.7 ms2.9 ms0.5x8.1x0.7x
custom-containers.md881.674.2 ms2.4 ms18.1 ms3.1 ms0.6x4.3x0.7x
typewriter.md881.895.6 ms4.1 ms35.0 ms4.9 ms0.7x6.2x0.9x
concepts.md914.2912.0 ms50.5 ms381.9 ms53.6 ms4.2x31.9x4.5x
INLINE_CODE_UPDATE.md941.664.7 ms17.2 ms60.9 ms15.6 ms3.7x12.9x3.3x
comparison.md1095.3920.5 ms74.0 ms552.2 ms85.2 ms3.6x26.9x4.1x
basic-usage.md1303.048.5 ms12.3 ms74.1 ms14.1 ms1.4x8.7x1.7x
CODE_BACKGROUND_SEPARATION.md1312.838.7 ms28.8 ms153.6 ms31.3 ms3.3x17.6x3.6x
P2_SUMMARY.md1382.618.3 ms38.4 ms157.2 ms41.9 ms4.6x18.9x5.0x
quick-start.md1463.047.3 ms7.3 ms64.2 ms9.6 ms1.0x8.8x1.3x
complex-html-examples.md1473.999.0 ms58.8 ms279.3 ms57.2 ms6.6x31.1x6.4x
CODE_COLOR_SEPARATION.md1623.5110.0 ms32.8 ms191.1 ms36.9 ms3.3x19.1x3.7x
P0_OPTIMIZATION_REPORT.md1683.5310.1 ms56.2 ms228.0 ms58.1 ms5.6x22.6x5.8x
COLOR_SYSTEM_REFACTOR.md1693.7818.5 ms64.0 ms355.5 ms69.1 ms3.5x19.2x3.7x
FOOTNOTE_TEST_GUIDE.md2192.8712.3 ms0.2 ms167.6 ms45.0 ms0.0x13.7x3.7x
P2_COLORS_PACKAGE_REPORT.md2264.1011.4 ms77.9 ms311.6 ms80.5 ms6.8x27.2x7.0x
FOOTNOTE_FIX_SUMMARY.md2363.9322.7 ms0.5 ms535.0 ms120.8 ms0.0x23.6x5.3x
BASE_COLORS_SYSTEM.md2594.4735.8 ms43.0 ms191.8 ms43.4 ms1.2x5.4x1.2x
OPTIMIZATION_COMPARISON.md2705.4217.8 ms52.3 ms366.1 ms61.9 ms2.9x20.6x3.5x
P1_OPTIMIZATION_REPORT.md3275.6320.7 ms106.8 ms433.8 ms114.8 ms5.2x21.0x5.5x
OPTIMIZATION_PLAN.md3716.8933.1 ms67.6 ms372.1 ms76.7 ms2.0x11.2x2.3x
OPTIMIZATION_SUMMARY.md3916.2419.1 ms208.4 ms980.6 ms217.8 ms10.9x51.3x11.4x
P1.5_COLOR_SYSTEM_REPORT.md4829.1222.0 ms145.5 ms789.8 ms168.2 ms6.6x35.9x7.7x
BLOCK_TRANSFORMER_ANALYSIS.md4899.2475.7 ms574.3 ms1984.1 ms619.9 ms7.6x26.2x8.2x
test-md-01.md91617.6787.7 ms1441.1 ms5754.7 ms1656.9 ms16.4x65.6x18.9x
【合计】6484128.55519.4 ms3190.3 ms14683.9 ms3728.6 ms6.1x28.3x7.2x

如何理解这份数据?

我们诚实地告诉你:有些场景 Incremark 更慢

你可能注意到,在 test-footnotes-*.mdFOOTNOTE_*.md 这些文件上,Incremark 比 Streamdown 慢很多(0.0x - 0.1x)。

原因很简单:Streamdown 不支持脚注语法。

当 Streamdown 遇到 [^1] 这样的脚注引用时,它直接跳过不处理。而 Incremark 会:

  1. 识别脚注引用
  2. 解析脚注定义块(可能包含多行内容、代码块、列表等)
  3. 建立引用关系
  4. 生成正确的 AST 结构

这不是性能问题,是功能差异。我们认为完整的脚注支持对于 AI 场景非常重要,所以选择实现它。

真正的性能优势在哪里?

排除脚注相关的文件,看看标准 Markdown 内容的表现:

文件行数IncremarkStreamdown优势
concepts.md9112.0 ms50.5 ms4.2x
comparison.md10920.5 ms74.0 ms3.6x
complex-html-examples.md1479.0 ms58.8 ms6.6x
P0_OPTIMIZATION_REPORT.md16810.1 ms56.2 ms5.6x
OPTIMIZATION_SUMMARY.md39119.1 ms208.4 ms10.9x
test-md-01.md91687.7 ms1441.1 ms16.4x

结论:对于标准 Markdown 内容,文档越大,Incremark 的优势越明显。

为什么会有这样的差距?

这是 O(n) vs O(n²) 算法复杂度的直接体现。

传统解析器(Streamdown、ant-design-x、markstream-vue)每次接收新的 chunk 都要重新解析整个文档

Chunk 1: 解析 100 字符
Chunk 2: 解析 200 字符 (100 旧 + 100 新)
Chunk 3: 解析 300 字符 (200 旧 + 100 新)
...
Chunk 100: 解析 10,000 字符

总工作量: 100 + 200 + 300 + ... + 10000 = 5,050,000 次字符处理

Incremark 的增量解析只处理新内容:

Chunk 1: 解析 100 字符 → 缓存稳定块
Chunk 2: 仅解析 ~100 新字符
Chunk 3: 仅解析 ~100 新字符
...
Chunk 100: 仅解析 ~100 新字符

总工作量: 100 × 100 = 10,000 次字符处理

差距是 500 倍。这就是为什么 18KB 的文档能快 16 倍以上。

功能对等性

我们努力确保两个引擎在功能上保持一致:

功能Marked 引擎Micromark 引擎
GFM(表格、删除线、自动链接)
数学公式($...$$$...$$
自定义容器(:::tip 等)
HTML 元素解析
脚注
打字机动画
增量更新

切换引擎

引擎选择是在初始化时进行的,而非运行时。这是为了 tree-shaking 优化而设计的。

为什么不支持运行时切换?

为了确保最优的打包体积:

  • 默认导入只包含 marked 引擎
  • micromark 引擎需要单独导入
  • 这让打包工具可以 tree-shake 未使用的引擎

如何切换引擎

vue
<script setup>
import { ref } from 'vue'
import { IncremarkContent } from '@incremark/vue'

const content = ref('')
const isFinished = ref(false)

// 引擎在初始化时选择
// 使用 marked(默认):无需额外导入
// 使用 micromark:导入 MicromarkAstBuilder
</script>

<template>
  <!-- 默认使用 marked 引擎 -->
  <IncremarkContent 
    :content="content" 
    :is-finished="isFinished"
    :incremark-options="{ gfm: true, math: true }"
  />
</template>

使用 Micromark 引擎

要使用 micromark,从单独的引擎入口导入 MicromarkAstBuilder

ts
import { createIncremarkParser } from '@incremark/core'
import { MicromarkAstBuilder } from '@incremark/core/engines/micromark'

// 使用 micromark 引擎创建解析器
const parser = createIncremarkParser({
  astBuilder: MicromarkAstBuilder,
  gfm: true,
  math: true
})

⚠️ Tree-shaking 说明:从 @incremark/core/engines/micromark 导入只会将 micromark 添加到你的 bundle 中。默认导入只保留 marked。

扩展引擎

两个引擎都支持自定义扩展。详见 扩展指南

ts
// 自定义 marked 扩展示例
import { createCustomExtension } from '@incremark/core'

const myExtension = createCustomExtension({
  name: 'myPlugin',
  // ... 扩展配置
})

总结与建议

方面MarkedMicromark
解析速度⚡⚡⚡⚡⚡⚡⚡⚡
包体积📦 更小📦 较大
CommonMark 兼容性✅ 良好✅ 完美
内置扩展✅ 脚注、数学公式、容器✅ 通过插件
插件生态🔧 成长中🔧 成熟
推荐场景流式 AI、实时渲染静态文档、严格规范

我们的建议

  1. 大多数场景:使用默认的 Marked 引擎,它已经足够好
  2. 遇到解析问题:如果某些边界情况 Marked 处理不好,尝试切换到 Micromark
  3. 极端性能需求:Marked 引擎是你的最佳选择
  4. 严格规范需求:Micromark 引擎更适合你

我们会持续优化两个引擎,确保它们都能为你提供最好的体验。