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

@arms/miniapp-replay

v0.1.0

Published

微信小程序 Session Replay SDK:协议冻结契约 + 零上报的 recorder 运行时 + 构建期 WXML 编译器(compiler,产出 templateMap)+ 浏览器端播放器(player,ESM/IIFE 双产物)

Readme

@arms/miniapp-replay

微信小程序 Session Replay SDK:构建期编译器解析 WXML 生成 templateMap,运行时录制页面数据与交互产出标准化事件流;回放期提供浏览器端播放器(@arms/miniapp-replay/player),将录制事件流还原为可视化回放。

特性

  • 零依赖、零上报:dependencies 为空;录制器只产事件(emit 回调),不内置任何网络/存储/分段逻辑,上报策略完全由接入方掌控
  • 事件流式 API:record(options) 返回 stop 函数、addCustomEvent(tag, payload) 注入业务事件,所有数据经单一 emit 回调流出
  • 构建期解析 + 运行时录制:compiler 在构建期静态分析 WXML(条件渲染、模板 AST),运行时零模板解析开销
  • 条件渲染状态跟踪:基于 templateMap 的 depIndex 增量求值 wx:if/elif/else 真假状态,状态随事件流携带
  • 回放自包含:templateMap 只存在于用户工程内(录制运行时消费,永不上传服务端);回放所需内容全部由事件流携带,无带外依赖
  • 分包支持:subPackages/subpackages 页面、custom-tab-bar、项目内自定义组件全量收敛
  • JSON-safe 冻结协议:事件结构可 JSON.stringify/parse 无损往返;枚举值只增不改,新旧数据双向兼容
  • 浏览器端播放器:./player 子路径提供 IIFE(window.MiniappReplay)+ ESM 双产物,同一套冻结协议下「录制 → 回放」闭环,支持 checkpoint seek 就近重建与 Custom 事件时间轴标记

架构

两阶段工作流:编译期产物留在用户工程内驱动录制,运行时事件流自包含携带回放所需全部内容。

graph LR
    subgraph 构建期
        A[WXML 源码] --> B["compiler<br/>(miniapp-replay-compile)"]
        B --> C["templateMap.generated.js<br/>(CJS 产物入用户工程,不上传)"]
    end
    subgraph 运行时
        C --> D["record({ emit, templateMap })<br/>(recorder)"]
        E["Page / Component<br/>setData + 交互 + 生命周期"] --> D
        D --> F["emit(eventWithTime)<br/>(事件流交接入方)"]
    end
    subgraph 回放期
        H["player<br/>(Replayer + renderers)"]
        H --> I["浏览器回放页<br/>(window.MiniappReplay)"]
    end
    F --> G["上报 / 存储 / 分段<br/>(接入方自行实现)"]
    G --> H

recorder 通过 Hook 全局 Page/Component 构造函数工作:navigation observer 拦截页面生命周期(onShow → Meta、onReady → FullSnapshot、onHide/onUnload → viewEnd),data observer 包装 setData 产出 Mutation 增量,interaction observer 拦截页面方法中的事件对象产出 Touch/Input/Scroll。传入 templateMap 后,条件渲染状态(conditionStates)随快照与增量事件一起流出。

快速开始

第一步:构建期接入(生成 templateMap)

安装为运行时依赖(--save,即写入 dependencies):运行时入口 require('@arms/miniapp-replay') 必须位于 dependencies,才会被微信开发者工具「构建 npm」拷贝进 miniprogram_npm(devDependencies 不参与拷贝);compiler CLI 则通过 npx 调用,无需额外安装方式:

npm install @arms/miniapp-replay --save

方式一:CLI(推荐,接入构建脚本)

npx miniapp-replay-compile --project . \
  --output ./utils/templateMap.generated.js

方式二:编程 API(自定义构建脚本)

const { generateTemplateMap } = require('@arms/miniapp-replay/compiler');

