
距离上次发帖已经过了一段时间,这段时间我一直在打磨 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 10 天前 体验了下很棒,性能很强 |
2 1244943563 OP @lizhenda 感谢感谢,今天因为这个 issue https://github.com/kingshuaishuai/incremark/issues/4 做了性能优化,在渲染上保持最少的更新,性能也拉满了。 目前算是接近稳定了,社区再跑一段时间修修 bug 再发 1.x |