weifuwu 0.89.0 → 0.89.1
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 +86 -95
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ npm install weifuwu # 一个依赖,完整应用栈
|
|
|
19
19
|
|------|-----------|
|
|
20
20
|
| **一个包,零构建** | 服务端 `node --import weifuwu/dev` 直跑 `.tsx`;浏览器 CDN import map 即用;CSS 一条 link 即得完整设计系统——没有构建步骤、没有脚手架 |
|
|
21
21
|
| **协议层全自研** | PG v3 / RESP2 / GraphQL schema / OpenAI 流式协议全部自研——确定性、可预测、错误模型统一;诚实裁剪:**不支持的能力明确报错,绝不静默降级** |
|
|
22
|
-
| **消灭样板** | 动态编译免构建、`
|
|
22
|
+
| **消灭样板** | 动态编译免构建、`useAsyncData` 一个 hook 覆盖 SSR 预取/hydration/SPA、自研 DB 客户端免双重编码与 parseRow 样板 |
|
|
23
23
|
| **SaaS 地基随包内置** | rateLimit / email / userSystem / messager / queue / ai 六个中间件**互相咬合**(身份是消息的路由)——从「自建基础设施」变「声明业务」 |
|
|
24
24
|
| **自托管友好** | 运行时仅 esbuild + graphql + ws;部署 = 一个 Node 进程 + Postgres + Redis;数据、代码、模型全部在自己手里 |
|
|
25
25
|
|
|
@@ -34,14 +34,15 @@ npm install weifuwu # 一个依赖,完整应用栈
|
|
|
34
34
|
| SaaS 地基 | 随包内置 | rateLimit / email / userSystem / messager / queue / ai → `ctx.*` 一行接入 |
|
|
35
35
|
|
|
36
36
|
> ⚠️ **注意:前后端都有 `ctx.ui`,但用途完全不同**
|
|
37
|
-
> - **后端** `ctx.ui`(SSR/编译):`ctx.ui.html`(HTML 模板)、`ctx.ui.js`(TSX→JS 动态编译)、`ctx.ui.css`(CSS 编译)、`ctx.ui.ssr`(组件 SSR
|
|
38
|
-
> - **前端** `ctx.ui`(渲染引擎,20+ hooks
|
|
39
|
-
> -
|
|
40
|
-
> - 状态:`
|
|
37
|
+
> - **后端** `ctx.ui`(SSR/编译):`ctx.ui.html`(HTML 模板)、`ctx.ui.js`(TSX→JS 动态编译)、`ctx.ui.css`(CSS 编译)、`ctx.ui.ssr`(组件 SSR)
|
|
38
|
+
> - **前端** `ctx.ui`(渲染引擎,20+ hooks——**一切会变化的值都是 getter**):
|
|
39
|
+
> - 数据:`useAsyncData(fetcher, key)`(唯一异步边界——同 key 并发合并/竞态取消/缓存保留/SSR 种子预热——**作者零退订零竞态代码**)
|
|
40
|
+
> - 状态:`signal(n)`(getter 读 + set/update 写——变化自动重渲染)/ `useObservable(obs$)`(任何 Observable → getter)/ `useExternal()`(共享状态)
|
|
41
|
+
> - 受控:`useControlled()`(受控/非受控)/ `useControlledInput()`(逐键回流)
|
|
41
42
|
> - 弹层:`usePopup()`(统一能力层——锚定浮层 + 会话级模态)/ `usePopupPosition()`(定位)
|
|
42
43
|
> - 事件:`useInView()` / `useScrollPosition()` / `useMedia()` / `useBreakpoint()` / `useGlobalKey()` / `useDrag()` / `useDragDrop()` / `useHoverCapable()` / `useLongPress()` / `useVisualViewport()`
|
|
43
|
-
> - 动画:`useAnimationEnd()
|
|
44
|
-
> - AI
|
|
44
|
+
> - 动画:`useAnimationEnd()` / `usePresence()`(显隐状态机)/ `useTween()` / `useReducedMotion()`
|
|
45
|
+
> - AI/生命周期:`useChat()`(AI 会话)/ `hold(fn)`(卸载清理——等价 onUnmount)
|
|
45
46
|
> 后端的是「把页面和代码交给浏览器」,前端的是「在浏览器里驱动 UI」。
|
|
46
47
|
|
|
47
48
|
### 与主流方案的关系
|
|
@@ -55,14 +56,13 @@ npm install weifuwu # 一个依赖,完整应用栈
|
|
|
55
56
|
| 认证 / 消息 / 队列 | **随包内置、互相咬合** | 自选 + 自缝 | 自选 + 自缝 |
|
|
56
57
|
| 部署 | 一个 Node 进程 + PG + Redis | 各组件自理 | 平台绑定 |
|
|
57
58
|
|
|
58
|
-
> 定位不是「替代某个框架」,而是**包换包**:用 weifuwu
|
|
59
|
+
> 定位不是「替代某个框架」,而是**包换包**:用 weifuwu 一个依赖替换你原本要缝合的整套栈。心智模型有借鉴(组件模型接近 React、中间件接近 Express),但每一层都是自研的确定性实现——组件模型见[核心概念](#核心概念),与 antd/Element Plus/shadcn 的对应见 [docs/components-map.md](docs/components-map.md)。
|
|
59
60
|
|
|
60
61
|
### 从这里开始
|
|
61
62
|
|
|
62
63
|
| 你想… | 去哪 |
|
|
63
64
|
|--------|------|
|
|
64
65
|
| 10 分钟跑通 SPA + SSR | [快速开始](#快速开始) |
|
|
65
|
-
| 立刻体验(跑现成 demo) | 快速开始的「30 秒体验」 |
|
|
66
66
|
| 零后端原型(一个 HTML 文件) | [CDN 快速原型](#cdn-快速原型零构建纯-html) |
|
|
67
67
|
| 按任务找 API(认证/消息/AI/移动端…) | [能力速查](#能力速查任务--api) |
|
|
68
68
|
| 读完整 API 参考 | [文档导航](#文档导航) |
|
|
@@ -71,13 +71,13 @@ npm install weifuwu # 一个依赖,完整应用栈
|
|
|
71
71
|
|
|
72
72
|
## 设计理念
|
|
73
73
|
|
|
74
|
-
> 顶部「定位」回答了**是什么 /
|
|
74
|
+
> 顶部「定位」回答了**是什么 / 为什么**;以下是**哲学展开**——五条核心哲学与技术原则。
|
|
75
75
|
|
|
76
76
|
### 核心哲学
|
|
77
77
|
|
|
78
78
|
**① 一个包,全栈一体。** 后端、前端、组件、样式装在一个 npm 包里,零配置、零构建、纯 link 可用:服务端 `--import weifuwu/dev` 直接跑 `.tsx`(Node loader + esbuild 同步编译);浏览器 CDN import map 直接跑;CSS 一条 link 即得完整设计系统。
|
|
79
79
|
|
|
80
|
-
**② 全自研,诚实裁剪。** VDOM、PG v3 / RESP2 协议、GraphQL schema、OpenAI 兼容流式协议——全部自研而非包装他人。动机不是炫技而是**确定性**:自研客户端输出确定、行为可预测、错误模型统一。配套纪律是诚实裁剪:**不支持的能力明确抛 `ProtocolError('unsupported')`,绝不静默降级或"尽量支持"
|
|
80
|
+
**② 全自研,诚实裁剪。** VDOM、PG v3 / RESP2 协议、GraphQL schema、OpenAI 兼容流式协议——全部自研而非包装他人。动机不是炫技而是**确定性**:自研客户端输出确定、行为可预测、错误模型统一。配套纪律是诚实裁剪:**不支持的能力明确抛 `ProtocolError('unsupported')`,绝不静默降级或"尽量支持"**。
|
|
81
81
|
|
|
82
82
|
**③ 消灭样板。** 框架的每一层都在消灭一类样板代码:
|
|
83
83
|
|
|
@@ -85,38 +85,28 @@ npm install weifuwu # 一个依赖,完整应用栈
|
|
|
85
85
|
|---|---|
|
|
86
86
|
| 构建样板 | 动态编译(`ctx.ui.js` / `weifuwu/dev`),改代码即刷即用,零构建步骤 |
|
|
87
87
|
| 样式样板 | WUI 设计语言 + 语义原语 + 变量定制,零自定义 CSS 文件(`--wf-brand-seed` 改一个值全站换肤,暗色自动派生) |
|
|
88
|
-
|
|
|
88
|
+
| 异步样板 | `useAsyncData` 内建并发合并/竞态取消/缓存保留/SSR 种子——**作者零退订、零竞态、零 loading 样板** |
|
|
89
89
|
| 协议样板 | 自研 PG/Redis 客户端消灭双重编码、parseRow 样板、`'EX'` 参数顺序陷阱 |
|
|
90
90
|
|
|
91
|
-
**④ SaaS 地基,应用必须的一等能力。** 不只是库——rateLimit / email / userSystem / messager / queue 五个中间件随包内置,且互相咬合:**身份是消息的路由,消息是身份的交互**(`sendTo(ctx.user.id)` 按身份路由、`createConversation(ctx.user.id)` 创建者即身份、成员校验自动对齐),AI
|
|
91
|
+
**④ SaaS 地基,应用必须的一等能力。** 不只是库——rateLimit / email / userSystem / messager / queue 五个中间件随包内置,且互相咬合:**身份是消息的路由,消息是身份的交互**(`sendTo(ctx.user.id)` 按身份路由、`createConversation(ctx.user.id)` 创建者即身份、成员校验自动对齐),AI 对话走同一协议。开发者从「自建基础设施」变「声明业务」——一个多租户 AI 平台(agent-platform)已完整消费这层地基。
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
**零运行时依赖** — 前端无 npm 运行时依赖(自研 VDOM,不引入 Virtual DOM 库、rxjs、immer 等)。后端仅依赖 `esbuild`(TSX→JS 编译)+ `graphql` + `ws`(语言/协议本身)——**数据库客户端(PostgreSQL/Redis 协议)、GraphQL schema 工具全部自研**。esbuild 作为运行时依赖随 `npm install weifuwu` 自动安装,`ctx.ui.js()` 开箱即用。
|
|
96
|
-
|
|
97
|
-
**两阶段组件模型** — 组件 = `async (initProps, ctx) => (props) => Promise<VNode>`。外层工厂只执行一次(mount,可 await 数据),内层 renderFn 每次状态/props 变化时执行(强制异步)。无 class、无 `this`、无 Hook——**位置即语义**:外层天生只跑一次,没有 hooks 规则、没有依赖数组、没有闭包陷阱(详解见[核心概念](#核心概念))。
|
|
98
|
-
|
|
99
|
-
**render-only 确定性渲染** — 渲染唯一触发 `ctx.ui.render()`(闭包绑定组件),状态是普通对象(`let` + `render()`);跨组件共享用 `createStore` + `ctx.ui.useExternal()`。行为可静态推导,无隐式触发(详见组件库)。
|
|
93
|
+
**⑤ 错误现形,自愈不可消音。** 渲染错误** hole 降级 + 下一拍重试**是容错不是静默——错误必须计数现形(`error-counter` 同错去重不刷屏、恢复清出再报);路由错误同样去重计数。dev 仪表 `window.__wfRenderHealth` 四轴读数(频率/规模/复用/错误)——**问题出现即读数**,不是问题出现后翻日志。
|
|
100
94
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
**中间件注入一切** — 后端和前端共用同一理念:中间件向 `ctx` 注入能力(`ctx.sql` / `ctx.redis` / `ctx.api` / `ctx.auth` / `ctx.i18n` / `ctx.limit` / `ctx.email` / `ctx.queue` / `ctx.ai` / `ctx.msg` 等),Handler/组件从 `ctx` 读取。
|
|
104
|
-
|
|
105
|
-
**async 工厂组件** — `async (initProps, ctx) => (props) => Promise<VNode>`(weifuwu **唯一组件形态**——同步组件已不支持):工厂层声明数据(`await ctx.data.get`)、mount 初始化状态(`let` + `render()`)、render 输出视图。异步在工厂边界与 renderFn,数据经闭包注入,写数据像写同步代码。三条纪律见[核心概念 · async 组件](#核心概念)。
|
|
95
|
+
### 技术原则(哲学的展开)
|
|
106
96
|
|
|
107
|
-
|
|
97
|
+
**零运行时依赖** — 前端无 npm 运行时依赖(自研 VDOM,不引入 Virtual DOM 库、rxjs、immer 等)。后端仅依赖 `esbuild`(TSX→JS 编译)+ `graphql` + `ws`(语言/协议本身)——**数据库客户端(PostgreSQL/Redis 协议)、GraphQL schema 工具全部自研**。
|
|
108
98
|
|
|
109
|
-
|
|
99
|
+
**前后端同构,机制公用、实现不一样** — handler 签名 `(req: Request, ctx) => Response` 双端字面同构;**路由内核五层单源**(`src/shared/router/`:trie 匹配 / pipeline 流程骨架 / context URL 解析与 ctx 注入 / chain 中间件链 / ctx-fields 扩展注册表)——`Router.handler()` 与 `UIRouter.resolve()` 跑同一个 `dispatchRouter`,差异点(verb 表/404 形态/错误语义/ctx 扩展)钩子化,**serve(Request/Response 编解码边界)留各自域**。
|
|
110
100
|
|
|
111
|
-
|
|
101
|
+
**工厂同步,异步边界全在 hooks,渲染纯同步** — 组件 = `(initProps, ctx) => (props) => VNode`(**同步工厂——async 即编译错**)。数据加载 `useAsyncData`、多源汇流 `useObservable`——异步全部收敛在 hooks 内部流管道(卸载自动退订——零泄漏);渲染纯同步(无 async 渲染、无中间态占位)。**一条规则易学易写易用**。
|
|
112
102
|
|
|
113
|
-
|
|
103
|
+
**信号 getter 纪律** — 一切会变化的值 = `() => T`——**任何位置调用返回最新值**——没有「必须在 render 内调用」的位置规则。`signal` 读 getter + set/update 写,变化自动重渲染;hooks 全部 getter 化(`useScrollPosition()` 返回最新位置)。
|
|
114
104
|
|
|
115
|
-
|
|
105
|
+
**VDOM 输出透明(命令流可回放)** — 渲染 = **命令流**(NDJSON 纯数据——13 种命令完整自足:create/insert/setText/…)——DOM = fold(命令流),可记录、可回放、可断言。VDOM 对用户输入零 magic:条件渲染的 false 在 DOM 里是诊断占位注释,数组项 key 与组件实例 id 直接落 DOM——devtools 看到的 DOM 就是引擎决策的可读输出。
|
|
116
106
|
|
|
117
|
-
|
|
107
|
+
**对账防线(演绎保证——错必被抓)** — 双树终态等价对账器 + 命令流模拟器(Sim)+ 状态机迁移表(编译期穷尽)+ fuzz 生成器(1310 对多种子)——**状态机保证暴露机制完整,对账器保证错误必被抓**,两者互补缺一不可。四层验证体系见 [AGENTS.md](AGENTS.md)。
|
|
118
108
|
|
|
119
|
-
|
|
109
|
+
**中间件注入一切** — 后端和前端共用同一理念:中间件向 `ctx` 注入能力(`ctx.sql` / `ctx.redis` / `ctx.api` / `ctx.auth` / `ctx.i18n` / `ctx.limit` / `ctx.email` / `ctx.queue` / `ctx.ai` / `ctx.msg` 等),Handler/组件从 `ctx` 读取——**注入声明(injects/depends)双端同一注册表机制**。
|
|
120
110
|
|
|
121
111
|
---
|
|
122
112
|
|
|
@@ -304,7 +294,6 @@ cd apps/agent-platform && npm run seed && npm run dev
|
|
|
304
294
|
| 模块 | CDN 路径 | 说明 |
|
|
305
295
|
|------|---------|------|
|
|
306
296
|
| `weifuwu/vdom` | `https://unpkg.com/weifuwu@latest/dist/vdom/index.js` | 前端运行时(UIRouter, uiServe, h/jsx, hooks 等) |
|
|
307
|
-
| `weifuwu/vdom` | `https://unpkg.com/weifuwu@latest/dist/vdom/index.js` | 前端运行时(UIRouter, uiServe, h/jsx, hooks 等) |
|
|
308
297
|
| `weifuwu/components` | `https://unpkg.com/weifuwu@latest/dist/components/index.js` | 129 个 UI 组件(Button, Card, Table, Modal, Icon 等) |
|
|
309
298
|
| `weifuwu/components` | `https://unpkg.com/weifuwu@latest/dist/components/style.css` | 组件 CSS + 183 个主题 Token + 48 个布局原语 + 90 个工具类 |
|
|
310
299
|
| 独立布局系统 | `https://unpkg.com/weifuwu@latest/dist/layout/weifuwu-layout.css` | 仅 CSS 布局,不依赖 JS |
|
|
@@ -364,7 +353,7 @@ cd apps/agent-platform && npm run seed && npm run dev
|
|
|
364
353
|
| 渲染页面(SPA / SSR) | `UIRouter` + `uiServe(router, { root })`;SSR = `uiSsr(router, url)`(结构吸收——首帧零差异) | — · — |
|
|
365
354
|
| 数据持久化 | `postgres()` → `` ctx.sql`SELECT *` `` · `redis()` → `ctx.redis` · **`sql.query`**(Query Language AST 双后端) | — |
|
|
366
355
|
| 零数据库开发/测试 | `createMemorySql()` / `MemoryRedis`——契约同真库、替换成本为零 | — |
|
|
367
|
-
| 数据管道(SSR 预取/hydration/SPA) | `ctx.
|
|
356
|
+
| 数据管道(SSR 预取/hydration/SPA) | `ctx.ui.useAsyncData(fetcher, key)`(并发合并/竞态取消/SSR 种子) | — |
|
|
368
357
|
| 用户注册/登录/会话/多租户 | `userSystem()` → `ctx.auth` + `/api/auth/*` | — |
|
|
369
358
|
| 限流防爆破 | `rateLimit()` + `ctx.limit()` | — |
|
|
370
359
|
| 发邮件 | `email()` → `ctx.email`(Resend/SMTP) | — |
|
|
@@ -382,84 +371,80 @@ cd apps/agent-platform && npm run seed && npm run dev
|
|
|
382
371
|
|
|
383
372
|
## 核心概念
|
|
384
373
|
|
|
385
|
-
###
|
|
374
|
+
### 一条规则:工厂同步、异步在 hooks、渲染纯同步
|
|
386
375
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
376
|
+
```tsx
|
|
377
|
+
type Component<P, C> = (initProps: P, ctx: C) => RenderFn<P> // 工厂同步(毫秒即挂载完)
|
|
378
|
+
type RenderFn<P> = (props: P) => VNode | null | (VNode | null)[] // 渲染纯同步
|
|
379
|
+
```
|
|
391
380
|
|
|
392
|
-
|
|
393
|
-
|
|
381
|
+
```tsx
|
|
382
|
+
const Counter = (_init, ctx) => {
|
|
383
|
+
// 工厂(mount 一次):建 signal、订阅、定时器——同步执行
|
|
384
|
+
const count = ctx.ui.signal(0)
|
|
385
|
+
return () => (
|
|
386
|
+
// 渲染(每次变化自动执行):读 getter——最新值
|
|
387
|
+
<button onClick={() => count.set((c) => c + 1)}>{count()}</button>
|
|
388
|
+
)
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
> **为什么工厂必须同步?** 同步工厂 = 毫秒级挂载(无 mounting 窗口、无 async
|
|
393
|
+
> 竞态、SSR/客户端行为完全一致)。异步需求全部收敛到 hooks 内部流管道——
|
|
394
|
+
> 作者层面**没有 await 渲染**这件事。事件回调内 `await` 合法(非渲染路径)。
|
|
394
395
|
|
|
395
|
-
|
|
396
|
+
### 数据:useAsyncData(唯一异步边界)
|
|
396
397
|
|
|
397
398
|
```tsx
|
|
398
|
-
const
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
399
|
+
const Home = (_: unknown, ctx: UIContext) => {
|
|
400
|
+
const [getMsg, reload] = ctx.ui.useAsyncData(
|
|
401
|
+
() => fetch('/api/hello').then((r) => r.json()), 'hello')
|
|
402
|
+
const msg = getMsg() // getter——最新值(null = loading/降级)
|
|
403
|
+
if (!msg) return <p>加载中…</p>
|
|
404
|
+
return <h1>{msg.msg} <button onClick={reload}>刷新</button></h1>
|
|
404
405
|
}
|
|
405
406
|
```
|
|
406
407
|
|
|
407
|
-
|
|
408
|
-
|
|
408
|
+
**内建语义(作者零代码)**:同 key 并发合并(N 组件 fetch 1 次)· 竞态取消
|
|
409
|
+
(旧请求作废)· 缓存保留(重挂载零请求)· 卸载自动退订(零泄漏)· SSR 种子
|
|
410
|
+
预热(首帧零请求)。多源汇流:`ctx.ui.useObservable(obs$)` + `combineLatest`。
|
|
409
411
|
|
|
410
|
-
###
|
|
412
|
+
### 路由:前后端同一内核
|
|
411
413
|
|
|
412
414
|
```
|
|
413
|
-
后端: app
|
|
414
|
-
app.
|
|
415
|
-
app
|
|
416
|
-
// ctx 已注入 ctx.sql
|
|
415
|
+
后端: const app = new Router()
|
|
416
|
+
app.get('/u/:id', (req, ctx) => Response.json(ctx.params))
|
|
417
|
+
serve(app, { port: 3000 })
|
|
417
418
|
|
|
418
419
|
前端: const router = new UIRouter()
|
|
419
|
-
router.get('/
|
|
420
|
-
|
|
421
|
-
// handler 执行——与后端 dispatchRouter 同一内核(shared/router
|
|
422
|
-
// 五层单源:trie/pipeline/context/chain/ctx-fields)
|
|
423
|
-
// ctx 注入:params/query/route(enrichCtx)——对齐后端中间件注入
|
|
420
|
+
router.get('/u/:id', (req, ctx) => h('h1', {}, ctx.params.id))
|
|
421
|
+
uiServe(router, { root: '#root' })
|
|
424
422
|
```
|
|
425
423
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
| 模式 | 后端 | 前端 |
|
|
431
|
-
|------|------|------|
|
|
432
|
-
| 注入 | 中间件注入 ctx.field | 中间件注入 ctx.field |
|
|
433
|
-
| 读取 | handler 读取 ctx | 组件读取 ctx |
|
|
434
|
-
| 渲染 | 返回 Response | `ctx.ui.render()` 触发局部 VDOM patch(render-only);共享状态 `createStore` + `useExternal` |
|
|
424
|
+
**`(req: Request, ctx) => Response` 字面同构**——两者内部跑同一个
|
|
425
|
+
`dispatchRouter`(`src/shared/router/` 五层单源:trie 匹配 / pipeline 骨架 /
|
|
426
|
+
context 解析与注入 / chain 中间件链 / ctx-fields 注册表)。差异点钩子化:
|
|
427
|
+
verb 表(server)/ctx.route 注入(client)/404 形态——**serve(编解码边界)留各自域**。
|
|
435
428
|
|
|
436
|
-
###
|
|
429
|
+
### 中间件注入一切(前后端一致)
|
|
437
430
|
|
|
438
|
-
async 组件让"拿数据渲染页面"像写同步代码——签名与同步组件一致,唯一差别是 `async` 关键字:`async (initProps, ctx) => renderFn`。异步只在工厂边界:
|
|
439
|
-
|
|
440
|
-
```tsx
|
|
441
|
-
const UserProfile = async (_init, ctx) => {
|
|
442
|
-
const user = await ctx.data.get(`/api/user/${ctx.params.id}`) // ① 工厂层:声明数据(三场景自动)
|
|
443
|
-
let liked = false // ② mount:客户端状态(render-only)
|
|
444
|
-
return async (props) =>
|
|
445
|
-
h('div', {},
|
|
446
|
-
h('p', {}, user.name), // 服务端状态(闭包,SSR 进 HTML)
|
|
447
|
-
h('button', { onClick: () => { liked = !liked; ctx.ui.render() } }, liked ? '❤️' : '🤍'))
|
|
448
|
-
}
|
|
449
431
|
```
|
|
432
|
+
后端: app.use(cors()); app.use(postgres())
|
|
433
|
+
app.get('/users', (req, ctx) => ctx.sql`SELECT *`)
|
|
434
|
+
// ctx 已注入 ctx.sql——injects/depends 声明受检
|
|
450
435
|
|
|
451
|
-
|
|
436
|
+
前端: ctx.ui.useAsyncData / useObservable / useChat / hold
|
|
437
|
+
// ctx.ui 渲染原语面——信号 getter 纪律
|
|
438
|
+
```
|
|
452
439
|
|
|
453
|
-
|
|
454
|
-
|---|---|---|
|
|
455
|
-
| ① 数据 key 必须含维度 | `ctx.data.get('/api/user')`——`/users/1 → /users/2` 导航命中旧缓存 | `ctx.data.get(\`/api/user/${ctx.params.id}\`)` |
|
|
456
|
-
| ② 会变的数据放组件状态 | `const count = data.count`——点击永不更新 | `let count = data.count` + 交互后 `ctx.ui.render()`(初始值 seed 自服务端数据) |
|
|
457
|
-
| ③ 初始状态必须确定性 | `let w = window.innerWidth`——SSR/hydration mismatch | 用服务端数据 seed,交互后再测 |
|
|
440
|
+
### 状态管理
|
|
458
441
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
442
|
+
| 场景 | API | 语义 |
|
|
443
|
+
|------|-----|------|
|
|
444
|
+
| 组件内状态 | `ctx.ui.signal(n)` | getter 读 + set/update 写——变化自动重渲染 |
|
|
445
|
+
| 跨组件共享 | `createStore` + `ctx.ui.useExternal(store)` | 共享状态订阅——getter 最新 |
|
|
446
|
+
| 任意 Observable | `ctx.ui.useObservable(obs$, initial)` | rxjs 风格流 → getter(卸载自动退订) |
|
|
447
|
+
| 服务端状态 | handler 返回 Response | 每请求独立 |
|
|
463
448
|
|
|
464
449
|
### 渲染策略:SPA 还是 SSR?
|
|
465
450
|
|
|
@@ -471,14 +456,20 @@ const UserProfile = async (_init, ctx) => {
|
|
|
471
456
|
| 后端 | HTML 外壳 | `uiSsr(router, url)`(完整 HTML + `__DATA__` 种子——同一棵路由树) |
|
|
472
457
|
| 客户端 | `uiServe(router, { root })` | **结构吸收**(首帧复用服务端 DOM——焦点/输入值保持——失败原子回退重建) |
|
|
473
458
|
|
|
474
|
-
**怎么选**:默认 SPA;需要 SEO 或首屏即内容时用 SSR
|
|
459
|
+
**怎么选**:默认 SPA;需要 SEO 或首屏即内容时用 SSR。两种模式可混合。
|
|
475
460
|
|
|
476
|
-
###
|
|
461
|
+
### VDOM 输出透明
|
|
477
462
|
|
|
478
|
-
|
|
463
|
+
- **命令流可回放**:渲染 = NDJSON 命令流(纯数据)——DOM = fold(命令流)——
|
|
464
|
+
可记录/回放/断言(测试基础设施与渲染本体同源)
|
|
465
|
+
- **诊断占位**:条件渲染 false → `<!--wf-hole-->` 注释锚(兄弟不误删)
|
|
466
|
+
- **id 落 DOM**:`data-wf-id` / `data-wf-key`——devtools 即引擎决策日志
|
|
467
|
+
- **非法输入显式化**:重复 key warn(不静默)、事件非函数 warn、受控缺回调
|
|
468
|
+
warn——**A 级检测兜底**
|
|
479
469
|
|
|
480
|
-
|
|
470
|
+
### Closeable 接口
|
|
481
471
|
|
|
472
|
+
所有有状态模块(postgres、redis)实现 `close(): Promise<void>`,serve 关闭时自动调用(WS 1001 握手先行 + 重复 close 幂等)。
|
|
482
473
|
|
|
483
474
|
---
|
|
484
475
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "weifuwu",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.89.
|
|
4
|
+
"version": "0.89.1",
|
|
5
5
|
"description": "AI SaaS full-stack framework — backend HTTP + frontend VDOM + 129 components + CSS design system + SaaS foundation (auth/queue/AI). Zero-build, zero-dependency.",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|