npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

taro-markstream

v1.0.0

Published

Taro 4 streaming Markdown renderer for AI chat -- minimal Vue 3 edition

Readme

taro-markstream

Taro 4 流式 Markdown 渲染器 — 面向 AI 聊天场景的最小 Vue 3 版本

安装

pnpm add taro-markstream

确保项目安装了必要的 peer 依赖:

pnpm add vue@^3 @tarojs/components@^4 @tarojs/taro@^4

使用

基础渲染

<template>
  <MarkdownRender
    content="# Hello\n**World**"
    @link-click="handleLink"
  />
</template>

<script setup lang="ts">
import { MarkdownRender } from 'taro-markstream/vue'
import 'taro-markstream/vue/index.css'

function handleLink(e: { href: string }) {
  Taro.setClipboardData({ data: e.href })
}
</script>

流式打字效果

<template>
  <MarkdownRender :content="visible" :final="final" />
</template>

<script setup lang="ts">
import { MarkdownRender, useSmoothMarkdownStream } from 'taro-markstream/vue'

const { visible, final, enqueue, finish } = useSmoothMarkdownStream({
  minCharsPerSecond: 30,
  maxCharsPerSecond: 200,
})

// AI 流式返回时逐个调用
enqueue(chunk)

// 流结束时调用
finish()
</script>

API

MarkdownRender Props

| Prop | 类型 | 默认值 | 说明 | |------|------|--------|------| | content | string | '' | Markdown 原文 | | final | boolean | false | 标记此次渲染为最终结果(关闭流式解析的容错模式) |

MarkdownRender Events

| 事件 | 载荷 | 说明 | |------|------|------| | @link-click | { href: string } | 用户点击链接时触发 | | @image-click | { src: string } | 用户点击图片时触发 |

useSmoothMarkdownStream Options

| 选项 | 类型 | 默认值 | 说明 | |------|------|--------|------| | minCharsPerSecond | number | 30 | 最小打字速度(字符/秒) | | maxCharsPerSecond | number | 200 | 最大打字速度(字符/秒) | | batchSize | number | 5 | 每次调度最多吐出的字符数 | | scheduleInterval | number | 50 | 调度间隔(毫秒) | | startDelay | number | 0 | 首次输出前的延迟(毫秒) |

useSmoothMarkdownStream 返回值

| 返回值 | 类型 | 说明 | |--------|------|------| | source | Ref<string> | 已入队的全部原文 | | visible | Ref<string> | 当前已打出的可见文本 | | done | Ref<boolean> | 是否已调用 finish() | | final | ComputedRef<boolean> | done && caughtUp,适合直接传给 MarkdownRender:final | | caughtUp | ComputedRef<boolean> | 是否已追上入队速度 | | pendingChars | ComputedRef<number> | 尚未打出的字符数 | | enqueue(chunk) | (chunk: string) => void | 入队一段文本 | | finish(opts?) | (opts?: { flush?: boolean }) => void | 标记流结束;flush: true 立即吐出所有剩余 | | flush() | () => void | 立即吐出所有待输出文本 | | reset(init?) | (initialMarkdown?: string) => void | 重置状态,可选初始文本 | | pause() | () => void | 暂停输出 | | resume() | () => void | 恢复输出 |

特性 / 支持的功能

✅ 已支持的 Markdown 语法

| 语法 | 渲染方式 | |------|----------| | # ~ ###### 标题 | <h1> ~ <h6> | | **粗体** | <strong> | | *斜体* | <em> | | ~~删除线~~ | <del> | | `行内代码` | <code> | | ![alt](src) 图片 | <img>(rich-text 渲染) | | [text](url) 链接 | <a> → 触发 link-click 事件 | | `` ```code```` 代码块 | <pre><code> | | > blockquote 引用 | <blockquote> | | - / * 无序列表 | <ul><li> | | 1. 有序列表 | <ol><li> | | --- 分割线 | <hr> | | \| 表 \| 表格 | <table> | | \n 换行 | <br> |

✅ 流式支持

  • useSmoothMarkdownStream composable → 基于 markstream-core 的平滑打字机效果
  • 流式增量解析 → 每次 content 变化时 stream-markdown-parser 增量解析
  • 事件驱动 → @link-click 处理链接交互