const { templateMap, warnings, stats } = generateTemplateMap('/path/to/your/miniapp-project');
// templateMap: { version, hash, generatedAt, pages }
// warnings:    跳过项(npm 三方组件、wxs/template/import/include/slot 等)
// stats:       { pageCount, conditionCount, warningCount }

产物为单一 CommonJS 模块(compact JSON,调试可加 --pretty):含 conditionRenders(条件为等效表达式原文,AST/depIndex 由录制运行时解析派生)与段六 template、内容 hash,提交进工程或接入 CI 均可,内容不变则 hash 稳定。templateMap 只存在于用户工程内供录制运行时消费,永不上传服务端(安全约束;回放所需内容全部由事件流携带,定案见 docs/template-map-runtime-only-2026-09-29.md)。

升级 SDK 后必须重跑 compiler 重新生成 templateMap:recorder 仅接受与自身同版本的 map(当前 1.1.0),版本不匹配时启动期告警一次并整体旁路模板通道(录制事件流不受影响,仅丢失条件状态与模板驱动 DOM 通道)。

第二步:运行时接入(app.js 启动录制)

// app.js
const { record, addCustomEvent } = require('@arms/miniapp-replay');
const templateMap = require('./utils/templateMap.generated.js');

// 事件缓冲与批量上报:本包只产出事件,策略完全由接入方实现。
// 下面是一个最小可行示例(收集到数组 + wx.request 批量上报)。
const buffer = [];
let flushTimer = null;
const BATCH_SIZE = 50;
const FLUSH_INTERVAL_MS = 10 * 1000;

function flush() {
  flushTimer = null;
  if (buffer.length === 0) return;
  const batch = buffer.splice(0, buffer.length);
  wx.request({
    url: 'https://your-server.example.com/api/replay/events',
    method: 'POST',
    data: {
      sessionId: 'your-session-id', // 会话 ID 的生成与管理由接入方决定
      events: batch,
    },
    fail() {
      // 失败重试 / 本地落盘 / 丢弃等策略由接入方决定
    },
  });
}

App({
  onLaunch() {
    this.stopReplay = record({
      emit(event) {
        buffer.push(event);
        if (buffer.length >= BATCH_SIZE) {
          flush();
        } else if (flushTimer === null) {
          flushTimer = setTimeout(flush, FLUSH_INTERVAL_MS);
        }
      },
      templateMap, // 传入后启用条件渲染状态跟踪
    });
  },
});

微信开发者工具中需先执行「构建 npm」(工具 → 构建 npm),运行时经 miniprogram 字段(dist/recorder)解析包入口。

第三步:事件消费

事件交给接入方后,如何持久化、上报、分段、采样完全由接入方决定:

  • emit 回调收到全部事件(eventWithTime 结构,见事件结构参考),事件已保证 timestamp 存在且 JSON-safe
  • 首个页面事件通常是 Meta(页面 onShow),随后 FullSnapshot(onReady 全量快照),之后是增量事件流
  • 业务关键节点可用 addCustomEvent 打标,与页面事件共用同一条时间线:
const { addCustomEvent } = require('@arms/miniapp-replay');

addCustomEvent('checkout', { step: 3, orderId: 'A001', amount: 99 });

完整示例

examples/ 下是一套可直接跑通的端到端示例,覆盖「录制 → 上报 → 存储 → 回放」全链路:

  • examples/replay-server —— Express 示例服务端,同时提供电商业务接口、Session Replay 事件存取与浏览器回放页
  • examples/wx-native-shop —— 微信原生小程序(零组件库),14 分类 × 12 商品,已接入录制与埋点

API 参考

record(options)

启动录制,返回 stop 函数(调用即停止,幂等)。

