weifuwu 0.83.0 → 0.85.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -51
- package/dist/components/ChatInput/ChatInput.d.ts +2 -2
- package/dist/components/Confirm/Confirm.d.ts +2 -2
- package/dist/components/Notification/Notification.d.ts +0 -2
- package/dist/components/Toast/Toast.d.ts +0 -2
- package/dist/components/index.d.ts +3 -3
- package/dist/components/index.js +12 -12
- package/dist/components/style.css +17 -0
- package/dist/index.js +2408 -341
- package/dist/ui-dom/Confirm.d.ts +1 -1
- package/dist/ui-dom/Notification.d.ts +1 -1
- package/dist/ui-dom/Toast.d.ts +1 -1
- package/dist/ui-dom/hooks/index.d.ts +0 -1
- package/dist/ui-dom/hooks/popup.d.ts +2 -0
- package/dist/ui-dom/hooks/stable.d.ts +2 -6
- package/dist/ui-dom/index.d.ts +24 -19
- package/dist/ui-dom/index.js +40 -22
- package/dist/ui-dom/jsx-runtime.js +1 -1
- package/dist/ui-dom/middleware/ws.d.ts +8 -0
- package/dist/ui-dom/popup-tracker.d.ts +1 -1
- package/dist/ui-dom/testing.d.ts +3 -9
- package/dist/ui-dom/testing.js +7 -1
- package/dist/ui-dom/types.d.ts +7 -37
- package/dist/ui-dom/vdom3/README.d.ts +6 -3
- package/dist/ui-dom/vdom3/app.d.ts +20 -0
- package/dist/ui-dom/vdom3/audit.d.ts +13 -0
- package/dist/ui-dom/vdom3/build.d.ts +7 -3
- package/dist/ui-dom/vdom3/commands.d.ts +13 -3
- package/dist/ui-dom/vdom3/comp-index.d.ts +18 -0
- package/dist/ui-dom/vdom3/delegate.d.ts +48 -0
- package/dist/ui-dom/vdom3/events.d.ts +8 -2
- package/dist/ui-dom/vdom3/index.d.ts +5 -3
- package/dist/ui-dom/vdom3/jsx.d.ts +26 -4
- package/dist/ui-dom/vdom3/record.d.ts +3 -0
- package/dist/ui-dom/vdom3/render.d.ts +19 -6
- package/dist/ui-dom/vdom3/replay.d.ts +11 -8
- package/dist/ui-dom/vdom3/root.d.ts +3 -7
- package/dist/ui-dom/vdom3/types.d.ts +128 -70
- package/dist/ui-dom/vdom3/ui.d.ts +3 -30
- package/dist/ui-dom/vdom3.js +12 -10
- package/dist/ui-dom/vnode.d.ts +9 -89
- package/docs/compat-three-library.md +1 -1
- package/docs/components-map.md +1 -1
- package/docs/frontend-middleware.md +59 -53
- package/docs/frontend-ui-dom.md +115 -132
- package/docs/frontend.md +28 -24
- package/docs/realtime.md +21 -21
- package/package.json +1 -1
- package/dist/test/client/setup.d.ts +0 -12
- package/dist/ui-dom/context.d.ts +0 -32
- package/dist/ui-dom/hooks/test/hooks.test.d.ts +0 -1
- package/dist/ui-dom/middleware/serve.d.ts +0 -27
- package/dist/ui-dom/router.d.ts +0 -73
- package/dist/ui-dom/vdom2/audit.d.ts +0 -28
- package/dist/ui-dom/vdom2/build.d.ts +0 -53
- package/dist/ui-dom/vdom2/ctx.d.ts +0 -32
- package/dist/ui-dom/vdom2/dom-trace.d.ts +0 -4
- package/dist/ui-dom/vdom2/events.d.ts +0 -65
- package/dist/ui-dom/vdom2/hydrate.d.ts +0 -21
- package/dist/ui-dom/vdom2/index.d.ts +0 -13
- package/dist/ui-dom/vdom2/kind.d.ts +0 -44
- package/dist/ui-dom/vdom2/lifecycle.d.ts +0 -54
- package/dist/ui-dom/vdom2/mount.d.ts +0 -77
- package/dist/ui-dom/vdom2/patch.d.ts +0 -116
- package/dist/ui-dom/vdom2/registry.d.ts +0 -27
- package/dist/ui-dom/vdom2/render.d.ts +0 -21
- package/dist/ui-dom/vdom2/route.d.ts +0 -29
- package/dist/ui-dom/vdom2/ssr.d.ts +0 -32
- package/dist/ui-dom/vdom2/trace.d.ts +0 -62
- package/dist/ui-dom/vdom2/transform.d.ts +0 -76
- package/dist/ui-dom/vdom2/transitions.d.ts +0 -24
- package/dist/ui-dom/vdom2/x2html.d.ts +0 -26
- package/dist/ui-dom/vdom3/compat.d.ts +0 -20
|
@@ -1,45 +1,46 @@
|
|
|
1
1
|
# 前端中间件与工具(weifuwu/ui-dom)
|
|
2
2
|
|
|
3
|
-
> ⚠️ **`weifuwu/client` 已并入 `weifuwu/ui-dom`**——中间件(api/auth/ws/i18n)位于 `weifuwu/ui-dom`,前端运行时唯一入口为 `weifuwu/ui-dom`(
|
|
3
|
+
> ⚠️ **`weifuwu/client` 已并入 `weifuwu/ui-dom`**——中间件(api/auth/ws/i18n)位于 `weifuwu/ui-dom`,前端运行时唯一入口为 `weifuwu/ui-dom`(vdom3 事件流引擎:createRouter + createRoot),见 [frontend-ui-dom.md](frontend-ui-dom.md)。
|
|
4
4
|
|
|
5
5
|
> 本页为 weifuwu 官方文档拆分页 · [返回 README](../README.md)
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## createRouter — 前端路由(vdom3)
|
|
8
8
|
|
|
9
9
|
```tsx
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
10
|
+
import { createRouter, h } from 'weifuwu/ui-dom'
|
|
11
|
+
|
|
12
|
+
let ctx: any = {}
|
|
13
|
+
ctx = v3Toast()(ctx) // 中间件面展开(ctx 注入——对齐后端 app.use)
|
|
14
|
+
|
|
15
|
+
const handle = createRouter(
|
|
16
|
+
[
|
|
17
|
+
{ path: '/', render: () => h(Home, {}) },
|
|
18
|
+
{ path: '/users', render: () => h(UserList, {}) },
|
|
19
|
+
{ path: '/users/:id', render: (params) => h(UserDetail, { id: params.id }) },
|
|
20
|
+
{ path: '*', render: () => h(NotFound, {}) },
|
|
21
|
+
],
|
|
22
|
+
document.querySelector('#root')!,
|
|
23
|
+
{ ctx },
|
|
24
|
+
)
|
|
21
25
|
```
|
|
22
26
|
|
|
23
|
-
###
|
|
27
|
+
### 嵌套布局(RouteDef.layout——跨路由工厂不重跑)
|
|
24
28
|
|
|
25
29
|
```tsx
|
|
26
|
-
const DashboardLayout
|
|
30
|
+
const DashboardLayout = async (page: any) => {
|
|
27
31
|
let open = true
|
|
28
|
-
return
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
h('main', {}, child),
|
|
33
|
-
)
|
|
34
|
-
}
|
|
32
|
+
return h('div', { class: 'wf-row' },
|
|
33
|
+
h('aside', {}, '导航菜单'),
|
|
34
|
+
h('main', {}, page), // 子路由内容(layout 包装——工厂复用——状态保持)
|
|
35
|
+
)
|
|
35
36
|
}
|
|
36
|
-
|
|
37
|
+
// { path: '/users', layout: DashboardLayout, render: () => h(UserList, {}) }
|
|
37
38
|
```
|
|
38
39
|
|
|
39
40
|
### 编程式导航
|
|
40
41
|
|
|
41
42
|
```tsx
|
|
42
|
-
// 在任意组件/页面中(
|
|
43
|
+
// 在任意组件/页面中(ctx.app——createRouter 注入)
|
|
43
44
|
ctx.app?.navigate('/users/123?tab=profile')
|
|
44
45
|
```
|
|
45
46
|
|
|
@@ -51,28 +52,28 @@ ctx.app?.navigate('/users/123?tab=profile')
|
|
|
51
52
|
| `ctx.route.query` | `Record<string, string>` | 查询参数 |
|
|
52
53
|
| `ctx.app.navigate(path)` | `(string) => void` | 编程式导航 |
|
|
53
54
|
|
|
54
|
-
|
|
|
55
|
-
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
59
|
-
| `mode` | `'history' \| 'hash'` | 路由模式(默认 history) |
|
|
55
|
+
| RouteDef | 签名 | 说明 |
|
|
56
|
+
|---------|------|------|
|
|
57
|
+
| `path` | `string` | 路径(`:id` 参数 / `*` 通配) |
|
|
58
|
+
| `render(params)` | `(params) => VNode` | 页面(params 注入——`ctx.route.params` 同源) |
|
|
59
|
+
| `layout(page)` | `(VNode) => VNode` | 嵌套布局(跨路由工厂不重跑——状态保持) |
|
|
60
60
|
|
|
61
61
|
| UIHandler | 类型 | 说明 |
|
|
62
62
|
|-----------|------|------|
|
|
63
63
|
| 签名 | `(location, ctx) => Promise<VNode> \| VNode` | 页面 = 异步组件(`ctx.data.get` 三场景;async 组件无需包装) |
|
|
64
|
-
| 返回值 | `VNode \| null` | 数据结构(落地由
|
|
64
|
+
| 返回值 | `VNode \| null` | 数据结构(落地由 createRouter 事件流渲染决定) |
|
|
65
65
|
|
|
66
66
|
---
|
|
67
67
|
|
|
68
68
|
## api — HTTP 客户端中间件
|
|
69
69
|
|
|
70
70
|
```tsx
|
|
71
|
-
import {
|
|
71
|
+
import { createRouter, h, api } from 'weifuwu/ui-dom'
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
let ctx: any = {}
|
|
74
|
+
const root = document.querySelector('#root')!
|
|
75
|
+
ctx = api({ baseURL: '/api' })(ctx)
|
|
76
|
+
createRouter([], root, { ctx })
|
|
76
77
|
|
|
77
78
|
// 在组件中使用
|
|
78
79
|
async function loadUsers(ctx: WfuiContext) {
|
|
@@ -124,11 +125,12 @@ try {
|
|
|
124
125
|
## auth — 认证中间件
|
|
125
126
|
|
|
126
127
|
```tsx
|
|
127
|
-
import {
|
|
128
|
+
import { createRouter, h, auth } from 'weifuwu/ui-dom'
|
|
128
129
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
let ctx: any = {}
|
|
131
|
+
const root = document.querySelector('#root')!
|
|
132
|
+
ctx = auth()(ctx)
|
|
133
|
+
createRouter([], root, { ctx })
|
|
132
134
|
|
|
133
135
|
// 在组件中
|
|
134
136
|
async function Profile(_props: {}, ctx: WfuiContext) {
|
|
@@ -176,11 +178,12 @@ await ctx.auth?.refresh() // → boolean
|
|
|
176
178
|
## ws — WebSocket 客户端中间件
|
|
177
179
|
|
|
178
180
|
```tsx
|
|
179
|
-
import {
|
|
181
|
+
import { createRouter, h, ws } from 'weifuwu/ui-dom'
|
|
180
182
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
183
|
+
let ctx: any = {}
|
|
184
|
+
const root = document.querySelector('#root')!
|
|
185
|
+
ctx = ws({ url: '/ws' })(ctx)
|
|
186
|
+
createRouter([], root, { ctx })
|
|
184
187
|
|
|
185
188
|
// 发送消息
|
|
186
189
|
ctx.ws?.send({ type: 'chat', body: 'hello' })
|
|
@@ -216,9 +219,10 @@ unsubscribe?.()
|
|
|
216
219
|
## i18n — 国际化中间件
|
|
217
220
|
|
|
218
221
|
```tsx
|
|
219
|
-
import {
|
|
222
|
+
import { createRouter, h, i18n } from 'weifuwu/ui-dom'
|
|
220
223
|
|
|
221
|
-
|
|
224
|
+
let ctx: any = {}
|
|
225
|
+
const root = document.querySelector('#root')!
|
|
222
226
|
app.use(i18n({
|
|
223
227
|
locale: 'zh-CN',
|
|
224
228
|
messages: {
|
|
@@ -301,12 +305,13 @@ import { ErrorBoundary } from 'weifuwu/ui-dom'
|
|
|
301
305
|
**① 命令式 `ctx.confirm()`(推荐,操作前询问)**
|
|
302
306
|
|
|
303
307
|
```tsx
|
|
304
|
-
import {
|
|
308
|
+
import { createRouter, h } from 'weifuwu/ui-dom'
|
|
305
309
|
import { confirm } from 'weifuwu/components'
|
|
306
310
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
311
|
+
let ctx: any = {}
|
|
312
|
+
const root = document.querySelector('#root')!
|
|
313
|
+
ctx = confirm()(ctx)
|
|
314
|
+
createRouter([], root, { ctx })
|
|
310
315
|
|
|
311
316
|
// 任意代码中(组件事件、async 逻辑)
|
|
312
317
|
async function handleDelete(ctx: WfuiContext) {
|
|
@@ -357,12 +362,13 @@ import { Confirm } from 'weifuwu/components'
|
|
|
357
362
|
`ctx.toast()` 是 `<Toast>` 组件的全局命令式封装:任意代码中一行调用,自动消失、自动清理,无需宿主状态。
|
|
358
363
|
|
|
359
364
|
```tsx
|
|
360
|
-
import {
|
|
365
|
+
import { createRouter, h } from 'weifuwu/ui-dom'
|
|
361
366
|
import { toast } from 'weifuwu/components'
|
|
362
367
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
368
|
+
let ctx: any = {}
|
|
369
|
+
const root = document.querySelector('#root')!
|
|
370
|
+
ctx = toast({ position: 'top-right', duration: 3000, max: 3 })(ctx)
|
|
371
|
+
createRouter([], root, { ctx })
|
|
366
372
|
|
|
367
373
|
// 任意代码中(组件事件、api 拦截器、WS 回调、定时器)
|
|
368
374
|
ctx.toast?.('保存成功', 'success')
|
package/docs/frontend-ui-dom.md
CHANGED
|
@@ -1,168 +1,151 @@
|
|
|
1
|
-
# ui-dom — 前端运行时(
|
|
1
|
+
# ui-dom — 前端运行时(vdom3 精准事件流引擎)
|
|
2
2
|
|
|
3
|
-
> **
|
|
4
|
-
>
|
|
5
|
-
>
|
|
6
|
-
> 已取代 `weifuwu/client`——前端运行时唯一入口(createApp/router 旧 API 已删除)。
|
|
7
|
-
>
|
|
8
|
-
> 概念对齐:**req = window.location**,**res = VNode**(数据结构),
|
|
9
|
-
> **uiServe = VDOM**(落地机制),**params/query 在 ctx**(对齐后端 `ctx.params`)。
|
|
3
|
+
> **createRouter(路由)+ createRoot(挂载)+ 精准事件流(渲染本体)**,
|
|
4
|
+
> 组件 = 两阶段异步组件(`async (initProps, ctx) => (props) => VNode`),
|
|
5
|
+
> **params 在 ctx**(对齐后端 `ctx.params`),渲染全链路可观测/可回放/可断言。
|
|
10
6
|
|
|
11
|
-
##
|
|
7
|
+
## 架构:vdom3 事件流引擎
|
|
12
8
|
|
|
13
|
-
### 心智模型统一:一套 API 贯穿两端,无需「两套前端」
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
后端: Request → [Middleware → ctx.field] → Handler → Response
|
|
17
|
-
前端: window.location → [UIMiddleware → ctx.field] → Handler → VNode
|
|
18
9
|
```
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
```tsx
|
|
26
|
-
const Counter = async (initProps, ctx) => {
|
|
27
|
-
let count = initProps.initial ?? 0 // mount:只执行一次
|
|
28
|
-
return async (props) =>
|
|
29
|
-
h('button', { onClick: () => { count += props.step; ctx.ui.render() } }, count) // render
|
|
30
|
-
}
|
|
10
|
+
用户操作(ctx.render())
|
|
11
|
+
→ 决策层事件(jsx/vdom):comp:render → vnode:patch(reuse/rebuild 决策)
|
|
12
|
+
→ 执行层事件(dom):node:create/insert/remove/move、prop:update、
|
|
13
|
+
text:update、event:bind/unbind、ref:cleanup
|
|
14
|
+
→ DOM(精准状态变化——事件数 = 实际变化数)
|
|
31
15
|
```
|
|
32
16
|
|
|
33
|
-
-
|
|
34
|
-
- **
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
- **`ctx.browser` 唯一入口**:复制/查询/滚动/存储/主题/定时器/媒体查询/事件监听全部经 BrowserEnv(组件侧 46 处迁移清零 + 测试侧 ~300 处对齐,SSR shim 三态同构)。开发者不需要记「SSR 里 `window.innerWidth` 会崩」这类环境知识——**框架替他们承担**。
|
|
39
|
-
- **诚实裁剪**:不支持的能力明确抛 `ProtocolError('unsupported')`——确定性失败,绝不静默降级。
|
|
40
|
-
|
|
41
|
-
### 零依赖 + 确定性:可审计、可测试、可读的运行时
|
|
42
|
-
|
|
43
|
-
- **零 npm 运行时依赖**(对比 React + react-dom + react-router + 状态库 + SSR 工具 5+ 依赖)。
|
|
44
|
-
- **自研 VDOM/diff**(keyed children、style diff、CSS 变量、Portal、hydration 游标收养)——每个算法都有对应测试与纪律条目(真实事故沉淀)。**读源码即可完全理解框架行为**。
|
|
45
|
-
- **VDOM 对开发者透明(占位法 + 单一规则源)**:写 JSX → 看 DOM 即真相——`data-wf-key`(数组项身份,元素/组件一致)、`data-wf-id`(组件实例身份)、`<!--wf-hole: xxx-->`(条件渲染 false 的占位注释)直接在 DOM 可见;非法输入(对象/数字 type)→ 诊断占位 + warn,不崩溃不静默;`?vdom_debug=1` 开启 patch trace + 结构 audit。**转化路径唯一清晰、无 magic、可推导**(规则表为内部开发契约)
|
|
46
|
-
- **零构建步骤**:`weifuwu/dev` loader + `ctx.ui.js/css` 动态编译,服务端直接跑 `.tsx`,改组件刷新即生效。
|
|
47
|
-
|
|
48
|
-
### 弹层/浮层体系:最难的 UI 类别变成复用的原语
|
|
49
|
-
|
|
50
|
-
`ctx.ui.usePopup()` 一个组合器覆盖:portal 渲染、fixed 定位 + 视口夹紧、锚点变化自动重算、外部点击/Escape 关闭、el-null 微任务重试、ref 稳定化。**开发者不需要再写任何弹层脚手架**——这是传统前端最常重复造轮子的地方。弹窗纪律(portal 必须、z-index token、exit 动画成对)由审计测试强制。
|
|
17
|
+
- **统一命名**:对象 + 动作 + 参数(`{ entity, action, target, payload }`——键 `entity:action`)——location(`route:change`)/ jsx(`comp:render`/`props:update`)/ vdom(`vnode:patch`)/ dom(`node:insert` 等)四层同构
|
|
18
|
+
- **DOM = fold(事件流)**:初始 DOM + 事件序列 = 任意时刻 DOM——可记录(`stream.events()`)、回放(`replay`)、断言(`expectEventSequence`/`eventsOf`)
|
|
19
|
+
- **不变量:无事件流不渲染**——所有状态变化都通过事件流(DOM 操作/组件生命周期/索引/事件注册/错误/挂起/内部决策/文本操作/副作用)——静态审计 + 运行时对照审计(`__WF_VDOM_AUDIT='1'`)守护
|
|
20
|
+
- **render-only**:渲染唯一触发 `ctx.render()`(组件级——只重跑该组件 renderFn + patch 其输出,兄弟零执行);`ctx.ui.render()` 为兼容面(同义)
|
|
21
|
+
- **组件级精准更新**:`ctx.render()` 只刷新自身——组件 O(1) 索引定位——props 未变的子组件剪枝——事件数 = 实际变化数
|
|
51
22
|
|
|
52
|
-
###
|
|
23
|
+
### 事件代理(所有监听统一注册表)
|
|
53
24
|
|
|
54
|
-
|
|
25
|
+
元素事件/全局监听/动画钩子全部收敛到**代理注册表**(`delegate.ts`):
|
|
26
|
+
- 元素事件(onClick 等 props)→ 挂载点监听(每挂载点每事件一次——O(1))+ 按 data-v3-id 祖先链分发(handler 更新 = Map 覆盖——**零重绑零噪音**)
|
|
27
|
+
- 全局监听(keydown/mousedown/pointer/popstate/scroll/选区/剪贴板)→ `addGlobalListener`(同事件聚合——EventTarget 每事件一次)
|
|
28
|
+
- 动画钩子(animationend)→ `bindElementListener`(once 自动解绑)
|
|
29
|
+
- 不冒泡事件(error/load)→ 捕获监听;`e.currentTarget` 还原为绑定元素(与元素级语义一致)
|
|
55
30
|
|
|
56
|
-
|
|
57
|
-
|------|------|
|
|
58
|
-
| **上手** | 两阶段组件 + 改状态后 render()——无 hooks/依赖数组心智负担 |
|
|
59
|
-
| **后端互迁** | req/res/中间件契约同构,SSR 透明,一份 router 两端共享 |
|
|
60
|
-
| **可靠性** | 确定性失败、诚实裁剪、环境边界、测试侧同构 |
|
|
61
|
-
| **效率** | 弹层/数据管道/事件原语全覆盖——不重复造轮子 |
|
|
62
|
-
| **可维护** | 零依赖可审计、纪律沉淀事故教训、构建可选 |
|
|
63
|
-
|
|
64
|
-
> 一句话:ui-dom 不是「又一个 React」——它是把 weifuwu 后端中间件生态的确定性、可测试性和纪律性,原样移植到了前端。开发者得到的是一个行为可预测、失败可预期、源码可读懂的运行时。
|
|
65
|
-
|
|
66
|
-
## 快速开始
|
|
31
|
+
## 快速开始(SPA)
|
|
67
32
|
|
|
68
33
|
```tsx
|
|
69
|
-
import {
|
|
70
|
-
import {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
34
|
+
import { createRouter, h } from 'weifuwu/ui-dom'
|
|
35
|
+
import { Button } from 'weifuwu/components'
|
|
36
|
+
|
|
37
|
+
// 两阶段异步组件:外层工厂(mount 一次——状态/数据),内层 renderFn(每次渲染)
|
|
38
|
+
const Counter = async (_init, ctx) => {
|
|
39
|
+
let count = 0
|
|
40
|
+
return async () =>
|
|
41
|
+
h('div', {},
|
|
42
|
+
h('button', {
|
|
43
|
+
onClick: () => { count++; ctx.render() }, // render-only:显式触发
|
|
44
|
+
}, `count:${count}`),
|
|
45
|
+
)
|
|
46
|
+
}
|
|
76
47
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
h('h2', {}, info.title),
|
|
83
|
-
h(Button, { onClick: () => { clicks++; ctx.ui.render() } }, `点击 ${clicks} 次`),
|
|
84
|
-
h(Button, { variant: 'secondary', onClick: () => ctx.toast?.('提示', 'success') }, '弹 toast'),
|
|
85
|
-
)
|
|
86
|
-
})
|
|
48
|
+
createRouter(
|
|
49
|
+
[{ path: '/', render: () => h(Counter, {}) }],
|
|
50
|
+
document.querySelector('#root') as HTMLElement,
|
|
51
|
+
)
|
|
52
|
+
```
|
|
87
53
|
|
|
88
|
-
|
|
89
|
-
|
|
54
|
+
- 页面组件 ctx:`ctx.render()`(页面级刷新)/ `ctx.route`(`{ path, params }`——动态参数)/ `ctx.ui`(24 hooks)
|
|
55
|
+
- 中间件面注入:`createRouter(routes, root, { ctx })`——`ctx` 为中间件展开结果(`api/auth/toast/confirm/i18n` 等——与后端 `app.use` 同理念):
|
|
90
56
|
|
|
91
|
-
|
|
57
|
+
```ts
|
|
58
|
+
let ctx: any = {}
|
|
59
|
+
ctx = await api({ baseURL: '', token: ... })(ctx)
|
|
60
|
+
ctx = auth({ ... })(ctx)
|
|
61
|
+
ctx = v3Toast()(ctx)
|
|
62
|
+
createRouter([...], root, { ctx })
|
|
92
63
|
```
|
|
93
64
|
|
|
94
|
-
##
|
|
95
|
-
|
|
96
|
-
### req / res / 落地分离
|
|
65
|
+
## 无路由挂载(createRoot)
|
|
97
66
|
|
|
98
|
-
|
|
99
|
-
|------|------|
|
|
100
|
-
| req | `window.location`(浏览器原生,不包装) |
|
|
101
|
-
| res | `VNode`(handler 返回的数据结构) |
|
|
102
|
-
| uiServe | VDOM 落地(renderValue/patchValue diff·patch DOM) |
|
|
103
|
-
| ssrPage | 服务端落地(renderSsr → HTML + `__DATA__`) |
|
|
67
|
+
组件树直接挂载(弹窗宿主、微前端片段、非路由页面):
|
|
104
68
|
|
|
105
|
-
|
|
69
|
+
```ts
|
|
70
|
+
import { createRoot, h } from 'weifuwu/ui-dom'
|
|
71
|
+
const handle = createRoot(h(App, {}), document.getElementById('root')!, { ctx })
|
|
72
|
+
await handle.ready // 首帧完成
|
|
73
|
+
```
|
|
106
74
|
|
|
107
|
-
|
|
75
|
+
## ctx.ui(组件库消费面——24 hooks)
|
|
108
76
|
|
|
109
|
-
|
|
110
|
-
|------|------|------|
|
|
111
|
-
| ctx 注入 | `use(mw: AppMiddleware)` | `(ctx) => ctx`——注入 ctx.toast/confirm 等(类型累积 `UIRouter<C & O>`) |
|
|
112
|
-
| 渲染中间件 | `use(mw: UIMiddleware)` | `(location, ctx, children) => (location, ctx) => vnode`——layout/SSR 包装 children |
|
|
113
|
-
| 子路由 | `use(prefix, sub)` | 独立路由树(sub 的中间件/notFound/嵌套均生效) |
|
|
77
|
+
`useExternal`(共享状态)/ `useControlled`/`useControlledInput`(受控原语)/ `useOpen`/`usePopup`(弹层组合器——portal 定位/Escape/外部点击)/ `usePopupPosition` / `useTween` / `useInView` / `useScrollPosition` / `useGlobalKey` / `useDrag`/`useDragDrop` / `useMedia`/`useBreakpoint` / `useReducedMotion` / `useStableRef` / `useAnimationEnd` / `usePresence`(退场状态机)/ `useLongPress` / `useHoverCapable` / `useVisualViewport` / `useAsync` / `useChat`(AI 会话——消息/工具调用/审批)。
|
|
114
78
|
|
|
115
|
-
|
|
79
|
+
组件库(weifuwu/components)零引擎耦合:只消费 `ctx.ui` 契约——vdom3 与未来引擎可无缝替换。
|
|
116
80
|
|
|
117
|
-
|
|
81
|
+
## 事件流可观测(调试/测试)
|
|
118
82
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
83
|
+
```ts
|
|
84
|
+
import { stream, evKey } from 'weifuwu/ui-dom'
|
|
85
|
+
import { eventsOf, expectEventSequence } from 'weifuwu/ui-dom'
|
|
122
86
|
|
|
123
|
-
|
|
124
|
-
|
|
87
|
+
const events = stream.events()
|
|
88
|
+
eventsOf(events, 'node:create').length // 创建了几个节点
|
|
89
|
+
// 决策层事件解释"为什么"(reuse/rebuild),执行层事件是"做了什么"——完全可断言
|
|
90
|
+
```
|
|
125
91
|
|
|
126
|
-
|
|
92
|
+
**完整事件覆盖**(任何问题都在事件流可定位):
|
|
93
|
+
- **错误**:`error:throw/caught`(工厂/renderFn/挂载/更新失败——phase 定位)+ **挂起超时**(renderFn/buildVNode 永不 resolve → 3s 检测——静默失败不再静默)
|
|
94
|
+
- **内部决策**:`internal:queue/notfound/skip`(busy 排队/组件未定位)
|
|
95
|
+
- **事件流自身**:`stream:overflow`(溢出可审计)/ `stream:watermark`(水位预警)/ `size()`/`capacity()`/`overflowCount()`/`subscribe(filter, fn)`(按层过滤订阅)
|
|
96
|
+
- **文本操作**:`text:input`(输入/组合)/ `text:select`(选区)/ `text:copy/cut/paste`(剪贴板摘要)
|
|
97
|
+
- **组件副作用**:`ref:mount` + `effect:animate/lock/unlock/focus/scroll`(动画/滚动锁/焦点 trap/滚动)
|
|
127
98
|
|
|
128
|
-
|
|
129
|
-
|------|------|-----------|
|
|
130
|
-
| `ctx.ui.render()`(无参) | 主动调用 | 当前组件(闭包绑定,无 this 陷阱) |
|
|
131
|
-
| `ctx.ui.render(['id'])` | 主动调用 | 指定组件(selfId 注册的语义 ID) |
|
|
132
|
-
| `ctx.ui.useExternal(store)` | store 变更自动 | **仅订阅组件**(unmount 自动退订) |
|
|
99
|
+
测试纪律(jsdom):`renderVNode`(VNode 层)/ `mountComponent`(同实例 re-render)/ `expectEventSequence`(精确事件序列)——渲染 = 事件序列断言。
|
|
133
100
|
|
|
134
|
-
|
|
135
|
-
- 跨组件共享:`createStore` + `useExternal`(替代已删除的 `$` Proxy / `dirty`)
|
|
136
|
-
- hooks(useMedia/useInView/usePopup 等)事件驱动重渲染——与"赋值自动"本质不同
|
|
101
|
+
### 多应用加载(app 节点)
|
|
137
102
|
|
|
138
|
-
|
|
103
|
+
```ts
|
|
104
|
+
import { registerApp, App } from 'weifuwu/ui-dom'
|
|
139
105
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
106
|
+
// 注册子应用(工厂——可 await 初始化——appId 复用不重跑)
|
|
107
|
+
registerApp('mini-todo', (props, ctx) => h(MiniTodo, {}))
|
|
108
|
+
// 父树中嵌入(不隔离设计——共享流/全局 id——天然归属唯一)
|
|
109
|
+
h(App, { appId: 'mini-todo', props: { title } })
|
|
110
|
+
```
|
|
111
|
+
- 边界事件(同流):`app:mount` / `app:update`(props keys)/ `app:unmount` / `app:error`(unknown-app)——payload 带 appId 可区分
|
|
112
|
+
- 子应用独立状态(工厂闭包)——渲染/事件与父同流(全链路可观测——调试一条流)
|
|
113
|
+
|
|
114
|
+
## SSR(事件流形态——DOM = fold 不变量)
|
|
115
|
+
|
|
116
|
+
vdom3 SSR 不是 HTML 字符串 + 游标收养,而是**事件流序列化**:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
// 服务端:构建 → 事件流 → HTML + 序列化事件
|
|
120
|
+
import { renderToEvents, eventsToHtml, serializeEvents } from 'weifuwu/ui-dom'
|
|
121
|
+
const events = await renderToEvents(vnode) // 事件流(node:create/insert/...)
|
|
122
|
+
const html = eventsToHtml(events) // 首帧 HTML
|
|
123
|
+
const data = serializeEvents(events) // 序列化事件(__DATA__)
|
|
124
|
+
// 客户端:replay(events, root) —— 零 DOM 猜测,与服务端 HTML 同构
|
|
125
|
+
import { deserializeEvents, replay } from 'weifuwu/ui-dom'
|
|
126
|
+
replay(deserializeEvents(data), root)
|
|
127
|
+
```
|
|
144
128
|
|
|
145
|
-
|
|
129
|
+
`ctx.data.get(key, fetcher)` 三场景自动适配(SSR 真 fetch → 序列化进 `__DATA__` → 客户端种子命中 → SPA 未命中触发 fetcher)。
|
|
146
130
|
|
|
147
|
-
|
|
148
|
-
uiServe(app, { root: '#root', hydrate: true })
|
|
149
|
-
```
|
|
131
|
+
## 组件纪律速查(事故沉淀——详见 AGENTS.md)
|
|
150
132
|
|
|
151
|
-
-
|
|
152
|
-
-
|
|
153
|
-
-
|
|
133
|
+
- **render-only**:状态是普通对象(`let` + 显式 `render()`)——禁止隐式触发
|
|
134
|
+
- **mount/render 分工**:只依赖稳定引用的回调(ctx/挂载闭包)→ mount 定义(零重绑);依赖最新 props → render 内定义(重绑是正确性要求)
|
|
135
|
+
- **ref 稳定**:带清理的 ref 定义在 mount 作用域(内联 ref 每次渲染新函数 → ref(null) 反复触发)
|
|
136
|
+
- **浮层 portal**:`ctx.ui.usePopup` + `popup.portal()`(#__wf_portal)——禁止 absolute 相对父容器
|
|
137
|
+
- **浏览器环境**:组件用 `ctx.browser`(禁裸 window/document)——SSR 安全三态
|
|
138
|
+
- **事件 handler**:代理 Map 覆盖——handler 更新零重绑零事件(稳定引用仅为性能建议——结构共享收益前提)
|
|
139
|
+
- **props 不可变(红线——Chat 空 bubble 事故)**:**禁止原地修改 props 引用的对象**(`m.content += text`)——vdom3 剪枝按 props 浅比较判断"未变"→ 复用旧输出 → 渲染不更新。**状态更新必须新建对象**(`$.msgs[i] = { ...m, content: ... }`——引用变 → 剪枝不命中 → 重渲染)。排查"渲染没更新"时看 `__wf_tail` 的 `comp:build` 事件 `reason: 'reuse-skip'`(= 契约:props 引用未变)
|
|
154
140
|
|
|
155
|
-
##
|
|
141
|
+
## 与后端共享
|
|
156
142
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
| `ctx.data.get/set/has` | 数据管道(缓存 + in-flight 合并 + `__DATA__` 种子) |
|
|
161
|
-
| `ctx.ui.*` | 原语(`render`/`useExternal`/`usePopup`/`useChat`/`useInView`…) |
|
|
162
|
-
| `ctx.browser.*` | 环境抽象(window/document 唯一入口,SSR shim 同构) |
|
|
163
|
-
| `ctx.toast` / `ctx.confirm` / `ctx.notification` | 命令式注入(`app.use(toast())` 等) |
|
|
143
|
+
- 中间件理念同构:后端 `app.use`(ctx.sql/redis/api/...)↔ 前端 `createRouter(routes, root, { ctx })`(ctx.api/auth/...)
|
|
144
|
+
- 路由参数:`ctx.route.params`(对齐后端 `ctx.params`)
|
|
145
|
+
- `weifuwu/dev`(Node loader):服务端直接跑 `.tsx`——与 `ctx.ui.js` 前端动态编译对称,两端同一 JSX 运行时
|
|
164
146
|
|
|
165
|
-
##
|
|
147
|
+
## 参考
|
|
166
148
|
|
|
167
|
-
|
|
168
|
-
|
|
149
|
+
- 组件库蓝本:`apps/components-demo`(115 组件 + createRouter 装配)
|
|
150
|
+
- 完整应用:`apps/agent-platform`(多租户 AI 平台——认证守卫/嵌套布局/AI 会话)
|
|
151
|
+
- 布局蓝本:`apps/layouts-demo`(8 种布局模式)
|
package/docs/frontend.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
> createRoot + 事件流)。vdom2(本文档)为当前生产引擎(冻结中——bug 修复在 vdom3)。
|
|
6
6
|
> 迁移路线图见仓库 `design/vdom3-migration-plan.md`。
|
|
7
7
|
|
|
8
|
-
> ⚠️ **`weifuwu/client` 已并入 `weifuwu/ui-dom`**(`src/client/` 已删除)——本页 import 均用 `weifuwu/ui-dom`。前端运行时唯一入口是 **`weifuwu/ui-dom`**(
|
|
8
|
+
> ⚠️ **`weifuwu/client` 已并入 `weifuwu/ui-dom`**(`src/client/` 已删除)——本页 import 均用 `weifuwu/ui-dom`。前端运行时唯一入口是 **`weifuwu/ui-dom`**(vdom3 精准事件流引擎:createRouter + createRoot——渲染全链路 `entity:action` 事件流),权威参考见 **[docs/frontend-ui-dom.md](frontend-ui-dom.md)**。
|
|
9
9
|
|
|
10
10
|
> 以下为完整 API 参考,按需查阅。新手建议先阅读 README 的「核心概念」和「快速开始」。
|
|
11
11
|
|
|
@@ -25,31 +25,34 @@ esbuild.build({
|
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|
|
28
|
-
## 应用引导(
|
|
28
|
+
## 应用引导(vdom3 事件流引擎——createRouter)
|
|
29
29
|
|
|
30
30
|
```tsx
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
31
|
+
import { createRouter } from 'weifuwu/ui-dom'
|
|
32
|
+
|
|
33
|
+
// 中间件面展开(对齐后端 app.use 链——ctx 注入)
|
|
34
|
+
let ctx: any = {}
|
|
35
|
+
ctx = await api({ baseURL: '' })(ctx)
|
|
36
|
+
ctx = await auth({ ... })(ctx)
|
|
37
|
+
ctx = v3Toast()(ctx)
|
|
38
|
+
|
|
39
|
+
// 路由(RouteDef[])+ 落地(事件流渲染)
|
|
40
|
+
const handle = createRouter(
|
|
41
|
+
[
|
|
42
|
+
{ path: '/', render: () => h(Home, {}) },
|
|
43
|
+
{ path: '/users/:id', render: (params) => h(UserPage, { id: params.id }) },
|
|
44
|
+
],
|
|
45
|
+
document.querySelector('#root')!,
|
|
46
|
+
{ ctx },
|
|
47
|
+
)
|
|
44
48
|
```
|
|
45
49
|
|
|
46
50
|
| API | 说明 |
|
|
47
51
|
|------|------|
|
|
48
|
-
| `
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
51
|
-
| `
|
|
52
|
-
| `app.ctx` | 当前 WfuiContext |
|
|
52
|
+
| `createRouter(routes, root, { ctx })` | vdom3 路由:RouteDef[](`{ path, render, layout? }`)+ 中间件面 ctx 注入 |
|
|
53
|
+
| `handle.navigate(path)` / `refresh()` / `close()` | 导航 / 重渲染当前页 / 卸载 |
|
|
54
|
+
| `ctx.route` | 当前路由 `{ path, params }`(对齐后端 ctx.params) |
|
|
55
|
+
| `createRoot(vnode, root, { ctx })` | 无路由组件树挂载(`handle.ready` 首帧完成) |
|
|
53
56
|
|
|
54
57
|
---
|
|
55
58
|
|
|
@@ -94,10 +97,11 @@ const Home: Component<{}, ApiInjected & RouteInjected> = async (_init, ctx) => {
|
|
|
94
97
|
}
|
|
95
98
|
// 未声明的注入字段编译期报错——注入从"文档约定"变成"类型保证"
|
|
96
99
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
// 中间件面展开 + createRouter(options.ctx 注入——类型由中间件函数累积)
|
|
101
|
+
let ctx: any = {}
|
|
102
|
+
ctx = api()(ctx) // 注入 ctx.api
|
|
103
|
+
ctx = v3Toast()(ctx) // 注入 ctx.toast
|
|
104
|
+
createRouter(routes, root, { ctx })
|
|
101
105
|
```
|
|
102
106
|
|
|
103
107
|
> 各中间件的注入接口:`api()` → `ApiInjected`、`auth()` → `AuthInjected`、`ws()` → `WsInjected`、`i18n()` → `I18nInjected`、`router()` → `RouteInjected`(均可从 `weifuwu/ui-dom` 导入)。
|