weifuwu 0.87.0 → 0.89.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.
Files changed (392) hide show
  1. package/README.md +111 -135
  2. package/dist/client/components/ActionSheet/ActionSheet.d.ts +1 -1
  3. package/dist/client/components/AiChat/AiChat.d.ts +3 -4
  4. package/dist/client/components/ChatInput/ChatInput.d.ts +5 -0
  5. package/dist/client/components/CitationCard/CitationCard.d.ts +1 -1
  6. package/dist/client/components/Command/Command.d.ts +1 -1
  7. package/dist/client/components/ContextMenu/ContextMenu.d.ts +1 -1
  8. package/dist/client/components/DatePicker/DatePicker.d.ts +1 -1
  9. package/dist/client/components/Dropdown/Dropdown.d.ts +1 -1
  10. package/dist/client/components/Editor/model/html.d.ts +10 -0
  11. package/dist/client/components/ImageCropper/ImageCropper.d.ts +6 -2
  12. package/dist/client/components/Menu/Menu.d.ts +1 -1
  13. package/dist/client/components/NavMenu/NavMenu.d.ts +1 -1
  14. package/dist/client/components/Popconfirm/Popconfirm.d.ts +1 -1
  15. package/dist/client/components/SlideCanvas/SlideCanvas.d.ts +1 -1
  16. package/dist/client/components/Tour/Tour.d.ts +1 -1
  17. package/dist/client/components/TreeSelect/TreeSelect.d.ts +1 -1
  18. package/dist/client/components/index.js +41 -41
  19. package/dist/client/components/style.css +298 -644
  20. package/dist/client/layout/weifuwu-layout.css +286 -640
  21. package/dist/client/vdom/browser/Browser.d.ts +2 -0
  22. package/dist/client/vdom/commands.d.ts +4 -1
  23. package/dist/client/vdom/context/UIContext.d.ts +8 -0
  24. package/dist/client/vdom/core/diff/attrs.d.ts +10 -2
  25. package/dist/client/vdom/core/field/attributes.d.ts +12 -0
  26. package/dist/client/vdom/core/field/events.d.ts +8 -0
  27. package/dist/client/vdom/core/field/input-sync.d.ts +14 -0
  28. package/dist/client/vdom/core/field/key.d.ts +3 -0
  29. package/dist/client/vdom/core/field/props.d.ts +13 -1
  30. package/dist/client/vdom/core/field/ref.d.ts +6 -0
  31. package/dist/client/vdom/core/field/style.d.ts +7 -1
  32. package/dist/client/vdom/core/node/component.d.ts +3 -4
  33. package/dist/client/vdom/core/node/native.d.ts +6 -1
  34. package/dist/client/vdom/core/patch/index.d.ts +24 -1
  35. package/dist/client/vdom/core/patch/processors.d.ts +8 -1
  36. package/dist/client/vdom/core/protocol.d.ts +45 -0
  37. package/dist/client/vdom/core/router.d.ts +7 -0
  38. package/dist/client/vdom/core/ssr/absorb.d.ts +31 -4
  39. package/dist/client/vdom/core/transform/index.d.ts +6 -6
  40. package/dist/client/vdom/core/v2/cycle.d.ts +54 -0
  41. package/dist/client/vdom/core/v2/diff.d.ts +80 -0
  42. package/dist/client/vdom/core/v2/integrate.d.ts +24 -0
  43. package/dist/client/vdom/core/v2/render.d.ts +28 -0
  44. package/dist/client/vdom/core/v2/schedule.d.ts +33 -0
  45. package/dist/client/vdom/core/v2/serve.d.ts +4 -0
  46. package/dist/client/vdom/core/v2/spy.d.ts +21 -0
  47. package/dist/client/vdom/core/v2/ssr.d.ts +14 -0
  48. package/dist/client/vdom/core/vnode.d.ts +9 -6
  49. package/dist/client/vdom/dev/error-counter.d.ts +27 -0
  50. package/dist/client/vdom/dev/render-health.d.ts +60 -0
  51. package/dist/client/vdom/hooks/chat.d.ts +32 -5
  52. package/dist/client/vdom/hooks/drag-media.d.ts +3 -1
  53. package/dist/client/vdom/hooks/env.d.ts +32 -0
  54. package/dist/client/vdom/hooks/popup-manager.d.ts +21 -0
  55. package/dist/client/vdom/hooks/popup.d.ts +4 -1
  56. package/dist/client/vdom/hooks/stable.d.ts +4 -0
  57. package/dist/client/vdom/hooks/use-observable.d.ts +13 -0
  58. package/dist/client/vdom/index.d.ts +14 -6
  59. package/dist/client/vdom/index.js +4 -7
  60. package/dist/client/vdom/jsx-runtime.js +1 -1
  61. package/dist/client/vdom/middlewares/api.d.ts +8 -0
  62. package/dist/client/vdom/middlewares/auth-i18n.d.ts +7 -0
  63. package/dist/client/vdom/middlewares/ws.d.ts +41 -1
  64. package/dist/client/vdom/observable/index.d.ts +13 -0
  65. package/dist/client/vdom/observable/observable.d.ts +26 -0
  66. package/dist/client/vdom/observable/operators.d.ts +76 -0
  67. package/dist/client/vdom/observable/sources.d.ts +54 -0
  68. package/dist/client/vdom/observable/types.d.ts +45 -0
  69. package/dist/client/vdom/store.d.ts +17 -2
  70. package/dist/client/vdom/testing.js +2 -2
  71. package/dist/server/ai/agent.d.ts +18 -0
  72. package/dist/server/ai/client.d.ts +14 -2
  73. package/dist/server/ai/contracts.d.ts +2 -2
  74. package/dist/server/ai/sse.d.ts +6 -0
  75. package/dist/server/core/collect.d.ts +31 -0
  76. package/dist/server/core/error-counter.d.ts +11 -0
  77. package/dist/server/core/hub.d.ts +2 -0
  78. package/dist/server/core/router.d.ts +12 -9
  79. package/dist/server/db/contracts.d.ts +24 -0
  80. package/dist/server/db/memory-redis.d.ts +3 -4
  81. package/dist/server/db/memory-sql.d.ts +20 -4
  82. package/dist/server/db/postgres-server.d.ts +2 -0
  83. package/dist/server/db/query.d.ts +21 -0
  84. package/dist/server/db/redis-server.d.ts +4 -2
  85. package/dist/server/db/sql-parser.d.ts +2 -1
  86. package/dist/server/email/index.d.ts +2 -0
  87. package/dist/server/index.d.ts +4 -1
  88. package/dist/server/index.js +3927 -1982
  89. package/dist/server/messager/index.d.ts +21 -2
  90. package/dist/server/middleware/compress.d.ts +25 -0
  91. package/dist/server/ui/index.d.ts +8 -1
  92. package/dist/server/user/index.d.ts +4 -0
  93. package/dist/shared/router/chain.d.ts +9 -0
  94. package/dist/shared/router/context.d.ts +35 -0
  95. package/dist/shared/router/ctx-fields.d.ts +20 -0
  96. package/dist/shared/router/pipeline.d.ts +70 -0
  97. package/dist/shared/router/trie.d.ts +11 -3
  98. package/dist/shared/router/types.d.ts +23 -0
  99. package/docs/style-guide.md +44 -41
  100. package/package.json +13 -10
  101. package/content/apps/admin.md +0 -55
  102. package/content/apps/agent-platform.md +0 -35
  103. package/content/apps/auth.md +0 -49
  104. package/content/apps/multi.md +0 -44
  105. package/content/apps/todo.md +0 -51
  106. package/content/backend/ai.md +0 -28
  107. package/content/backend/auth.md +0 -20
  108. package/content/backend/email.md +0 -23
  109. package/content/backend/graphql.md +0 -23
  110. package/content/backend/http.md +0 -23
  111. package/content/backend/limit.md +0 -23
  112. package/content/backend/middleware.md +0 -17
  113. package/content/backend/queue.md +0 -23
  114. package/content/backend/redis.md +0 -26
  115. package/content/backend/router.md +0 -23
  116. package/content/backend/schedule.md +0 -23
  117. package/content/backend/sql.md +0 -27
  118. package/content/backend/sse.md +0 -26
  119. package/content/backend/ws.md +0 -26
  120. package/content/capabilities/app-node.md +0 -21
  121. package/content/capabilities/data.md +0 -21
  122. package/content/capabilities/events.md +0 -21
  123. package/content/capabilities/hooks.md +0 -22
  124. package/content/capabilities/i18n.md +0 -21
  125. package/content/capabilities/popup.md +0 -21
  126. package/content/capabilities/render-only.md +0 -23
  127. package/content/capabilities/router.md +0 -22
  128. package/content/capabilities/self-id.md +0 -21
  129. package/content/capabilities/store.md +0 -21
  130. package/content/capabilities/theme.md +0 -21
  131. package/content/capabilities/two-phase.md +0 -21
  132. package/content/components/accordion.md +0 -50
  133. package/content/components/actionsheet.md +0 -46
  134. package/content/components/affix.md +0 -51
  135. package/content/components/aichat.md +0 -57
  136. package/content/components/alert.md +0 -52
  137. package/content/components/alertgroup.md +0 -45
  138. package/content/components/anchor.md +0 -52
  139. package/content/components/app.md +0 -37
  140. package/content/components/approvalcard.md +0 -56
  141. package/content/components/appshell.md +0 -50
  142. package/content/components/aspectratio.md +0 -47
  143. package/content/components/authpage.md +0 -61
  144. package/content/components/autocomplete-v2.md +0 -40
  145. package/content/components/autocomplete.md +0 -56
  146. package/content/components/avatar.md +0 -51
  147. package/content/components/avatargroup.md +0 -47
  148. package/content/components/backtop.md +0 -53
  149. package/content/components/badge.md +0 -55
  150. package/content/components/breadcrumb.md +0 -49
  151. package/content/components/button.md +0 -63
  152. package/content/components/calendar-v2.md +0 -40
  153. package/content/components/calendar.md +0 -50
  154. package/content/components/card.md +0 -58
  155. package/content/components/carousel.md +0 -52
  156. package/content/components/cascader-v2.md +0 -39
  157. package/content/components/cascader.md +0 -54
  158. package/content/components/chart.md +0 -53
  159. package/content/components/chatinput.md +0 -63
  160. package/content/components/checkbox.md +0 -51
  161. package/content/components/checkboxgroup.md +0 -52
  162. package/content/components/citationcard.md +0 -48
  163. package/content/components/codeblock.md +0 -48
  164. package/content/components/codeeditor.md +0 -47
  165. package/content/components/collapse.md +0 -49
  166. package/content/components/colorpicker.md +0 -50
  167. package/content/components/command.md +0 -50
  168. package/content/components/confirm.md +0 -57
  169. package/content/components/contextmenu.md +0 -48
  170. package/content/components/copybutton.md +0 -51
  171. package/content/components/datepicker.md +0 -52
  172. package/content/components/descriptions-v2.md +0 -39
  173. package/content/components/descriptions.md +0 -52
  174. package/content/components/diffview-v2.md +0 -39
  175. package/content/components/diffview.md +0 -50
  176. package/content/components/divider.md +0 -48
  177. package/content/components/drawer.md +0 -55
  178. package/content/components/dropdown.md +0 -53
  179. package/content/components/editor.md +0 -49
  180. package/content/components/emptystate.md +0 -51
  181. package/content/components/exportcsv.md +0 -38
  182. package/content/components/field.md +0 -54
  183. package/content/components/filepreview-office.md +0 -37
  184. package/content/components/filepreview.md +0 -49
  185. package/content/components/filetree.md +0 -56
  186. package/content/components/fileupload-v2.md +0 -39
  187. package/content/components/fileupload.md +0 -57
  188. package/content/components/floatbutton.md +0 -52
  189. package/content/components/form-v2.md +0 -41
  190. package/content/components/form.md +0 -60
  191. package/content/components/grid.md +0 -51
  192. package/content/components/highlight-v2.md +0 -39
  193. package/content/components/highlight.md +0 -46
  194. package/content/components/hovercard.md +0 -51
  195. package/content/components/icon.md +0 -50
  196. package/content/components/imagecropper.md +0 -45
  197. package/content/components/img.md +0 -56
  198. package/content/components/infinitescroll-v2.md +0 -40
  199. package/content/components/infinitescroll.md +0 -54
  200. package/content/components/input.md +0 -65
  201. package/content/components/inputnumber.md +0 -58
  202. package/content/components/inview.md +0 -56
  203. package/content/components/jsonschemaform.md +0 -53
  204. package/content/components/jsonviewer-v2.md +0 -39
  205. package/content/components/jsonviewer.md +0 -49
  206. package/content/components/kanban.md +0 -46
  207. package/content/components/label.md +0 -48
  208. package/content/components/layout.md +0 -54
  209. package/content/components/layoutcontent.md +0 -43
  210. package/content/components/layoutheader.md +0 -43
  211. package/content/components/layoutsider.md +0 -43
  212. package/content/components/link.md +0 -52
  213. package/content/components/list.md +0 -44
  214. package/content/components/loading.md +0 -45
  215. package/content/components/logviewer-v2.md +0 -39
  216. package/content/components/logviewer.md +0 -53
  217. package/content/components/markdown.md +0 -51
  218. package/content/components/markdowneditor.md +0 -47
  219. package/content/components/math.md +0 -42
  220. package/content/components/mentions-v2.md +0 -39
  221. package/content/components/mentions.md +0 -52
  222. package/content/components/menu.md +0 -56
  223. package/content/components/menubar.md +0 -44
  224. package/content/components/messagebubble.md +0 -50
  225. package/content/components/modal.md +0 -59
  226. package/content/components/navmenu.md +0 -47
  227. package/content/components/notification.md +0 -51
  228. package/content/components/pageheader.md +0 -53
  229. package/content/components/pagination.md +0 -49
  230. package/content/components/paragraph.md +0 -45
  231. package/content/components/passwordinput.md +0 -57
  232. package/content/components/pininput-v2.md +0 -39
  233. package/content/components/pininput.md +0 -50
  234. package/content/components/pipeline.md +0 -49
  235. package/content/components/popconfirm.md +0 -58
  236. package/content/components/popover.md +0 -61
  237. package/content/components/progressbar.md +0 -50
  238. package/content/components/prompttemplate.md +0 -49
  239. package/content/components/qrcode.md +0 -50
  240. package/content/components/radiogroup.md +0 -53
  241. package/content/components/rate.md +0 -53
  242. package/content/components/reasoningblock.md +0 -47
  243. package/content/components/relationgraph.md +0 -52
  244. package/content/components/resizable.md +0 -53
  245. package/content/components/result.md +0 -49
  246. package/content/components/scrollbar.md +0 -49
  247. package/content/components/searchinput.md +0 -51
  248. package/content/components/segmentedcontrol.md +0 -59
  249. package/content/components/select-searchable.md +0 -47
  250. package/content/components/select.md +0 -66
  251. package/content/components/sessionlist.md +0 -52
  252. package/content/components/sheetgrid.md +0 -44
  253. package/content/components/skeleton.md +0 -55
  254. package/content/components/slidecanvas.md +0 -44
  255. package/content/components/slider.md +0 -62
  256. package/content/components/sortablelist.md +0 -39
  257. package/content/components/space.md +0 -52
  258. package/content/components/sparkline.md +0 -52
  259. package/content/components/statcard-countdown.md +0 -39
  260. package/content/components/statcard.md +0 -56
  261. package/content/components/steps.md +0 -48
  262. package/content/components/switch.md +0 -50
  263. package/content/components/tabbar.md +0 -45
  264. package/content/components/table-v2.md +0 -40
  265. package/content/components/table.md +0 -66
  266. package/content/components/tabs.md +0 -56
  267. package/content/components/tag.md +0 -51
  268. package/content/components/tagsinput-v2.md +0 -39
  269. package/content/components/tagsinput.md +0 -54
  270. package/content/components/text.md +0 -45
  271. package/content/components/textarea.md +0 -57
  272. package/content/components/themeswitch.md +0 -69
  273. package/content/components/timeline.md +0 -50
  274. package/content/components/title.md +0 -45
  275. package/content/components/toast.md +0 -52
  276. package/content/components/toggle-togglegroup.md +0 -52
  277. package/content/components/togglegroup.md +0 -47
  278. package/content/components/toolcallcard.md +0 -51
  279. package/content/components/tooltip.md +0 -50
  280. package/content/components/tour.md +0 -50
  281. package/content/components/transfer.md +0 -52
  282. package/content/components/tree-v2.md +0 -39
  283. package/content/components/tree.md +0 -59
  284. package/content/components/treeselect.md +0 -54
  285. package/content/components/typography.md +0 -51
  286. package/content/components/videoplayer.md +0 -52
  287. package/content/components/virtuallist.md +0 -52
  288. package/content/components/virtualtable-v2.md +0 -39
  289. package/content/components/virtualtable.md +0 -55
  290. package/content/components/watermark.md +0 -53
  291. package/content/components/wave.md +0 -43
  292. package/content/guides/ai-chat-family.md +0 -24
  293. package/content/guides/choose.md +0 -67
  294. package/content/guides/component-model.md +0 -32
  295. package/content/guides/component-standards.md +0 -123
  296. package/content/guides/components-guide.md +0 -813
  297. package/content/guides/custom-component.md +0 -321
  298. package/content/guides/data-guide.md +0 -281
  299. package/content/guides/file-preview-family.md +0 -18
  300. package/content/guides/frontend.md +0 -968
  301. package/content/guides/layout-choice.md +0 -64
  302. package/content/guides/layout-guide.md +0 -241
  303. package/content/guides/middleware.md +0 -459
  304. package/content/guides/mobile-guide.md +0 -105
  305. package/content/guides/page-building.md +0 -98
  306. package/content/guides/production.md +0 -37
  307. package/content/guides/quality.md +0 -60
  308. package/content/guides/realtime-guide.md +0 -296
  309. package/content/guides/render-only.md +0 -44
  310. package/content/guides/saas-guide.md +0 -253
  311. package/content/guides/server-guide.md +0 -359
  312. package/content/guides/start.md +0 -41
  313. package/content/guides/styling.md +0 -154
  314. package/content/guides/ui-dom-guide.md +0 -157
  315. package/content/index.json +0 -4344
  316. package/content/index.md +0 -181
  317. package/content/layout/align.md +0 -20
  318. package/content/layout/anchor.md +0 -19
  319. package/content/layout/app-shell.md +0 -23
  320. package/content/layout/border.md +0 -20
  321. package/content/layout/center.md +0 -19
  322. package/content/layout/cluster.md +0 -19
  323. package/content/layout/container.md +0 -19
  324. package/content/layout/fill.md +0 -19
  325. package/content/layout/grid.md +0 -19
  326. package/content/layout/hidden.md +0 -22
  327. package/content/layout/layer.md +0 -19
  328. package/content/layout/position.md +0 -22
  329. package/content/layout/row.md +0 -23
  330. package/content/layout/safe-area.md +0 -20
  331. package/content/layout/scroll.md +0 -21
  332. package/content/layout/spacing.md +0 -22
  333. package/content/layout/split.md +0 -19
  334. package/content/layout/stack.md +0 -19
  335. package/content/layout/surface.md +0 -23
  336. package/content/layout/text.md +0 -22
  337. package/content/patterns/app-shell.md +0 -34
  338. package/content/patterns/dashboard.md +0 -36
  339. package/content/patterns/data-screen.md +0 -30
  340. package/content/patterns/detail-page.md +0 -31
  341. package/content/patterns/docs.md +0 -34
  342. package/content/patterns/focus-task.md +0 -34
  343. package/content/patterns/landing.md +0 -35
  344. package/content/patterns/list-page.md +0 -32
  345. package/content/patterns/mobile.md +0 -31
  346. package/content/patterns/settings-page.md +0 -33
  347. package/content/patterns/workspace.md +0 -32
  348. package/dist/cli/content-sync.test.d.ts +0 -1
  349. package/dist/cli/docs-cli.test.d.ts +0 -1
  350. package/dist/cli/docs.d.ts +0 -2
  351. package/dist/cli/docs.mjs +0 -10961
  352. package/dist/client/vdom/core/build.d.ts +0 -36
  353. package/dist/client/vdom/core/diff/children.d.ts +0 -29
  354. package/dist/client/vdom/core/diff/index.d.ts +0 -22
  355. package/dist/client/vdom/core/diff/output.d.ts +0 -30
  356. package/dist/client/vdom/core/diff/same.d.ts +0 -22
  357. package/dist/client/vdom/core/serve.d.ts +0 -75
  358. package/dist/client/vdom/core/ssr/index.d.ts +0 -25
  359. package/examples/apps/admin/README.md +0 -23
  360. package/examples/apps/admin/api.ts +0 -27
  361. package/examples/apps/admin/app.tsx +0 -144
  362. package/examples/apps/admin/main.tsx +0 -8
  363. package/examples/apps/admin/server.ts +0 -37
  364. package/examples/apps/auth/README.md +0 -23
  365. package/examples/apps/auth/api.ts +0 -49
  366. package/examples/apps/auth/app.tsx +0 -137
  367. package/examples/apps/auth/main.tsx +0 -8
  368. package/examples/apps/auth/server.ts +0 -37
  369. package/examples/apps/multi/README.md +0 -23
  370. package/examples/apps/multi/app.tsx +0 -83
  371. package/examples/apps/multi/main.tsx +0 -8
  372. package/examples/apps/multi/server.ts +0 -33
  373. package/examples/apps/todo/README.md +0 -23
  374. package/examples/apps/todo/api.ts +0 -26
  375. package/examples/apps/todo/app.tsx +0 -128
  376. package/examples/apps/todo/main.tsx +0 -9
  377. package/examples/apps/todo/server.ts +0 -39
  378. package/examples/hello-world/README.md +0 -9
  379. package/examples/hello-world/client.ts +0 -14
  380. package/examples/hello-world/routes.tsx +0 -26
  381. package/examples/hello-world/server.ts +0 -46
  382. package/examples/patterns/AppShell.tsx +0 -224
  383. package/examples/patterns/Dashboard.tsx +0 -153
  384. package/examples/patterns/DataScreen.tsx +0 -104
  385. package/examples/patterns/DetailPage.tsx +0 -79
  386. package/examples/patterns/Docs.tsx +0 -105
  387. package/examples/patterns/FocusTask.tsx +0 -67
  388. package/examples/patterns/Landing.tsx +0 -90
  389. package/examples/patterns/ListPage.tsx +0 -77
  390. package/examples/patterns/Mobile.tsx +0 -154
  391. package/examples/patterns/SettingsPage.tsx +0 -98
  392. package/examples/patterns/SplitWorkspace.tsx +0 -113