| 字段 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | emit | (event) => void | —(必填) | 事件回调,所有录制事件经此流出;缺失时抛错 | | templateMap | TemplateMap \| null | null | 编译期模板映射;传入后启用条件渲染状态跟踪(conditionStates)与模板驱动 DOM 通道 | | maskInput | boolean | false | 是否对输入值脱敏(逐字符替换为 *) | | maskDataKeys | string[] | [] | 需脱敏的 setData 字段;匹配末段 key('phone')或完整 path('user.phone'),命中替换为 '***' | | excludeMethods | string[] | [] | 不拦截的页面/组件方法名(性能敏感方法可排除) | | touchMoveThrottleMs | number | 100 | touchmove 节流间隔(ms) | | scrollThrottleMs | number | 200 | 滚动事件节流间隔(ms) | | touchTrailSampleRate | number | 1 | 触摸轨迹采样率 0-1(仅作用于 touchmove;1 为全量采样) | | gestureEnabled | boolean | false | 高级手势识别(swipe/longpress/pinch/rotate/doubleTap 摘要事件) | | layoutSnapshotEnabled | boolean | false | 布局快照校准(setData 回调时机补采关键节点几何信息) | | layoutKeypointSelector | string | '.layout-keypoint' | 布局关键点选择器(配合 layoutSnapshotEnabled) | | checkpointMaxMutations | number | 1000 | checkpoint 触发阈值:自上次快照以来的 mutation 条数 | | checkpointMaxIntervalMs | number | 300000 | checkpoint 触发阈值:距上次快照的时间间隔(ms) | | checkpointMaxBytes | number | 1048576(1MB) | checkpoint 触发阈值:事件流累计字节 | | styleEnabled | boolean | true | 计算样式装订(仅 DOM 通道生效):FullSnapshot 装订 attrs.style、结构变更增量补采;无 wx.createSelectorQuery 能力自动降级 | | debug | boolean | false | 调试日志开关(生产保持 false) |

值保护:Mutation 的 value 超体积阈值截断为 '[TRUNCATED]',序列化失败为 '[SERIALIZE_ERROR]',命中脱敏规则为 '***'。

addCustomEvent(tag, payload)

注入自定义业务事件(EventType.Custom = 5),经当前活跃录制会话的 emit 通道流出,timestamp 自动补齐。

  • tag:非空字符串,非法值或无活跃会话时 console.warn 并忽略(不抛错)
  • payload:可选;为 undefined 时整体省略该字段。不做体积截断/序列化保护,调用方需自行保证 JSON-safe

registerComponentAdapter(tag, AdapterClass) / ComponentAdapter

组件适配器扩展点:为自定义组件/三方组件库注册字段监控,其 setData 变更将产出 ComponentInteraction(source=6)事件。

本包不内置三方组件库适配器(保持零依赖);仅内置 custom-tab-bar 适配器(宿主全局自定义 TabBar 零配置生效)。三方组件(如 TDesign)可自行注册:

const { ComponentAdapter, registerComponentAdapter } = require('@arms/miniapp-replay');

class MyPickerAdapter extends ComponentAdapter {
  // 受监控字段的当前值(data-observer 在 setData 回调时机 diff 并 emit)
  getMonitoredFields() {
    return this.pickData(['value', 'visible', 'columns']);
  }
  // 渲染提示(可选,供回放端选择等效渲染模板);缺省 null
  getComponentHint() {
    return { type: 'picker', library: 'my-ui' };
  }
}

// tag 匹配组件实例 this.is 的任一路径段(从后往前)
registerComponentAdapter('my-picker', MyPickerAdapter);

配套的 unregisterComponentAdapter(tag) 注销已注册的适配器(返回注销前该 tag 是否已注册),适用于测试隔离与运行时动态卸载场景。

子路径导出

