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

uni-render

v0.0.1-alpha.4

Published

让 UniApp 支持 Vue render 函数(h 函数)开发,兼容 H5 和微信小程序

Readme

uni-render

npm version npm downloads license

让 UniApp 支持 Vue 渲染函数(h 函数)开发,兼容微信小程序

🎯 核心特性

  • 兼容微信小程序:通过 eventId 映射,不传递函数
  • 响应式支持:数据变化自动更新视图
  • 简单 API:只需 useRender + 响应式桥接
  • 纯 JSON 通信:RenderNode 可通过 setData 传递

📦 安装

npm install uni-render
# 或
pnpm add uni-render

🚀 快速开始

<template>
  <render-component :node="node" />
</template>

<script setup>
import { ref as vueRef } from 'vue'
import { ref, h, useRender, watch } from 'uni-render'

// 定义内部组件
const InnerComponent = {
  setup() {
    const count = ref(0)
    
    return () => h('view', { class: 'counter' }, [
      h('text', {}, `计数: ${count.value}`),
      h('button', { onClick: () => count.value++ }, '+1')
    ])
  }
}

// 使用 Custom Renderer
const nodeInternal = useRender(InnerComponent)

// 桥接到 mp-vue
const node = vueRef(nodeInternal.value)
watch(() => nodeInternal.value, (newVal) => {
  node.value = newVal
}, { deep: true })
</script>

🔍 工作原理

架构(兼容微信小程序)

┌──────────────────────────────────────────────────────────────┐
│  逻辑层 (JSCore)                                             │
├──────────────────────────────────────────────────────────────┤
│  1. useRender 执行 render 函数                               │
│  2. VNode → RenderNode(纯 JSON,事件用 eventId 代替)       │
│  3. 事件处理器存入 Map                                        │
│  4. 响应式变化 → 自动重新生成 RenderNode                      │
└────────────────────────┬─────────────────────────────────────┘
                         │ setData({ node: RenderNode })
                         │ ← 纯 JSON,可以传递
                         ▼
┌──────────────────────────────────────────────────────────────┐
│  渲染层 (WebView)                                            │
├──────────────────────────────────────────────────────────────┤
│  1. RenderComponent 接收 RenderNode                          │
│  2. 递归渲染为原生组件                                        │
│  3. 事件触发 → renderEvent(eventId)                          │
└──────────────────────────────────────────────────────────────┘

RenderNode 数据结构

interface RenderNode {
  id: number              // 节点 ID
  type: string            // 'view' | 'text' | 'button' | ...
  props: {
    class?: string
    style?: string
    bindtap?: string      // 事件 ID,如 'e0'
    // ... 其他属性
  }
  children: RenderNode[]
  text?: string           // 文本内容
}

关键:RenderNode 是纯 JSON,没有函数,可以安全地通过 setData 传递。

📐 API

useRender(componentOrRenderFn)

使用 Custom Renderer 渲染组件,返回响应式 RenderNode。

// 组件定义模式
function useRender(component: Component): ComputedRef<RenderNode>

// 渲染函数模式
function useRender(renderFn: () => VNode): ComputedRef<RenderNode>

特性

  • 自动在组件卸载时清理事件
  • 支持组件定义和渲染函数两种模式

renderEvent(eventId, event)

触发渲染事件(供 RenderComponent 调用)。

function renderEvent(eventId: string, event?: any): void

🎨 支持的组件

| 类型 | 说明 | |------|------| | view | 容器 | | text | 文本 | | button | 按钮 | | input | 输入框 | | image | 图片 |

🔧 事件支持

| Vue 事件 | 小程序事件 | |----------|-----------| | onClick | bindtap | | onTap | bindtap | | onLongPress | bindlongpress | | onInput | bindinput | | onChange | bindchange | | onFocus | bindfocus | | onBlur | bindblur |

📝 完整示例

<template>
  <view class="container">
    <text class="title">计数器</text>
    <render-component :node="node" />
  </view>
</template>

<script setup>
import { ref as vueRef } from 'vue'
import { ref, h, useRender, watch, computed } from 'uni-render'

// 定义内部组件
const CounterComponent = {
  setup() {
    const count = ref(0)
    const double = computed(() => count.value * 2)
    
    return () => h('view', { class: 'counter' }, [
      h('text', { class: 'count' }, `计数: ${count.value}`),
      h('text', { class: 'double' }, `双倍: ${double.value}`),
      h('view', { class: 'buttons' }, [
        h('button', { onClick: () => count.value-- }, '-1'),
        h('button', { onClick: () => count.value++ }, '+1')
      ])
    ])
  }
}

// 使用 Custom Renderer
const nodeInternal = useRender(CounterComponent)

// 桥接到 mp-vue
const node = vueRef(nodeInternal.value)
watch(() => nodeInternal.value, (v) => node.value = v, { deep: true })
</script>

🌍 平台兼容性

| 平台 | 支持 | |------|------| | H5 | ✅ | | 微信小程序 | ✅ | | 支付宝小程序 | ✅ | | 其他小程序 | ✅ |

🔬 核心技术实现

Custom Renderer 架构

基于 Vue createRenderer 实现独立的渲染器,将 Vue 组件渲染为纯 JSON(RenderNode)。

Vue 组件 (runtime-core)
       ↓ createRenderer
InternalNode(内部节点树)
       ↓ toRenderNode
RenderNode(纯 JSON)
       ↓ RenderComponent
真实 UI

响应式桥接

问题:运行在 @vue/runtime-core 中,需要与 mp-vue 的模板系统通信。

方案:两套响应式系统 + watch 手动同步

// 内部系统(@vue/runtime-core)
const nodeInternal = useRender(InnerComponent)

// 外部系统(mp-vue)
const node = vueRef(nodeInternal.value)

// 桥接:监听内部变化 → 同步到外部
watch(() => nodeInternal.value, (newVal) => {
  node.value = newVal  // 触发 mp-vue 模板更新
}, { deep: true })

事件系统(双 Map 架构)

问题vOn 需要在 mp-vue 组件上下文中调用,但 Custom Renderer 运行在独立的上下文。

方案:双 Map 事件系统

// 1. 全局事件表:eventId → handler
const eventRegistry = new Map<string, Function>()

// 2. 组件事件表:scopeId → Set<eventId>
const scopeRegistry = new Map<string, Set<string>>()

// 注册事件(带作用域)
export function registerEvent(handler: Function, scopeId?: string): string {
  const eventId = `__mp_evt_${++counter}__`
  eventRegistry.set(eventId, handler)
  if (scopeId) {
    scopeRegistry.get(scopeId)!.add(eventId)
  }
  return eventId
}

// 触发事件
export function renderEvent(eventId: string, event?: any): void {
  eventRegistry.get(eventId)?.(event)
}

// 清理作用域事件
export function clearEventScope(scopeId: string): void {
  const eventIds = scopeRegistry.get(scopeId)
  eventIds?.forEach(id => eventRegistry.delete(id))
  scopeRegistry.delete(scopeId)
}

事件流程

h('view', { onClick: handler })
  ↓ patchProp
registerEvent(handler, scopeId) → eventId
  ↓
RenderNode: { props: { bindtap: eventId, 'data-eid-tap': eventId }}
  ↓
RenderComponent 渲染 → 用户点击
  ↓
onTap → renderEvent(eventId) → handler()

核心文件

| 文件 | 作用 | |------|------| | mpRenderer.ts | Custom Renderer 实现(useRender, toRenderNode) | | eventRegistry.ts | 双 Map 事件系统(页面隔离) | | types.ts | RenderNode 类型定义 |

📄 License

MIT