今天发布了 Incremark 0.3.0:双引擎架构 + 完整插件生态, AI 流式渲染的终极方案
Incremark 0.3.0 发布:双引擎架构 + 完整插件生态,AI 流式渲染的终极方案
距离上次发帖已经过了一段时间,这段时间我一直在打磨 incremark ,今天正式发布 0.3.0 版本。这次更新的核心是双引擎架构——你可以在极速的 marked 和稳定的 micromark 之间自由切换,同时享受完整的插件生态。
先说结论
md 来源:本地随机抽取的 38 个文件,最大只有 18kb ,文件更大的时候,这个差距只会随指数拉大,没有专门写测试 markdown 文件,就拉了文档中的一些 md 以及使用 cursor 时候生成的一些文档。可查看文档看详细数据 详细数据
| 对比方案 | 平均优势 | 最大差距 |
|---|---|---|
| vs Streamdown | 约 6.1 倍 | 16.4x |
| vs ant-design-x | 约 7.2 倍 | 18.9x |
| vs markstream-vue | 约 28.3 倍 | 65.6x |
基于 38 个真实 markdown 文档( 6,484 行,128.55 KB )的基准测试结果。
在线体验:
- Vue 演示: https://incremark-vue.vercel.app/
- React 演示: https://incremark-react.vercel.app/
- Svelte 演示: https://incremark-svelte.vercel.app/
- 文档: https://www.incremark.com/
为什么要做双引擎?
上次发帖后收到很多反馈,主要集中在两个方向:
- “能不能更快?” —— 性能党
- “能不能支持 xxx 语法?” —— 功能党
这两个需求看似矛盾,但我找到了一个优雅的解决方案:双引擎架构。
Marked 引擎(默认)—— 极速派
// 默认使用 marked ,可以省略
<IncremarkContent
:content="content"
:is-finished="isFinished"
:incremark-options="{ engine: 'marked' }"
/>
特点:
- 🚀 极速:针对流式场景深度优化
- 🔧 自研扩展:我们为 marked 补齐了脚注、数学公式、自定义容器、内联 HTML 解析
- ⚡ Tree-shaking 友好:只打包你用到的功能
Micromark 引擎 —— 稳定派
<IncremarkContent
:content="content"
:is-finished="isFinished"
:incremark-options="{ engine: 'micromark' }"
/>
特点:
- ✅ 严格 CommonMark:100% 规范兼容
- 🔌 丰富生态:直接使用 micromark/mdast 社区的所有插件
- 🛡️ 稳定可靠:久经考验的解析器
Tree-shaking 优化
为了打包体积,我们做了 tree-shaking 优化:
// 默认只打包 marked (极速模式)
import { createIncremarkParser } from '@incremark/core'
const parser = createIncremarkParser({ gfm: true })
// 如果需要 micromark ,单独导入
import { MicromarkAstBuilder } from '@incremark/core/engines/micromark'
const parser = createIncremarkParser({
astBuilder: MicromarkAstBuilder,
gfm: true
})
这样你的项目只会打包你实际使用的引擎,不用担心 bundle 体积问题。
我们为 Marked 做了什么增强?
原生 marked 不支持很多 AI 场景常用的语法,我们通过自研扩展补齐了这些能力:
| 功能 | 原生 Marked | Incremark Marked | Streamdown |
|---|---|---|---|
| 脚注 | ❌ | ✅ 完整 GFM 脚注 | ❌ |
| 数学公式 | ❌ | ✅ $...$ 和 $$...$$ |
⚠️ 部分 |
| 自定义容器 | ❌ | ✅ :::tip、:::warning |
❌ |
| 内联 HTML 解析 | ⚠️ 基础 | ✅ 完整 HTML 树 | ⚠️ 基础 |
这解释了为什么在某些基准测试中 Incremark 看起来”更慢”——因为我们在做更多的事情:
| 文件 | Incremark | Streamdown | 说明 |
|---|---|---|---|
| footnotes.md | 1.7 ms | 0.2 ms | Streamdown 跳过了脚注解析 |
| FOOTNOTE_FIX_SUMMARY.md | 22.7 ms | 0.5 ms | 同上 |
这是功能差异,不是性能问题。 Streamdown 跳过不支持的语法所以看起来更快,而 Incremark 完整解析了所有内容。
完整基准测试数据
为了让大家心里有底,我把 38 个测试文件的完整数据都放出来:
| 文件 | 行数 | 大小 | Incremark | Streamdown | ant-design-x | markstream-vue |
|---|---|---|---|---|---|---|
| introduction.md | 34 | 1.57 KB | 5.6 ms | 4.5 ms | 12.8 ms | 57.5 ms |
| quick-start.md | 71 | 3.14 KB | 12.8 ms | 26.7 ms | 37.9 ms | 214.2 ms |
| concepts.md | 91 | 4.38 KB | 12.0 ms | 50.5 ms | 51.5 ms | 287.5 ms |
| comparison.md | 109 | 5.39 KB | 20.5 ms | 94.9 ms | 85.2 ms | 418.9 ms |
| OPTIMIZATION_SUMMARY.md | 391 | 8.90 KB | 19.1 ms | 208.4 ms | 340.3 ms | 1685.7 ms |
| BLOCK_TRANSFORMER.md | 489 | 9.24 KB | 75.7 ms | 320.9 ms | 619.9 ms | 2268.7 ms |
| test-md-01.md | 916 | 17.67 KB | 87.7 ms | 1441.1 ms | 1656.9 ms | 7927.9 ms |
规律很明显:文档越长,Incremark 的优势越大。 这就是 O(n) vs O(n²) 的威力。
核心架构一览
┌─────────────────────────────────────────────────────────────────┐
│ IncremarkContent │
│ (声明式组件,处理 content/stream 输入) │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ IncremarkParser │
│ ┌─────────────────────────────────────────────────────────────┐
│ │ 双引擎 AST 构建器 │
│ │ ┌──────────────────┐ ┌──────────────────┐ │
│ │ │ MarkedAstBuilder│ │MicromarkAstBuilder│ │
│ │ │ (默认,极速) │ │ (稳定,严格) │ │
│ │ └──────────────────┘ └──────────────────┘ │
│ └─────────────────────────────────────────────────────────────┘
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ BlockTransformer │
│ (打字机效果,字符级增量渲染) │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Vue │ │ React │ │ Svelte │ │
│ │ 组件库 │ │ 组件库 │ │ 组件库 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘
最简使用方式
我们推荐使用 IncremarkContent 组件,这是最简单的方式:
<script setup>
import { ref } from 'vue'
import { IncremarkContent } from '@incremark/vue'
const content = ref('')
const isFinished = ref(false)
// 处理 AI 流式输出
async function handleStream(stream) {
content.value = ''
isFinished.value = false
for await (const chunk of stream) {
content.value += chunk
}
isFinished.value = true
}
</script>
<template>
<IncremarkContent
:content="content"
:is-finished="isFinished"
:incremark-options="{ gfm: true, math: true }"
/>
</template>
新增 Svelte 支持
应社区要求,我们新增了 Svelte 5 支持:
pnpm add @incremark/svelte
<script lang="ts">
import { IncremarkContent } from '@incremark/svelte'
let content = $state('')
let isFinished = $state(false)
</script>
<IncremarkContent {content} {isFinished} />
致 V2EX 社区
说实话,这个项目的 star 大部分应该都是 V2EX 的朋友们贡献的。上次发帖后收到了很多有价值的反馈:
- @Leon6868 提的淡入动画 → 已实现
effect: 'fade-in' - @weareoutman 提的 memo render → 我们的 BlockTransformer 就是干这个的
- 还有很多关于性能、兼容性的讨论
感谢大家的支持和反馈,让这个项目越来越好。
资源链接
- 📚 文档: https://www.incremark.com/
- 🎮 在线演示( Vue ): https://incremark-vue.vercel.app/
- 🎮 在线演示( React ): https://incremark-react.vercel.app/
- 🎮 在线演示( Svelte ): https://incremark-svelte.vercel.app/
- 💻 GitHub: https://github.com/kingshuaishuai/incremark
如果觉得有用,欢迎 star ⭐️
有任何问题或建议,欢迎在评论区讨论,或者直接提 issue 。