| 子路径 | 内容 | | --- | --- | | @arms/miniapp-replay | recorder 运行时主入口:record / addCustomEvent / registerComponentAdapter / unregisterComponentAdapter / ComponentAdapter / CustomTabBarAdapter / EventType / IncrementalSource 与全部协议类型 | | @arms/miniapp-replay/protocol | 协议权威源:枚举常量(EventType / IncrementalSource / MATH_WHITELIST)与全部协议类型,供 player / 深度消费方使用。Node 端专用;小程序端请从主入口 require('@arms/miniapp-replay') 获取 EventType / IncrementalSource 等协议常量(微信「构建 npm」只拷贝 miniprogram 字段指向的 dist/recorder) | | @arms/miniapp-replay/compiler | 构建期编程 API:generateTemplateMap / compileExpression / parseWxml / serializeTemplate / computeHash 等 | | @arms/miniapp-replay/player | 浏览器端播放器:Replayer / VisualRenderer / TouchRenderer / ComponentRenderer / LayoutCalibrator / Timeline 与协议类型。浏览器/打包器专用(仅 ESM,不支持 require);另有 IIFE 产物 dist/player/replay-player.js(全局名 MiniappReplay)+ replay-player.css 供 <script> 直引,见播放器章节 |

bin 命令:miniapp-replay-compile(即 dist/compiler/cli.js)。

播放器(@arms/miniapp-replay/player)

浏览器端回放端(移植自演进原型 miniapp-replay/player):消费 recorder 产出的事件流,将小程序页面数据与交互还原为可视化回放。仅限浏览器/打包器环境使用,不支持 require。

双产物形态

同一套源码两种消费方式:

方式一:IIFE + <script> 直引(回放页集成)

<link rel="stylesheet" href="/path/to/replay-player.css" />
<script src="/path/to/replay-player.js"></script>
<script>
  const { Replayer, VisualRenderer, TouchRenderer, ComponentRenderer, LayoutCalibrator, Timeline } = window.MiniappReplay;
</script>
  • dist/player/replay-player.js:minify 后的 IIFE 产物,全局名 MiniappReplay(挂 6 个类);
  • dist/player/replay-player.css:播放器自身样式(由构建从入口 CSS import 提取);
  • 两个文件名是回放页集成的硬性契约,重命名 entry 需同步宿主页面引用。

方式二:ESM import(打包器集成)

import { Replayer, VisualRenderer } from '@arms/miniapp-replay/player';
import '@arms/miniapp-replay/dist/player/replay-player.css'; // 播放器样式(宿主自行引入)

约定 DOM 结构契约

播放器不创建页面骨架,直接按硬编码 id 查找宿主元素(构造函数 document.getElementById):

| 元素 | 消费方 | 说明 | | --- | --- | --- | | #simulator-canvas | VisualRenderer | 页面主画布(数据等效渲染输出) | | #tabbar-canvas | VisualRenderer / ComponentRenderer | TabBar 渲染通道(优先于适配器等效渲染) | | #current-route | VisualRenderer | 当前路由显示 | | #phone-frame | VisualRenderer / TouchRenderer / ComponentRenderer / LayoutCalibrator | 手机壳容器(输入指示器/覆盖层挂载点) | | #touch-overlay | TouchRenderer | 触摸轨迹覆盖层 | | #event-list | Timeline | 事件列表(虚拟滚动) | | #btn-play | Timeline | 播放/暂停按钮 | | #progress-slider | Timeline | 进度条(<input type="range">) | | #time-display | Timeline | 时间显示 | | .btn-speed[data-speed] | Timeline | 倍速按钮组(可多个) |

LayoutCalibrator 与 ComponentRenderer 会在 #phone-frame 内自动创建各自的独立覆盖层(#layout-calibrator-overlay / #component-layer),无需宿主预置。

主题 CSS 变量

replay-player.css 不定义 :root,全部主题色由宿主页面提供(17 个变量,共 58 处引用):

--accent / --accent-dim / --accent-hover / --bg-hover / --bg-primary / --bg-secondary
--bg-tertiary / --border / --miniapp-primary / --miniapp-primary-light / --phone-bg
--phone-text / --radius / --text-muted / --text-primary / --text-secondary / --transition

Custom 事件时间轴标记

EventType.Custom = 5(addCustomEvent 注入)在 Timeline 事件列表中显示为 🏷️「自定义」条目:描述为 tag: payload预览(payload 序列化后约 20 字符截断;无 payload 仅显示 tag)。各 renderer 对 Custom 事件静默忽略(仅时间轴标记,不参与渲染);未知事件类型走两级兜底(❓ + Type N 标签)。

