@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 --> Hrecorder 通过 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 / --transitionCustom 事件时间轴标记
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 录制」端到端集成