✅ 样式

  • 内置基础 CSS(可覆盖)
  • 750rpx 设计稿适配
  • 支持通过宿主页面的样式覆盖

❌ 不支持的功能

markstream-vue 原版相比,以下功能已移除

渲染器

| 功能 | 原因 | |------|------| | KaTeX 数学公式 | 小程序无 DOM,不可用 | | Mermaid 图形 | 需要 Web Worker + SVG 渲染 | | D2 图形 | 需要 Web Worker | | AntV Infographic | 需要 DOM | | Monaco Editor | 浏览器专属 | | Shiki 代码高亮 | 需要 Web Worker | | HTML 富文本(v-html) | 小程序无 innerHTML,使用 <rich-text> |

虚拟滚动与性能优化

| 功能 | 原因 | |------|------| | useMarkstreamVirtualAdapter | 聊天场景不需要内部文档虚拟化 | | MarkstreamVirtualTimeline | 外层时间线虚拟滚动另由宿主实现 | | 节点批量渲染调度器 | 单条消息节点数有限,无需批次调度 | | 高度测量 / ResizeObserver | 无虚拟滚动就不需要高度测量 | | 视口优先级调度 | 由 IntersectionObserver 替代 | | 延迟渲染 / deferNodes | 不支持 |

组件

| 功能 | 原因 | |------|------| | CodeBlockNode (Monaco) | 编辑器不可用 | | MathBlockNode / MathInlineNode | KaTeX 不可用 | | MermaidBlockNode | 不可用 | | D2BlockNode | 不可用 | | InfographicBlockNode | 不可用 | | Tooltip (Floating UI) | 弹窗不可用 | | MarkdownCodeBlockNode | Shiki 不可用 | | MarkstreamVirtualTimeline | 已移除 | | AdmonitionNode / VmrContainerNode | 高级语法支持 | | Emoji 解析 | 纯文本显示 | | Checkbox 渲染 | 交互组件不可用 | | Footnote / Reference | 高级语法 | | Subscript / Superscript | HTML 标签受限 | | Insert / Highlight | HTML 标签受限 |

平台特性

| 功能 | 原因 | |------|------| | Web Workers | 小程序不支持 | | requestAnimationFrame | 使用 setTimeout 替代,50ms+ 间隔 | | document / HTMLElement | 小程序无 DOM | | ResizeObserver | 小程序不支持 | | Intl.Segmenter | 使用内置降级(Array.from) | | performance.now() | 小程序中不可靠,使用 Date.now() | | <style scoped> 深度选择器 | 仅支持 class 选择器 |

架构

taro-markstream
├── stream-markdown-parser  (复用,框架无关)
├── markstream-core          (复用,仅适配 rAF → setTimeout)
└── Vue 组件层
    ├── useSmoothMarkdownStream  (composable)
    ├── MarkdownRender           (核心组件)
    └── nodes/renderer.ts        (~15 种节点 VNode 渲染)

常见陷阱

为什么不能用 <rich-text>

微信小程序没有动态 DOM 注入机制,Taro 在编译期将组件调用树静态分析后生成 base.wxml 模板文件(如 tmpl_0_view)。

<rich-text nodes={html}> 的问题: Taro 编译器只看到 <rich-text>,无法预知运行时 HTML 字符串会解出什么标签。若 HTML 中含有 <a>(内部编号 62),Taro 因未扫描到显式的 <a>h('a') 调用而跳过该模板的生成,导致微信报错 Template tmpl_0_62 not found

<MarkdownRender> 使用 VNode 递归渲染的理由: 将 Markdown AST 的每个节点显式映射为 h('view')h('text')h('image') 等调用。Taro 编译期扫描到这些调用,就会在 base.wxml 中生成对应的标准模板(tmpl_0_view 等),运行时永远不会出现"模板未找到"的断层。同时规避了 rich-text 二次解析的性能损耗,并且链接点击可通过 @link-click 自由定制跳转逻辑。

开发

# 在 monorepo 中
pnpm install
pnpm --filter taro-markstream build

# 启动 playground
pnpm --filter playground-taro dev:weapp

License

MIT