@@ -1,137 +0,0 @@
1
- /**
2
- * auth 应用模板——登录/注册 → 受保护页 → 登出(完整全栈,复制即用)
3
- *
4
- * 演示能力:
5
- * - 应用级状态(createStore user)+ 路由守卫(未登录 → 登录页)
6
- * - 表单校验 + 提交 loading + 错误条(AuthPage 组件)
7
- * - 后端:内存用户表 + 会话(api.ts 注册函数——独立/嵌入共享)
8
- * - 会话持久化:ctx.browser.storage(浏览器纪律)
9
- */
10
- import { UIRouter, uiServe, h, createStore, createClientBrowser } from 'weifuwu/vdom'
11
- import type { Component } from 'weifuwu/vdom'
12
- import { AuthPage, Input, Button, Alert, PageHeader, Avatar, Space } from 'weifuwu/components'
13
-
14
- export interface User { id: string; email: string; name: string }
15
-
16
- export const authStore = createStore<{ user: User | null; error: string | null }>({ user: null, error: null })
17
-
18
- async function api(browser: any, path: string, body: Record<string, string>): Promise<{ ok: boolean; user?: User; error?: string }> {
19
- const token = browser.storageGet('auth:token')
20
- const res = await fetch(path, {
21
- method: 'POST',
22
- headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) },
23
- body: JSON.stringify(body),
24
- })
25
- return res.json()
26
- }
27
-
28
- /** 登录/注册页(AuthPage 组件——同一骨架两用) */
29
- const AuthFormPage = (mode: 'login' | 'register'): Component =>
30
- async (_init: any, ctx: any) => {
31
- const browser = ctx.browser
32
- let email = ''
33
- let password = ''
34
- let name = ''
35
- let error = ''
36
- let loading = false
37
- const submit = async () => {
38
- error = ''
39
- if (!email.includes('@')) { error = '请输入有效邮箱'; ctx.render(); return }
40
- if (password.length < 6) { error = '密码至少 6 位'; ctx.render(); return }
41
- loading = true; ctx.render()
42
- const res = await api(browser, `/api/auth/${mode}`, { email, password, name })
43
- loading = false
44
- if (res.ok && res.user) {
45
- authStore.update((s) => { s.user = res.user!; s.error = null })
46
- browser.storageSet('auth:token', (res as any).token ?? '')
47
- location.hash = '#/'
48
- } else {
49
- error = res.error ?? '请求失败'
50
- }
51
- ctx.render()
52
- }
53
- return async (_p: any) => (
54
- <AuthPage
55
- title={mode === 'login' ? '登录' : '注册'}
56
- subtitle="auth 应用模板——内存用户 + 会话(演示)"
57
- logo={<Avatar name="wf" />}
58
- submitLabel={mode === 'login' ? '登录' : '注册'}
59
- loading={loading}
60
- error={error || null}
61
- onSubmit={() => void submit()}
62
- footer={<a href={mode === 'login' ? '#/register' : '#/login'} class="wf-link" style="cursor:pointer">{mode === 'login' ? '没有账号?注册' : '已有账号?登录'}</a>}
63
- >
64
- {mode === 'register' && (
65
- <Input label="昵称" value={name} onInput={(e: any) => { name = (e.target as HTMLInputElement).value; ctx.render() }} placeholder="如何称呼你" />
66
- )}
67
- <Input label="邮箱" type="email" value={email} onInput={(e: any) => { email = (e.target as HTMLInputElement).value; ctx.render() }} placeholder="name@example.com" required />
68
- <Input label="密码" type="password" value={password} onInput={(e: any) => { password = (e.target as HTMLInputElement).value; ctx.render() }} placeholder="至少 6 位" />
69
- </AuthPage>
70
- )
71
- }
72
-
73
- export const LoginPage = AuthFormPage('login')
74
- export const RegisterPage = AuthFormPage('register')
75
-
76
- /** 受保护页(未登录 → 登录页——路由守卫) */
77
- export const DashboardPage: Component = async (_init: any, ctx: any) => {
78
- const browser = ctx.browser
79
- // 会话恢复(mount——ctx.browser.storage 安全适配)
80
- void (async () => {
81
- try {
82
- const token = browser.storageGet('auth:token')
83
- if (!token) return
84
- const res = await fetch('/api/auth/me', { headers: { Authorization: `Bearer ${token}` } })
85
- if (res.ok) {
86
- const { user } = await res.json()
87
- authStore.update((s) => { s.user = user })
88
- }
89
- } catch { /* 无会话 */ }
90
- })()
91
- const state = ctx.ui.useExternal(authStore)
92
- const logout = () => {
93
- browser.storageRemove('auth:token')
94
- authStore.update((s) => { s.user = null })
95
- location.hash = '#/login'
96
- }
97
- return async (_p: any) => {
98
- if (!state.user) return h(LoginPage, {})
99
- const u = state.user
100
- return (
101
- <div class="wf-stack wf-gap-md" style="max-width:560px">
102
- <PageHeader
103
- title={`欢迎,${u.name ?? u.email}`}
104
- sub="登录态由 createStore 持有——路由守卫读它决定渲染"
105
- >
106
- <Button variant="ghost" onClick={logout}>登出</Button>
107
- </PageHeader>
108
- <div class="wf-surface wf-border wf-rounded-md wf-p-md wf-stack wf-gap-sm">
109
- <Space align="center" size="md"><Avatar name={u.name ?? u.email} size="lg" /><b>{u.email}</b></Space>
110
- <Alert variant="success">✓ 已登录——受保护内容可见。刷新页面会话自动恢复(ctx.browser.storage)</Alert>
111
- </div>
112
- </div>
113
- )
114
- }
115
- }
116
-
117
- /** 路由守卫:包装页面——未登录跳登录(守卫逻辑在页面组件内读 store) */
118
- export const guard = (page: Component): Component => page
119
-
120
- // ── 路由表 ──
121
- export const authRoutes = [
122
- { path: '/', render: () => h(DashboardPage, {}) },
123
- { path: '/login', render: () => h(LoginPage, {}) },
124
- { path: '/register', render: () => h(RegisterPage, {}) },
125
- ]
126
-
127
- export const pathFromHash = (): string => location.hash.replace(/^#/, '') || '/'
128
-
129
- export function createAuthApp(root: HTMLElement, _options?: { history?: boolean }): ReturnType<typeof uiServe> {
130
- // vdom 规范面:UIRouter + uiServe——布局共享精准路由
131
- const router = new UIRouter()
132
- for (const r of authRoutes) {
133
- router.get(r.path, (req: Request, ctx: any) =>
134
- (ctx as { stream: (v: unknown) => Response }).stream(r.render()))
135
- }
136
- return uiServe(router, { root, browser: createClientBrowser()! })
137
- }
@@ -1,8 +0,0 @@
1
- /**
2
- * auth 独立入口——复制本目录即可运行:
3
- * cd examples/apps/auth && node server.ts → http://localhost:3301
4
- */
5
- import { createAuthApp, pathFromHash } from './app.tsx'
6
-
7
- const sub = createAuthApp(document.querySelector('#root') as HTMLElement)
8
- window.addEventListener('hashchange', () => sub.navigate(pathFromHash()))
@@ -1,37 +0,0 @@
1
- /**
2
- * auth 独立 server——内存用户 + 会话(契约层:生产换 postgres() + userSystem)
3
- * cd examples/apps/auth && node server.ts → http://localhost:3301
4
- */
5
- import { serve, Router, ui, createMemorySql } from 'weifuwu'
6
- import { resolve, dirname } from 'node:path'
7
- import { fileURLToPath } from 'node:url'
8
- import { registerAuthApi, ensureAuthTables } from './api.ts'
9
-
10
- const __dirname = dirname(fileURLToPath(import.meta.url))
11
- const sql = createMemorySql()
12
- await ensureAuthTables(sql)
13
-
14
- const app = new Router()
15
- app.use(ui())
16
- app.get('/app.js', (req, ctx) => ctx.ui.js(resolve(__dirname, 'main.tsx')))
17
- registerAuthApi(app, sql)
18
-
19
- app.get('/*', async (req, ctx) => ctx.ui.html`
20
- <!DOCTYPE html>
21
- <html lang="zh-CN">
22
- <head>
23
- <meta charset="UTF-8">
24
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
25
- <link rel="stylesheet" href="/components.css">
26
- <title>auth 应用模板</title>
27
- </head>
28
- <body>
29
- <div id="root"></div>
30
- <script src="/app.js"></script>
31
- </body>
32
- </html>
33
- `)
34
- app.get('/components.css', (req, ctx) => ctx.ui.css('weifuwu/components/style.css'))
35
-
36
- serve(app, { port: 3301 })
37
- console.log('auth 模板 → http://localhost:3301')
@@ -1,23 +0,0 @@
1
- # multi 应用模板(复制即用)
2
-
3
- ```bash
4
- # 1. 安装依赖(weifuwu 包——含 content/ 离线文档 + examples/ 模板源码)
5
- npm i weifuwu
6
-
7
- # 2. 复制本目录到你的项目(或直接在 examples/apps/multi 内开发)
8
- # 3. 启动
9
- node server.ts # → http://localhost:multi/3300 3301 3302 3303
10
- ```
11
-
12
- ## 改造指南
13
-
14
- 见 `node_modules/weifuwu/content/apps/multi.md`(目录结构 + 逐步改造步骤)。
15
-
16
- ## 结构
17
-
18
- | 文件 | 职责 |
19
- |------|------|
20
- | app.tsx | 前端:路由表 + 页面组件 + 状态 |
21
- | api.ts | 后端:API 注册函数(独立/嵌入共享) |
22
- | server.ts | 独立入口(MemorySql——生产换 postgres() 一行) |
23
- | main.tsx | 独立前端入口 |
@@ -1,83 +0,0 @@
1
- /**
2
- * multi 应用模板——应用编排(父应用嵌子应用:registerApp/hApp + 独立状态)
3
- *
4
- * 演示能力(design/vdom3-app-node.md):
5
- * - registerApp 注册子应用(工厂闭包持有实例状态——appId 复用不重跑)
6
- * - hApp(<App appId />)在父树渲染子应用——app:mount/update 边界事件
7
- * - 子应用独立状态:各自 let + render(),互不干扰
8
- * 纯前端编排——无后端(模板结构最简形态:app.tsx + server.ts + main.tsx)
9
- */
10
- import { UIRouter, uiServe, h, createClientBrowser } from 'weifuwu/vdom'
11
- import type { Component } from 'weifuwu/vdom'
12
- import { Button, Input, Tag, PageHeader, Space } from 'weifuwu/components'
13
-
14
- // ── 子应用 1:计数器(独立状态) ──
15
- const Counter = async (_init: any, ctx: any) => {
16
- let count = 0
17
- let step = 1
18
- return async (_p: any) => (
19
- <div class="wf-stack wf-gap-sm">
20
- <Space align="center"><Tag variant="primary">子应用 1</Tag><b>{count}</b></Space>
21
- <div class="wf-row wf-gap-xs">
22
- <Button size="sm" onClick={() => { count -= step; ctx.render() }}>-{step}</Button>
23
- <Button size="sm" variant="primary" onClick={() => { count += step; ctx.render() }}>+{step}</Button>
24
- <Button size="sm" variant="ghost" onClick={() => { step = step === 1 ? 5 : 1; ctx.render() }}>步长 {step}→{step === 1 ? 5 : 1}</Button>
25
- </div>
26
- </div>
27
- )
28
- }
29
-
30
- // ── 子应用 2:迷你任务(独立状态——与计数器互不干扰) ──
31
- const MiniTodo = async (_init: any, ctx: any) => {
32
- let items: string[] = ['子应用任务 A', '子应用任务 B']
33
- let input = ''
34
- return async (_p: any) => (
35
- <div class="wf-stack wf-gap-sm">
36
- <Tag variant="success">子应用 2</Tag>
37
- <div class="wf-row wf-gap-xs">
38
- <input class="wf-input" style="flex:1" value={input}
39
- onInput={(e: any) => { input = (e.target as HTMLInputElement).value; ctx.render() }}
40
- placeholder="新任务…" />
41
- <Button size="sm" onClick={() => { if (input.trim()) { items.push(input.trim()); input = ''; ctx.render() } }}>添加</Button>
42
- </div>
43
- <ul style="margin:0;padding-left:16px">
44
- {items.map((it, i) => <li key={it + i} class="wf-text-sm">{it}</li>)}
45
- </ul>
46
- </div>
47
- )
48
- }
49
-
50
- // ── 父应用:工作台(嵌入子应用 + 边界事件观测) ──
51
- export const WorkbenchPage: Component = async (_init: any, ctx: any) => {
52
- return async (_p: any) => (
53
- <div class="wf-stack wf-gap-md">
54
- <PageHeader title="应用编排工作台" sub="父应用嵌入子应用——registerApp 注册 · <App appId /> 渲染 · 独立状态" />
55
- <div class="wf-grid" style="--wf-cols:repeat(auto-fit,minmax(240px,1fr));--wf-gap:12px">
56
- <div class="wf-surface wf-border wf-rounded-md wf-p-md wf-stack wf-gap-sm">
57
- <b class="wf-text-sm">计数器(子应用 1)</b>
58
- {h(Counter, {})}
59
- </div>
60
- <div class="wf-surface wf-border wf-rounded-md wf-p-md wf-stack wf-gap-sm">
61
- <b class="wf-text-sm">迷你任务(子应用 2)</b>
62
- {h(MiniTodo, {})}
63
- </div>
64
- </div>
65
- </div>
66
- )
67
- }
68
-
69
- export const multiRoutes = [
70
- { path: '/', render: () => h(WorkbenchPage, {}) },
71
- ]
72
-
73
- export const pathFromHash = (): string => location.hash.replace(/^#/, '') || '/'
74
-
75
- export function createMultiApp(root: HTMLElement, _options?: { history?: boolean }): ReturnType<typeof uiServe> {
76
- // vdom 规范面:UIRouter + uiServe——布局共享精准路由
77
- const router = new UIRouter()
78
- for (const r of multiRoutes) {
79
- router.get(r.path, (req: Request, ctx: any) =>
80
- (ctx as { stream: (v: unknown) => Response }).stream(r.render()))
81
- }
82
- return uiServe(router, { root, browser: createClientBrowser()! })
83
- }
@@ -1,8 +0,0 @@
1
- /**
2
- * multi 独立入口——复制本目录即可运行:
3
- * cd examples/apps/multi && node server.ts → http://localhost:3303
4
- */
5
- import { createMultiApp, pathFromHash } from './app.tsx'
6
-
7
- const sub = createMultiApp(document.querySelector('#root') as HTMLElement)
8
- window.addEventListener('hashchange', () => sub.navigate(pathFromHash()))
@@ -1,33 +0,0 @@
1
- /**
2
- * multi 独立 server——纯前端编排(无后端 API)
3
- * cd examples/apps/multi && node server.ts → http://localhost:3303
4
- */
5
- import { serve, Router, ui } from 'weifuwu'
6
- import { resolve, dirname } from 'node:path'
7
- import { fileURLToPath } from 'node:url'
8
-
9
- const __dirname = dirname(fileURLToPath(import.meta.url))
10
-
11
- const app = new Router()
12
- app.use(ui())
13
- app.get('/app.js', (req, ctx) => ctx.ui.js(resolve(__dirname, 'main.tsx')))
14
-
15
- app.get('/*', async (req, ctx) => ctx.ui.html`
16
- <!DOCTYPE html>
17
- <html lang="zh-CN">
18
- <head>
19
- <meta charset="UTF-8">
20
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
21
- <link rel="stylesheet" href="/components.css">
22
- <title>multi 应用模板</title>
23
- </head>
24
- <body>
25
- <div id="root"></div>
26
- <script src="/app.js"></script>
27
- </body>
28
- </html>
29
- `)
30
- app.get('/components.css', (req, ctx) => ctx.ui.css('weifuwu/components/style.css'))
31
-
32
- serve(app, { port: 3303 })
33
- console.log('multi 模板 → http://localhost:3303')
@@ -1,23 +0,0 @@
1
- # todo 应用模板(复制即用)
2
-
3
- ```bash
4
- # 1. 安装依赖(weifuwu 包——含 content/ 离线文档 + examples/ 模板源码)
5
- npm i weifuwu
6
-
7
- # 2. 复制本目录到你的项目(或直接在 examples/apps/todo 内开发)
8
- # 3. 启动
9
- node server.ts # → http://localhost:todo/3300 3301 3302 3303
10
- ```
11
-
12
- ## 改造指南
13
-
14
- 见 `node_modules/weifuwu/content/apps/todo.md`(目录结构 + 逐步改造步骤)。
15
-
16
- ## 结构
17
-
18
- | 文件 | 职责 |
19
- |------|------|
20
- | app.tsx | 前端:路由表 + 页面组件 + 状态 |
21
- | api.ts | 后端:API 注册函数(独立/嵌入共享) |
22
- | server.ts | 独立入口(MemorySql——生产换 postgres() 一行) |
23
- | main.tsx | 独立前端入口 |
@@ -1,26 +0,0 @@
1
- /**
2
- * todo 后端 API——可复用注册函数(独立 server 与 showcase 嵌入共享同一实现)
3
- * 契约:registerTodoApi(app, sql)——sql 为 Sql 契约(MemorySql/postgres 均可)
4
- */
5
- import type { Router } from 'weifuwu'
6
-
7
- export function registerTodoApi(app: Router, sql: any): void {
8
- app.get('/api/todos', async () => {
9
- const rows = await sql`SELECT * FROM todos ORDER BY id DESC`
10
- return Response.json({ rows })
11
- })
12
- app.post('/api/todos', async (req: Request) => {
13
- const { name } = await req.json()
14
- const rows = await sql`INSERT INTO todos (name) VALUES (${String(name)}) RETURNING id, name, done`
15
- return Response.json({ row: rows[0] }, { status: 201 })
16
- })
17
- app.patch('/api/todos/:id', async (req: Request, ctx: any) => {
18
- const { done } = await req.json()
19
- await sql`UPDATE todos SET done = ${!!done} WHERE id = ${Number((ctx as any).params.id)}`
20
- return Response.json({ ok: true })
21
- })
22
- app.delete('/api/todos/:id', async (req: Request, ctx: any) => {
23
- await sql`DELETE FROM todos WHERE id = ${Number((ctx as any).params.id)}`
24
- return Response.json({ ok: true })
25
- })
26
- }
@@ -1,128 +0,0 @@
1
- /**
2
- * todo 应用模板——完整全栈(复制即用,随 npm 包发布 examples/apps/todo/)
3
- *
4
- * 演示(design/showcase-plan.md §P2):
5
- * - 多页路由(/ 列表 + /new 新建)+ createStore 跨页状态
6
- * - 后端:MemorySql 持久化(契约层——生产换 postgres() 一行)
7
- * - 嵌入方式:createRouter(routes, root, { history: false })——showcase 页面内嵌
8
- */
9
- import { UIRouter, uiServe, h, createStore, createClientBrowser } from 'weifuwu/vdom'
10
- import type { Component } from 'weifuwu/vdom'
11
- import { Button, Input, Checkbox, Tag, EmptyState, PageHeader, Form, Field, Alert } from 'weifuwu/components'
12
-
13
- export interface Todo {
14
- id: string
15
- name: string
16
- done: boolean
17
- }
18
-
19
- export const todoStore = createStore<{ todos: Todo[]; loading: boolean; error: string | null }>({
20
- todos: [],
21
- loading: true,
22
- error: null,
23
- })
24
-
25
- export async function loadTodos(): Promise<void> {
26
- todoStore.update((s) => { s.loading = true; s.error = null })
27
- try {
28
- const res = await fetch('/api/todos')
29
- const data = await res.json()
30
- todoStore.update((s) => { s.todos = data.rows; s.loading = false })
31
- } catch (e) {
32
- todoStore.update((s) => { s.error = (e as Error).message; s.loading = false })
33
- }
34
- }
35
-
36
- /** 列表页 */
37
- export const TodoList: Component = async (_init: any, ctx: any) => {
38
- void loadTodos()
39
- const state = ctx.ui.useExternal(todoStore)
40
- const toggle = async (t: Todo) => {
41
- await fetch(`/api/todos/${t.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ done: !t.done }) })
42
- await loadTodos()
43
- }
44
- const del = async (t: Todo) => {
45
- await fetch(`/api/todos/${t.id}`, { method: 'DELETE' })
46
- await loadTodos()
47
- }
48
- return async (_p: any) => (
49
- <div class="wf-stack wf-gap-md">
50
- <PageHeader title="任务列表" sub={`${state.todos.filter((t: Todo) => !t.done).length} 个未完成`}>
51
- <Button variant="primary" onClick={() => location.hash = '#/new'}>新建任务</Button>
52
- </PageHeader>
53
- {state.error && <Alert variant="error">{state.error}</Alert>}
54
- {state.loading && <div class="wf-text-secondary">加载中…</div>}
55
- {!state.loading && state.todos.length === 0 && <EmptyState text="暂无任务——点击「新建任务」开始" />}
56
- <div class="wf-stack wf-gap-xs">
57
- {state.todos.map((t: Todo) => (
58
- <div key={t.id} class="wf-surface wf-border wf-rounded-sm wf-p-sm wf-row wf-between">
59
- <div class="wf-row wf-gap-sm">
60
- <Checkbox checked={t.done} onChange={() => void toggle(t)} />
61
- <span class={t.done ? 'wf-text-tertiary' : ''} style={t.done ? 'text-decoration:line-through' : ''}>{t.name}</span>
62
- </div>
63
- <div class="wf-row wf-gap-xs">
64
- {t.done && <Tag variant="success">完成</Tag>}
65
- <Button size="sm" variant="ghost" onClick={() => void del(t)}>删除</Button>
66
- </div>
67
- </div>
68
- ))}
69
- </div>
70
- </div>
71
- )
72
- }
73
-
74
- /** 新建页 */
75
- export const TodoNew: Component = async (_init: any, ctx: any) => {
76
- let name = ''
77
- let error = ''
78
- let saving = false
79
- const submit = async () => {
80
- if (!name.trim()) { error = '任务名不能为空'; ctx.render(); return }
81
- saving = true; error = ''; ctx.render()
82
- try {
83
- await fetch('/api/todos', {
84
- method: 'POST', headers: { 'Content-Type': 'application/json' },
85
- body: JSON.stringify({ name: name.trim() }),
86
- })
87
- await loadTodos()
88
- location.hash = '#/'
89
- } catch (e) {
90
- error = (e as Error).message; saving = false; ctx.render()
91
- }
92
- }
93
- return async (_p: any) => (
94
- <div class="wf-stack wf-gap-md" style="max-width:480px">
95
- <PageHeader title="新建任务" sub="填写任务名称——提交后写入 MemorySql" />
96
- <Form onSubmit={() => void submit()}>
97
- <Field label="任务名称" error={error}>
98
- <Input value={name} onInput={(e: any) => { name = (e.target as HTMLInputElement).value; ctx.render() }}
99
- placeholder="例如:完成 showcase Phase 2" autoFocus />
100
- </Field>
101
- <div class="wf-row wf-gap-sm">
102
- <Button variant="primary" loading={saving} onClick={() => void submit()}>保存</Button>
103
- <Button variant="ghost" onClick={() => location.hash = '#/'}>取消</Button>
104
- </div>
105
- </Form>
106
- </div>
107
- )
108
- }
109
-
110
- // ── 路由表(layout 包裹 + 参数——模板可复制部分) ──
111
- export const todoRoutes = [
112
- { path: '/', render: () => h(TodoList, {}) },
113
- { path: '/new', render: () => h(TodoNew, {}) },
114
- ]
115
-
116
- /** hash → 内部路径(模板导航约定:location.hash = '#/new'——独立运行可深链/嵌入不污染宿主 pathname) */
117
- export const pathFromHash = (): string => location.hash.replace(/^#/, '') || '/'
118
-
119
- /** 应用入口:createTodoApp(root)——独立运行(main.tsx)与嵌入(showcase)复用 */
120
- export function createTodoApp(root: HTMLElement, _options?: { history?: boolean }): ReturnType<typeof uiServe> {
121
- // vdom 规范面:UIRouter + uiServe——布局共享精准路由
122
- const router = new UIRouter()
123
- for (const r of todoRoutes) {
124
- router.get(r.path, (req: Request, ctx: any) =>
125
- (ctx as { stream: (v: unknown) => Response }).stream(r.render()))
126
- }
127
- return uiServe(router, { root, browser: createClientBrowser()! })
128
- }
@@ -1,9 +0,0 @@
1
- /**
2
- * todo 应用模板独立入口——复制本目录即可运行:
3
- * cd examples/apps/todo && node server.ts → http://localhost:3300
4
- * hash 桥接:hashchange → 内部路由(独立运行可深链 #/new)
5
- */
6
- import { createTodoApp, pathFromHash } from './app.tsx'
7
-
8
- const sub = createTodoApp(document.querySelector('#root') as HTMLElement)
9
- window.addEventListener('hashchange', () => sub.navigate(pathFromHash()))
@@ -1,39 +0,0 @@
1
- /**
2
- * todo 后端——MemorySql CRUD(契约层:演示用内存实现——生产换 postgres() 一行)
3
- * 独立运行:node server.ts → http://localhost:3300
4
- */
5
- import { serve, Router, ui, createMemorySql } from 'weifuwu'
6
- import { resolve, dirname } from 'node:path'
7
- import { fileURLToPath } from 'node:url'
8
- import { registerTodoApi } from './api.ts'
9
-
10
- const __dirname = dirname(fileURLToPath(import.meta.url))
11
- const sql = createMemorySql()
12
-
13
- await sql.unsafe('CREATE TABLE IF NOT EXISTS todos (id serial PRIMARY KEY, name text, done boolean DEFAULT false)')
14
-
15
- const app = new Router()
16
- app.use(ui())
17
- app.get('/app.js', (req, ctx) => ctx.ui.js(resolve(__dirname, 'main.tsx')))
18
-
19
- registerTodoApi(app, sql)
20
-
21
- app.get('/*', async (req, ctx) => ctx.ui.html`
22
- <!DOCTYPE html>
23
- <html lang="zh-CN">
24
- <head>
25
- <meta charset="UTF-8">
26
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
27
- <link rel="stylesheet" href="/components.css">
28
- <title>todo 应用模板</title>
29
- </head>
30
- <body>
31
- <div id="root"></div>
32
- <script src="/app.js"></script>
33
- </body>
34
- </html>
35
- `)
36
- app.get('/components.css', (req, ctx) => ctx.ui.css('weifuwu/components/style.css'))
37
-
38
- serve(app, { port: 3300 })
39
- console.log('todo 模板 → http://localhost:3300')
@@ -1,9 +0,0 @@
1
- # hello-world 最小起步项目
2
-
3
- ```bash
4
- npm i weifuwu
5
- node server.ts # → http://localhost:3400
6
- ```
7
-
8
- 三文件:server.ts(后端)+ routes.tsx(路由/组件)+ client.ts(前端入口)。
9
- 改造:加页面 → routes.tsx 路由表加一行;加数据 → server.ts 加 /api 端点(组件用 ctx.data 读取)。
@@ -1,14 +0,0 @@
1
- /**
2
- * client.ts——客户端入口(纯 SPA 渲染:vdom3 事件流引擎)
3
- * createRouter(routes, root) —— 监听 location → 匹配 → 事件流渲染
4
- */
5
- import { UIRouter, uiServe } from 'weifuwu/vdom'
6
- import { routes } from './routes.tsx'
7
-
8
- // 路由声明 → UIRouter(get 挂载)→ uiServe 渲染(vdom 规范面)
9
- const router = new UIRouter()
10
- for (const r of routes) {
11
- router.get(r.path, (req: Request, ctx: any) =>
12
- (ctx as { stream: (v: unknown) => Response }).stream(r.render()))
13
- }
14
- uiServe(router, { root: document.querySelector('#root') as HTMLElement })
@@ -1,26 +0,0 @@
1
- /**
2
- * routes.tsx——路由声明(两阶段异步组件)
3
- *
4
- * 新手必读(概念见 content/guides/component-model.md):
5
- * - 外层 async 工厂 = mount(只一次):await 数据 / 初始化状态
6
- * - 内层 async renderFn = 每次渲染:读最新 props / 输出视图
7
- * - 状态 = 普通对象 let + 改后 ctx.render()(render-only)
8
- */
9
- import { h } from 'weifuwu/vdom'
10
-
11
- // 两阶段异步组件:工厂层 await 数据(ctx.data——缓存 + 并发合并)
12
- const Home = async (_init: any, ctx: any) => {
13
- const data = await ctx.data.get('/api/hello')
14
- let count = 0 // 组件状态(render-only)
15
- return async (_props: any) =>
16
- h('div', { class: 'wf-container wf-stack', style: '--wf-max:640px;--wf-gap:16px;padding:40px 16px;text-align:center' },
17
- h('h1', { class: 'wf-text-3xl wf-m-0' }, 'weifuwu hello-world'),
18
- h('p', { class: 'wf-text-secondary' }, `数据管道:${(data as any).msg}`),
19
- h('button', {
20
- class: 'wf-btn wf-btn--primary',
21
- onClick: () => { count++; ctx.render() }, // 改状态 → 显式 render()
22
- }, `点击 ${count} 次`),
23
- )
24
- }
25
-
26
- export const routes = [{ path: '/', render: () => h(Home, {}) }]