Replayer API 简表

new Replayer(events, options?)

| options 字段 | 类型 | 说明 | | --- | --- | --- | | onEvent | (event, index) => void | 事件分发回调(含 __RESET__ 信号,见下) | | onTimeUpdate | (currentTime, duration) => void | 播放进度回调(100ms 节流) | | onPlayStateChange | (isPlaying) => void | 播放/暂停状态变化 | | onComplete | () => void | 播放完毕 | | speed | number | 初始倍速(默认 1) |

公开方法:play() / pause() / toggle() / seek(ms)(相对 startTime 偏移)/ seekPercent(0~1) / getProgress() / setSpeed(n) / addEvent(event)(增量实时回放,二分有序插入)/ loadEvents(events) / getMetaData() / destroy()(派生状态归零 + 回调置空,残留监听器再触发 seek 安全早退)。

PlayerEvent 与 __RESET__ 信号:onEvent 收到的是协议事件(AnyReplayEvent)或重置信号 { type: '__RESET__' }(非协议事件)。seek 时 Replayer 按 checkpoint 就近重建:先发 __RESET__ 让渲染器清空派生状态,再从锚点快照起重放到目标时刻;锚点前的跨快照累积通道(Meta / TabBar 快照与变更 / 组件交互)会从头轻量补放,避免丢状态。渲染器接口约定:handleEvent(event: PlayerEvent),遇到 __RESET__ 即重置内部累积。

导出面:Replayer / VisualRenderer / TouchRenderer / ComponentRenderer / LayoutCalibrator / Timeline / TemplateDomRenderer,及 ReplayerOptions / PlayerEvent / ResetSignal 与协议类型(EventType / IncrementalSource / AnyReplayEvent 等)。

事件结构参考

所有事件共享外壳(eventWithTime):

interface ReplayEvent<T> {
  type: EventType;      // 事件类型,见下表
  data: T;              // 载荷,结构由 type(及增量事件的 source)决定
  timestamp: number;    // 事件发生时刻(epoch 毫秒,recorder 出口保证存在)
}

EventType(值冻结)

| 值 | 常量 | 说明 | | --- | --- | --- | | 2 | FullSnapshot | 全量快照:页面/TabBar 的 data 快照 + 设备信息 | | 3 | IncrementalSnapshot | 增量快照:数据变更、触摸、输入、滚动等(按 data.source 细分) | | 4 | Meta | 元信息:页面 onShow 的路由/视口/设备,或 viewEnd: true 离场标记 | | 5 | Custom | 自定义事件:addCustomEvent 注入的业务事件 |

IncrementalSource(值冻结;数值为小程序模型自定义)

| 值 | 常量 | 说明 | | --- | --- | --- | | 0 | Mutation | setData 数据变更 | | 1 | Scroll | 页面滚动 | | 2 | ViewportResize | 视口变化(预留) | | 3 | Touch | 触摸交互(touchstart/touchmove/touchend/tap/longpress) | | 4 | Input | 输入(input/change/blur) | | 5 | LayoutSnapshot | 布局快照校准(增强) | | 6 | ComponentInteraction | 组件适配器上报的组件状态变更(增强) | | 7 | Gesture | 识别后的高级手势摘要(增强) | | 8 | ScrollView | scroll-view 组件内滚动(增强;与页面滚动 source=1 区分) | | 9 | DomMutation | 模板驱动 DOM 通道的结构增量补丁(增强) |

未知 source 值(>= 5)应被旧消费方静默忽略。

载荷示例

FullSnapshot(type=2,onReady 时机):

{
  "type": 2,
  "data": {
    "route": "pages/chat/index",
    "initialData": { "isLoading": false, "inputValue": "", "messages": [] },
    "device": { "platform": "devtools", "model": "iPhone X", "windowWidth": 375, "windowHeight": 700 },
    "conditionStates": { "c0": false, "c1": true, "c2": null }
  },
  "timestamp": 1760000000000
}

