docs(console-web): document page lifecycle and UI boundaries
Explain the centralized page registry, primary-page retention TTL, typed lifecycle callbacks, and Host/Page state ownership. Document the cva and cn component convention and keep global CSS limited to theme, base rules, and shared keyframes.
This commit is contained in:
@@ -15,22 +15,37 @@ index.html / src/host
|
||||
Solid reactive owner、事件监听器、第三方 UI 库和页面局部缓存会随文档一起释放,避免
|
||||
长期导航后把所有页面资源都留在同一个 JavaScript realm 中。
|
||||
|
||||
需要保留表单、筛选器或昂贵页面状态时,可以在 `src/host/app.tsx` 的 `NAVIGATION`
|
||||
配置中设置 `keepAlive: true`。当前服务版本和运行设置启用保活。Host 会隐藏而不是销毁
|
||||
这些 iframe,并保持其 DOM、JavaScript realm 和 Solid 状态:
|
||||
页面 ID、标题、导航图标、保活偏好和懒加载入口统一声明在
|
||||
`src/pages/registry.ts` 的 `PAGE_DEFINITIONS`。新增页面时创建
|
||||
`src/pages/<id>/index.tsx` 并添加一条注册记录即可,不需要分别维护路由、导航和加载映射。
|
||||
|
||||
一级导航页面默认在对应注册记录中设置 `keepAlive: true`。它们数量固定,切换时保留
|
||||
DOM、JavaScript realm、Solid 状态、表单和滚动位置。未来的详情页、临时编辑页等短期
|
||||
页面应设置 `keepAlive: false`,离开后立即销毁:
|
||||
|
||||
- 离开时发送 `deactivate`,页面应暂停轮询、媒体、动画或其它后台工作。
|
||||
- 返回时发送 `activate`,恢复页面任务,iframe 的 `instance` 和 `timeOrigin` 不变。
|
||||
- LRU 淘汰、关闭 Host 或普通页面离开时发送 `dispose`,随后销毁文档。
|
||||
- `MAX_KEEP_ALIVE_IFRAMES` 限制保活文档数量;活动的非保活页面最多临时多占一个 iframe。
|
||||
- 页面连续隐藏 30 分钟后失效,Host 发送 `dispose` 并销毁 iframe;再次访问会创建新文档。
|
||||
- 失效时间从 `deactivate` 开始计算,页面在前台显示的时间不计入;到期前返回会重置计时。
|
||||
- 关闭 Host 或离开非保活页面时同样发送 `dispose`,随后销毁文档。
|
||||
|
||||
页面可以使用 `PageProps.active` 响应生命周期,也可以监听
|
||||
`wasmeld:activate`、`wasmeld:deactivate`、`wasmeld:dispose` 或统一的
|
||||
`wasmeld:lifecycle` 事件。页面自身不应通过 `display: none` 判断状态,因为 Host
|
||||
可能改变具体的隐藏实现。
|
||||
这里不使用按页面数量淘汰的 LRU。打开另一个一级页面不会造成先前页面被立即释放,
|
||||
同时长期不再使用的隐藏页面最终仍会归还内存。
|
||||
|
||||
Page SDK 通过 `PageProps.lifecycle` 提供框架无关的 `onShow`、`onHide` 和
|
||||
`onUnload` 事件。它们分别对应 Host 协议内部的 `activate`、`deactivate` 和
|
||||
`dispose`,重复消息会被过滤;浏览器 `pagehide` 还会作为 unload 兜底。每次订阅都会
|
||||
返回取消函数。如果懒加载组件订阅时页面已经显示或隐藏,`onShow` 或 `onHide` 会立即
|
||||
重放当前事件,避免错过首次初始化;通用的 `lifecycle.on(type, listener)` 只监听未来
|
||||
变化。`PageProps.active` 继续用于 Solid 响应式渲染和 effect,页面自身不应通过
|
||||
`display: none` 判断状态,因为 Host 可能改变具体的隐藏实现。
|
||||
|
||||
```tsx
|
||||
export default function StreamPage(props: PageProps) {
|
||||
onCleanup(props.lifecycle.onShow(refreshData));
|
||||
onCleanup(props.lifecycle.onHide(pauseStream));
|
||||
onCleanup(props.lifecycle.onUnload(closeStream));
|
||||
|
||||
createEffect(() => {
|
||||
if (props.active()) {
|
||||
resumeStream();
|
||||
@@ -38,11 +53,18 @@ export default function StreamPage(props: PageProps) {
|
||||
pauseStream();
|
||||
}
|
||||
});
|
||||
onCleanup(closeStream);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
`onShow` 在首次显示和保活后再次显示时触发,适合主动刷新可能已经过期的数据。调用记录
|
||||
页面使用该事件请求 Host 刷新 Runtime 快照。`onUnload` 最多触发一次;收到该事件后
|
||||
不得再创建定时器、连接或其它长期资源。浏览器销毁文档时不会等待异步任务,因此
|
||||
`onUnload` 只应用于同步清理;必须发送的少量遥测数据应使用 `sendBeacon`。旧的
|
||||
`wasmeld:activate`、
|
||||
`wasmeld:deactivate`、`wasmeld:dispose` 和 `wasmeld:lifecycle` DOM 事件暂时保留,
|
||||
新页面应使用类型化 SDK。
|
||||
|
||||
iframe 是资源生命周期边界,不是安全边界。Host 和 Page 都是 Wasmeld 自己构建并同源
|
||||
发布的可信代码;Wasm 服务的安全边界仍然在后端 Wasmtime Sandbox 和 Host Capability
|
||||
Registry 中。
|
||||
@@ -50,15 +72,35 @@ Registry 中。
|
||||
## 目录
|
||||
|
||||
```text
|
||||
src/host/ 常驻 Host Shell、iframe LRU 和 Host-owned dialogs
|
||||
src/components/ui/ 跨页面复用的无业务 UI
|
||||
src/host/ 常驻 Host Shell、iframe 隐藏超时管理和 Host-owned dialogs
|
||||
src/components/
|
||||
feedback/ 空状态等反馈组合
|
||||
layout/ PageHeading 等页面结构
|
||||
services/ ServiceTable、StatusBadge 等服务领域组件
|
||||
ui/ Button、Input、Select、Textarea、Card 等无业务基础组件
|
||||
src/lib/ API client、领域模型和纯函数
|
||||
src/primitives/ Solid reactive primitives;Solid 不使用 React Hooks 约定
|
||||
src/pages/ 每个 iframe 页面一个目录和独立懒加载 chunk
|
||||
src/pages/ 集中式页面注册表;每个 iframe 页面一个目录和独立懒加载 chunk
|
||||
src/sdk/ Host/Page postMessage 协议与两侧 client
|
||||
src/styles/ Tailwind v4 theme 和共享组件样式
|
||||
src/styles/ Tailwind v4 theme、文档基础规则和全局 keyframes
|
||||
```
|
||||
|
||||
`components/ui` 只能依赖通用样式和基础函数,不得引用 Runtime、Service、WIT 或具体页面
|
||||
状态。领域组件可以组合 UI 基础组件,但 UI 基础组件不能反向依赖领域目录。
|
||||
|
||||
UI 基础组件采用与 shadcn 相同的源码内样式模式:
|
||||
|
||||
- 组件基础样式和可枚举变体使用 Tailwind class 与 `class-variance-authority` 声明。
|
||||
- 调用者的 `class` 通过 `cn()` 合并;`clsx` 处理条件值,`tailwind-merge` 解决冲突。
|
||||
- Button、Input、Select、Textarea、Card、Table 等组件暴露类型化的变体属性,不依赖
|
||||
`.btn`、`.select` 之类的全局语义 class。
|
||||
- `src/styles/app.css` 不定义组件层,只保留主题 token、文档基础规则和无法内联的全局
|
||||
keyframes。
|
||||
|
||||
业务特有的结构留在 `feedback`、`layout`、`services` 或页面目录中,并组合 UI primitive。
|
||||
导航项等只在一个业务上下文出现的控件可以使用就地 Tailwind class,不需要为了形式统一
|
||||
强行包装成通用组件。
|
||||
|
||||
`src/sdk/protocol.ts` 中的 `SDK_VERSION` 是文档间协议版本。新增可选消息可以保持原版本;
|
||||
删除字段、改变字段语义或产生不兼容状态时必须升级版本,并让两侧同时发布。消息接收端
|
||||
同时检查 origin、source、channel 和 version,不接收任意窗口的控制命令。
|
||||
@@ -75,7 +117,7 @@ Host 会继续向隐藏的保活页面发送只读状态快照,但拒绝其管
|
||||
| 状态 | 跨 Tab | Host 到 Page | 生命周期 |
|
||||
| ------------------------------------------ | -------- | --------------------- | --------------- |
|
||||
| Runtime 快照、连接、API 地址、控制操作状态 | 始终共享 | 分发给已存在的 iframe | Host Tab |
|
||||
| 当前路由、搜索、弹窗、toast、iframe LRU | 不共享 | 当前 Tab 自己管理 | Browser Tab |
|
||||
| 当前路由、搜索、弹窗、toast、iframe 保活集 | 不共享 | 当前 Tab 自己管理 | Browser Tab |
|
||||
| 页面筛选、表单、滚动位置、页面资源 | 不共享 | 不进入 Host | iframe document |
|
||||
|
||||
`src/host/tab-sync.ts` 使用独立版本的 Host Tab 协议:
|
||||
|
||||
Reference in New Issue
Block a user