FlexUI Shadow Sync 详细设计方案
1. 概述
@flexui/shadow-sync 是 FlexUI 的跨后端 Shadow Tree 同步模块,解决 FlexUI 组件树在多个渲染后端之间保持一致性的问题。
1.1 设计目标
- 跨后端同步:将同一组件树的状态同步到多个后端 (如 DOM Backend + Native Backend)
- 增量传输:仅传输变更部分,减少通信开销
- 消息通道:通过抽象 Channel 实现灵活的传输方式
- 回放支持:支持录制和回放渲染指令序列
1.2 包信息
| 字段 | 值 |
|---|---|
| 包名 | @flexui/shadow-sync |
| 版本 | 0.1.0 |
| 语言 | TypeScript |
| 构建 | Rollup |
| 入口 | dist/flexui_shadow_sync.js / dist/flexui_shadow_sync.es.js |
2. 架构设计
┌──────────────────────────────────────────────────┐
│ FlexUI Component Tree │
├──────────────────────────────────────────────────┤
│ ShadowSync Backend │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ DOM Backend │ │ Shadow B. │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ ┌──────┴──────────────────┴───────┐ │
│ │ Message Channel │ │
│ │ ┌──────────────────────────┐ │ │
│ │ │ Serialization / Transport│ │ │
│ │ └──────────────────────────┘ │ │
│ └──────────────┬───────────────────┘ │
├─────────────────┼──────────────────────────────────┤
│ Remote Backend (Native / Another Context) │
│ ┌──────────────┴───────────────────┐ │
│ │ View Controller │ │
│ │ ┌────────────┐ ┌──────────────┐ │ │
│ │ │ Element Mgr│ │ Layout Mgr │ │ │
│ │ └────────────┘ └──────────────┘ │ │
│ └──────────────────────────────────┘ │
└──────────────────────────────────────────────────┘
3. 核心模块
3.1 ShadowSyncElement(同步元素)
ShadowSyncElementType
├── Fragment → 片段节点
├── Element → 标准元素
├── TextNode → 文本节点
├── Component → 组件节点
├── ShadowRoot → Shadow Root
└── VirtualNode → 虚拟节点
每个 ShadowSyncElement 有唯一 ID,通过 Channel 与远端对应:
ShadowSyncElement
├── _id: number → 全局唯一 ID
├── _slotId: number? → Slot ID
├── __wxElement: Element? → 源 FlexUI Element
├── shadowRoot → Shadow Root 引用
└── _prepareElement() → 准备并发送创建指令
3.2 MessageChannel(消息通道)
message_channel.ts (~55KB) 是同步的核心,定义了完整的消息协议:
Channel (抽象接口)
├── createElement(id, tagName, stylingName, ownerShadowRoot)
├── createTextNode(id, content, ownerShadowRoot)
├── setAttribute(id, name, value)
├── removeAttribute(id, name)
├── addEvent(id, name)
├── removeEvent(id, name)
├── appendChild(parentId, childId)
├── insertBefore(parentId, childId, refId)
├── removeChild(parentId, childId)
├── setStyle(id, style)
├── setTextContent(id, content)
├── setSlot(id, slotId)
└── ...
消息类型:
| 类别 | 操作 |
|---|---|
| 节点操作 | createElement, createTextNode, appendChild, insertBefore, removeChild |
| 属性操作 | setAttribute, removeAttribute |
| 样式操作 | setStyle, addClass, removeClass |
| 事件操作 | addEvent, removeEvent, fireEvent |
| 文本操作 | setTextContent |
3.3 ViewController(视图控制器)
view_controller.ts (~25KB) 管理远端视图:
ViewController
├── 接收 Channel 消息
├── 管理 Element 实例
├── 维护节点树
├── 应用布局计算
└── 触发渲染刷新
3.4 Backend(同步后端)
backend.ts (~29KB) 实现 composedBackend 接口:
ShadowSync Backend
├── domBackend → 本地 DOM 后端
├── shadowBackend → 远端同步后端
├── Channel 管理
└── ID 生成器
每个元素操作同时在本地 DOM 和远端执行,保持两棵树的状态一致。
3.5 Replay(回放系统)
replay.ts 支持录制和回放渲染指令:
Replay System
├── 录制: 记录所有 Channel 指令
├── 回放: 按序执行录制的指令
└── 用途: 调试、测试、离线渲染
3.6 Template Engine(模板引擎桥接)
template_engine.ts 将 Shadow Sync 与 FlexUI 的模板引擎桥接。
3.7 Utils(工具函数)
utils.ts 提供 ID 生成器等基础工具。
4. 通信协议
4.1 最小化传输
Shadow Sync 采用增量同步策略:
- 仅传输变更的属性和样式
- 节点操作通过 ID 引用,避免传输完整节点树
- 批量操作合并为单次传输
4.2 Channel 抽象
Channel 作为抽象层,支持多种传输实现:
- 进程内:直接函数调用
- 跨进程:通过 JS Bridge / IPC
- 跨设备:通过 WebSocket / 网络协议
5. 使用场景
5.1 原生渲染
FlexUI Component (JS) → Shadow Sync → Native 渲染引擎
组件树在 JS 线程维护,渲染指令通过 Shadow Sync 发送到 Native 线程执行。
5.2 双后端渲染
FlexUI Component (JS) → DOM Backend (Web 预览) + Shadow Backend (Native 真机)
同一组件树同时渲染到浏览器 DOM 和 Native 后端。
5.3 远程调试
DevTools Extension ← Shadow Sync (回放) ← Host App
开发者工具通过回放渲染指令重现宿主应用的组件树。
6. 设计决策
6.1 为什么需要 Shadow Sync?
直接用 @flexui/core 的 composedBackend 会要求 Native 后端在同一个 JS 线程中运行,但实际场景中 Native 渲染通常在独立的 UI 线程。Shadow Sync 通过消息通道解决了这个跨线程/跨进程的同步问题。
6.2 为什么用 ID 引用而非序列化节点树?
- 性能:传输节点 ID 比传输完整节点树数据量小几个数量级
- 一致性:两端通过 ID 维护同一棵逻辑树,避免序列化/反序列化的歧义
- 灵活性:远端可以自行管理节点生命周期和布局
6.3 与 ComposedBackend 的关系
Shadow Sync Backend 实现了 composedBackend 接口:
domBackend: 本地 (JS 线程内的) 渲染shadowBackend: 远端 (通过 Channel) 的同步副本
这保持了与核心框架接口的兼容性,上层代码无需感知同步机制。