距离上次发帖已经过了一段时间,这段时间我一直在打磨 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 )的基准测试结果。
在线体验:
上次发帖后收到很多反馈,主要集中在两个方向:
这两个需求看似矛盾,但我找到了一个优雅的解决方案:双引擎架构。
// 默认使用 marked ,可以省略
<IncremarkContent
:content="content"
:is-finished="isFinished"
:incremark-options="{ engine: 'marked' }"
/>
特点:
<IncremarkContent
:content="content"
:is-finished="isFinished"
:incremark-options="{ engine: 'micromark' }"
/>
特点:
为了打包体积,我们做了 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 不支持很多 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 5 支持:
pnpm add @incremark/svelte
<script lang="ts">
import { IncremarkContent } from '@incremark/svelte'
let content = $state('')
let isFinished = $state(false)
</script>
<IncremarkContent {content} {isFinished} />
说实话,这个项目的 star 大部分应该都是 V2EX 的朋友们贡献的。上次发帖后收到了很多有价值的反馈:
effect: 'fade-in'感谢大家的支持和反馈,让这个项目越来越好。
如果觉得有用,欢迎 star ⭐️
有任何问题或建议,欢迎在评论区讨论,或者直接提 issue 。
1
lizhenda 23 小时 25 分钟前
体验了下很棒,性能很强
|
2
1244943563 OP @lizhenda 感谢感谢,今天因为这个 issue https://github.com/kingshuaishuai/incremark/issues/4 做了性能优化,在渲染上保持最少的更新,性能也拉满了。
目前算是接近稳定了,社区再跑一段时间修修 bug 再发 1.x |