Mutation(type=3,source=0;conditionStates 为可选字段,仅在条件真假翻转时以「空 mutations 的补充事件」形式出现):

{
  "type": 3,
  "data": {
    "source": 0,
    "scope": "page",
    "pageRoute": "pages/chat/index",
    "mutations": [{ "path": "inputValue", "value": "你好" }],
    "conditionStates": { "changed": { "c3": true } }
  },
  "timestamp": 1760000001200
}

Meta(type=4,onShow 形态;onHide/onUnload 时为 { "route": "...", "viewEnd": true }):

{
  "type": 4,
  "data": {
    "route": "pages/chat/index",
    "viewport": { "width": 375, "height": 700 },
    "device": { "platform": "devtools", "model": "iPhone X" }
  },
  "timestamp": 1760000000000
}

Custom(type=5):

{
  "type": 5,
  "data": { "tag": "checkout", "payload": { "orderId": "A001", "amount": 99 } },
  "timestamp": 1760000002500
}

Touch(type=3,source=3):

{
  "type": 3,
  "data": {
    "source": 3,
    "pageRoute": "pages/chat/index",
    "type": "tap",
    "timestamp": 1760000002000,
    "target": { "id": "send-btn", "dataset": { "scene": "chat" } },
    "x": 120,
    "y": 640
  },
  "timestamp": 1760000002000
}

协议差异说明

API 形态:与业界主流 session-replay 方案一致的通用形态——record(options) / emit 回调 / 返回 stop 函数 / addCustomEvent(tag, payload);EventType 数值沿用主流约定(FullSnapshot=2 / IncrementalSnapshot=3 / Meta=4 / Custom=5)。

差异(重要,不可与 Web 端 session-replay 播放器直接互通):

| 维度 | Web 端 session-replay 方案 | 本包 | | --- | --- | --- | | 采集对象 | 浏览器 DOM(MutationObserver 等浏览器 API) | 小程序 Page/Component 的 setData 与交互事件 | | FullSnapshot 载荷 | DOM 树序列化(节点/属性/样式) | data 快照(route + initialData),非 DOM 树 | | IncrementalSource 数值 | MouseMove=1 / MouseInteraction=2 / Scroll=3 / Input=5 … | 数值完全不同:Scroll=1 / ViewportResize=2 / Touch=3 / Input=4;5-9 为小程序增强源 | | 事件消费 | 可直接喂 Web 端 session-replay 播放器 | 需按本协议实现 player(IncrementalSource 数值两套体系禁止混用比较) |

同时消费 Web 端 session-replay 数据与本协议数据时,必须按数据来源分别使用各自的枚举常量。

能力边界

以下为当前版本的诚实边界声明(compiler 遇到超界项跳过并计入 warnings,不中断编译):

  • 三方组件库:npm 组件(usingComponents 中的 bare spec,如 tdesign-miniprogram/*)不在分析范围内,跳过并 warning;回放不还原其内部结构。可用 registerComponentAdapter 自行注册适配器补充监控
  • WXML 特性:<wxs>、<template>、<import>、<include>、<slot> 标签跳过;引用 WXS 函数或调用方法的表达式无法解析,对应条件求值恒为 null
  • 悬空 elif/else:前序条件编译失败会传染,合成 condition 保守置 null(null 语义)
  • 上报/存储/分段:本包只产出事件,不含任何 transport/session/segment 逻辑——由接入方在 emit 回调中自行实现(参考实现见 examples/wx-native-shop/utils/replay/reporter.js 与 examples/replay-server)
  • 模板驱动 DOM 通道:FullSnapshot 携带 dom(按 templateMap 渲染的 VNode 树),增量经 IncrementalSource.DomMutation=9 上报结构补丁,player 按 WXML 结构 1:1 还原。边界:无 templateMap 或补丁失步时自动回退启发式数据视图;回放样式由录制运行时计算样式装订提供(SelectorQuery computedStyle → attrs.style,可用 record({ styleEnabled: false }) 关闭;无 id/class 的元素不参与采集,继承与 player 基线兜底;方案与偏差见 docs/template-map-runtime-only-2026-09-29.md)。真机 CPU 基准(R1)模拟器代理复测见 docs/template-render-r1-simulator-bench-2026-09-29.md,真机实测为发布前手动门禁。脱敏提示:DOM 快照携带页面全部静态文本(WXML 内联文案),敏感文案需接入方在上报链路自行脱敏
  • 伪元素绘制的图标不可采集:::before/::after 承载的 icon font 字形对 SelectorQuery 不可见(宿主元素量得 0×0),计算样式装订无法覆盖,回放中此类图标缺失(如示例购物车的删除 ×)。平台 API 层面无采集通道;关键图标建议用 <text>/<image> 等真实元素实现
  • 自定义组件(DOM 通道):项目内组件按 properties 完整语义展开——实例 attached 时登记(properties 框架已并入 data),渲染按「properties 指纹匹配 + 注册顺序兜底」分配,父绑定值变化才传播(组件自 setData 同名属性保留)。已知边界:同路由多实例动态增删交错时指纹可能错配(内容互换级偏差);实例缺失时维持 boundary 占位;slot/抽象节点/npm 三方组件维持边界不展开
  • swiper / 原生组件(DOM 通道):swiper 仅展示首张幻灯片(当前帧下标不在录制数据中,静态近似以避免多图纵向堆叠撑破布局);video/canvas/map 等原生组件以占位盒呈现,不还原内部画面
  • 样式装订补采预算:元素矩形/样式查询落空(视图未就绪)时按 800ms / 2500ms 两次定时补采;仍落空则该作用域样式保持缺失,直至下一个结构补丁触发重采

开发

npm install          # 安装依赖
npm run build        # 构建全部产物(dist/recorder + dist/protocol + dist/compiler + cli.js + dist/player)
npm run build:debug  # 同 build,但不混淆压缩(MINIAPP_REPLAY_DEBUG=1),便于调试产物
npm test             # 全量测试(单测 + 集成 + CLI + dist 冒烟 + player,tests/player 自动切 happy-dom 环境)
npm run typecheck    # TypeScript 类型检查(主 tsconfig + tsconfig.player.json)

发布构建默认对 recorder/protocol/player 产物做 esbuild 混淆压缩(空白/注释剔除、语法压缩、局部标识符重命名;属性名与协议字符串契约不受影响),compiler/CLI 为本地构建期 Node 工具刻意保持不压缩。需要排查产物内部问题时用 npm run build:debug 产出不混淆版本(如微信开发者工具中调试 SDK 集成)。

双类型面隔离:主 tsconfig.json(recorder/compiler,无 DOM lib)与 tsconfig.player.json(player,含 DOM lib)相互独立,类型检查命令:

npx tsc --noEmit -p tsconfig.json        # recorder / compiler / protocol + 既有测试
npx tsc --noEmit -p tsconfig.player.json # player 源码 + tests/player + tsup.player.config.ts

目录结构:

src/
├── protocol/        # 冻结契约:事件协议、templateMap 结构、表达式 AST(权威源)
├── recorder/        # 运行时录制器:record/addCustomEvent、observer、条件跟踪、适配器注册表
├── compiler/        # 构建期编译器:WXML 解析器、表达式编译器、templateMap 生成器、CLI
└── player/          # 浏览器端播放器:Replayer + 4 个 renderer + Timeline(DOM lib 独立类型面)
tests/
├── fixtures/        # 自包含 demo-app 夹具(主包+分包+组件+less)
├── protocol/        # 协议单测
├── recorder/        # 录制器单测
├── compiler/        # 编译器单测 + CLI 集成
├── player/          # 播放器单测(happy-dom 环境)+「recorder 录制 → player 回放」端到端闭环
└── integration/     # dist 冒烟 + 「compiler 产物 → recorder 录制」端到端集成

License

MIT