weifuwu 0.55.2 → 0.56.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.
@@ -1,4 +1,13 @@
1
1
  // src/client/vnode.ts
2
+ var ASYNC_MARK = "__wfAsyncComponent";
3
+ function asyncComponent(factory) {
4
+ const fn = factory;
5
+ fn[ASYNC_MARK] = true;
6
+ return fn;
7
+ }
8
+ function isAsyncComponent(type) {
9
+ return typeof type === "function" && type?.[ASYNC_MARK] === true;
10
+ }
2
11
  var Fragment = /* @__PURE__ */ Symbol("Fragment");
3
12
  var Portal = /* @__PURE__ */ Symbol("Portal");
4
13
  function jsx(type, props, key) {
@@ -56,10 +65,11 @@ function renderValue(v, ctx) {
56
65
  }
57
66
  if (vnode.type === Fragment) {
58
67
  const frag = document.createDocumentFragment();
59
- forEach(vnode.props?.children, (child) => {
68
+ const children = vnode.props?.children == null ? [] : Array.isArray(vnode.props.children) ? vnode.props.children : [vnode.props.children];
69
+ for (const child of children) {
60
70
  const node = renderValue(child, ctx);
61
71
  if (node != null) frag.appendChild(node);
62
- });
72
+ }
63
73
  return frag;
64
74
  }
65
75
  if (typeof vnode.type === "function") {
@@ -102,6 +112,60 @@ function renderValue(v, ctx) {
102
112
  if (typeof vnode.props?.ref === "function") vnode.props.ref(el);
103
113
  return el;
104
114
  }
115
+ var asyncFactoryCache = /* @__PURE__ */ new WeakMap();
116
+ function clearAsyncComponentCache() {
117
+ asyncFactoryCache = /* @__PURE__ */ new WeakMap();
118
+ }
119
+ function startAsyncFactory(Comp, ctx) {
120
+ const existing = asyncFactoryCache.get(Comp);
121
+ if (existing) return existing;
122
+ const entry = { promise: null };
123
+ entry.promise = Promise.resolve().then(() => Comp(ctx)).then((def) => {
124
+ if (typeof def !== "function") {
125
+ throw new Error(
126
+ `asyncComponent factory <${Comp.name || "anonymous"}> must return a Component (initProps, ctx) => (props) => VNode.`
127
+ );
128
+ }
129
+ entry.resolved = def;
130
+ return def;
131
+ });
132
+ asyncFactoryCache.set(Comp, entry);
133
+ return entry;
134
+ }
135
+ async function resolveAsyncFactory(Comp, ctx) {
136
+ return startAsyncFactory(Comp, ctx).promise;
137
+ }
138
+ function resolveAsyncFactorySync(Comp) {
139
+ return asyncFactoryCache.get(Comp)?.resolved;
140
+ }
141
+ function scheduleFullReRender(ctx) {
142
+ const ui = ctx.ui;
143
+ if (ui && typeof ui.render === "function") ui.render(["_wf_root"]);
144
+ }
145
+ function mountComponent(Comp, props, vnode, ctx) {
146
+ let def;
147
+ if (isAsyncComponent(Comp)) {
148
+ def = resolveAsyncFactorySync(Comp);
149
+ if (!def) {
150
+ void startAsyncFactory(Comp, ctx).promise.then(
151
+ () => scheduleFullReRender(ctx),
152
+ () => {
153
+ }
154
+ );
155
+ return null;
156
+ }
157
+ } else {
158
+ def = Comp;
159
+ }
160
+ let childVNode = def(props, ctx);
161
+ if (typeof childVNode !== "function") {
162
+ throw new Error(
163
+ `Component ${Comp.name || "anonymous"} must return a render function. Use (init_props, ctx) => (props) => VNode pattern.`
164
+ );
165
+ }
166
+ vnode._render = childVNode;
167
+ return childVNode(props);
168
+ }
105
169
  function renderComponent(Comp, props, vnode, ctx) {
106
170
  ;
107
171
  ctx.ui = ctx.ui ?? {};
@@ -116,14 +180,7 @@ function renderComponent(Comp, props, vnode, ctx) {
116
180
  vnode._ctxVersion = childCtx.ui._ctxVersion ?? 0;
117
181
  let childVNode;
118
182
  try {
119
- childVNode = Comp(props, childCtx);
120
- if (typeof childVNode !== "function") {
121
- throw new Error(
122
- `Component ${Comp.name || "anonymous"} must return a render function. Use (init_props, ctx) => (props) => VNode pattern.`
123
- );
124
- }
125
- vnode._render = childVNode;
126
- childVNode = childVNode(props);
183
+ childVNode = mountComponent(Comp, props, vnode, childCtx);
127
184
  } catch (e) {
128
185
  const errHandler = ctx.ui?._errorHandler;
129
186
  if (errHandler) {
@@ -193,14 +250,6 @@ function patchPortal(oldV, newV, ctx) {
193
250
  ensureKeys(oldChildren, newChildren);
194
251
  patchKeyedChildren(sub, oldChildren, newChildren, ctx);
195
252
  }
196
- function forEach(children, fn) {
197
- if (children == null) return;
198
- if (Array.isArray(children)) {
199
- children.forEach(fn);
200
- return;
201
- }
202
- fn(children);
203
- }
204
253
  function flattenChildren(children) {
205
254
  if (children == null) return [];
206
255
  if (!Array.isArray(children)) return [children];
@@ -300,11 +349,7 @@ function patchValue(parent, oldNode, oldInput, newInput, ctx) {
300
349
  if (typeof newV._render === "function") {
301
350
  childNew = newV._render(newV.props);
302
351
  } else {
303
- childNew = comp(newV.props, childCtx);
304
- if (typeof childNew === "function") {
305
- newV._render = childNew;
306
- childNew = childNew(newV.props);
307
- }
352
+ childNew = mountComponent(comp, newV.props, newV, childCtx);
308
353
  }
309
354
  } catch (e) {
310
355
  const errHandler = ctx.ui?._errorHandler;
@@ -395,8 +440,8 @@ function patchProps(el, oldProps, newProps) {
395
440
  if (newVal !== oldVal) el.innerHTML = String(newVal ?? "");
396
441
  } else if (newVal !== oldVal) {
397
442
  if (key === "class" || key === "className") {
398
- if (el instanceof SVGElement) el.setAttribute("class", String(newVal ?? ""));
399
- else el.className = String(newVal ?? "");
443
+ if (el instanceof SVGElement) el.setAttribute("class", classToString(newVal));
444
+ else el.className = classToString(newVal);
400
445
  } else if (key === "style" && typeof newVal === "object") {
401
446
  const st = el.style;
402
447
  for (const sk of Object.keys(newVal)) {
@@ -425,6 +470,14 @@ function patchProps(el, oldProps, newProps) {
425
470
  }
426
471
  }
427
472
  }
473
+ function classToString(v) {
474
+ if (typeof v === "string") return v;
475
+ if (Array.isArray(v)) return v.filter(Boolean).join(" ");
476
+ if (v && typeof v === "object") {
477
+ return Object.entries(v).filter(([, b]) => b).map(([k]) => k).join(" ");
478
+ }
479
+ return "";
480
+ }
428
481
  function getKey(input) {
429
482
  if (input == null || typeof input !== "object") return void 0;
430
483
  return input.key;
@@ -596,6 +649,15 @@ function cleanupPortalChildren(vnode) {
596
649
  function callRefCleanup(input) {
597
650
  if (input == null || typeof input !== "object") return;
598
651
  const vnode = input;
652
+ if (vnode._id) {
653
+ if (vnode._customId) idRegistry.delete(vnode._customId);
654
+ idRegistry.delete(vnode._id);
655
+ vnode._id = void 0;
656
+ vnode._customId = void 0;
657
+ vnode._render = void 0;
658
+ vnode._parentNode = void 0;
659
+ vnode._refNode = void 0;
660
+ }
599
661
  if (vnode._child != null) {
600
662
  if (Array.isArray(vnode._child)) {
601
663
  for (const child of vnode._child) {
@@ -619,6 +681,192 @@ function callRefCleanup(input) {
619
681
  vnode._remoteEl = void 0;
620
682
  }
621
683
  }
684
+ function cursorAdvance(c) {
685
+ c.node = c.node ? c.node.nextSibling : null;
686
+ }
687
+ function cursorInsert(c, n) {
688
+ if (c.node && c.node.parentNode) c.node.parentNode.insertBefore(n, c.node);
689
+ else c.parent.appendChild(n);
690
+ }
691
+ function cursorReplace(c, n) {
692
+ if (c.node && c.node.parentNode) {
693
+ c.node.parentNode.replaceChild(n, c.node);
694
+ cursorAdvance(c);
695
+ } else {
696
+ c.parent.appendChild(n);
697
+ }
698
+ }
699
+ async function renderValueHydrating(v, ctx, c) {
700
+ if (v == null || typeof v === "boolean") return null;
701
+ if (typeof v === "string" || typeof v === "number") {
702
+ const text = String(v);
703
+ if (c.node && c.node.nodeType === 3) {
704
+ if (c.node.textContent !== text) c.node.textContent = text;
705
+ cursorAdvance(c);
706
+ return c.node;
707
+ }
708
+ const tn = document.createTextNode(text);
709
+ cursorInsert(c, tn);
710
+ return tn;
711
+ }
712
+ if (Array.isArray(v)) {
713
+ let first = null;
714
+ for (const item of v) {
715
+ const n = await renderValueHydrating(item, ctx, c);
716
+ if (n != null && !first) first = n;
717
+ }
718
+ return first;
719
+ }
720
+ const vnode = v;
721
+ if (vnode.type === Portal || vnode.type === Fragment) {
722
+ const children = vnode.props?.children;
723
+ const arr = children == null ? [] : Array.isArray(children) ? children : [children];
724
+ let first = null;
725
+ for (const child of arr) {
726
+ const n = await renderValueHydrating(child, ctx, c);
727
+ if (n != null && !first) first = n;
728
+ }
729
+ return first;
730
+ }
731
+ if (typeof vnode.type === "function") {
732
+ return renderComponentHydrating(vnode, ctx, c);
733
+ }
734
+ const tag = vnode.type;
735
+ const props = vnode.props ?? {};
736
+ let el;
737
+ if (c.node && c.node.nodeType === 1 && c.node.tagName.toLowerCase() === tag.toLowerCase()) {
738
+ el = c.node;
739
+ cursorAdvance(c);
740
+ } else {
741
+ el = SVG_TAGS.has(tag) ? document.createElementNS(SVG_NS, tag) : document.createElement(tag);
742
+ cursorReplace(c, el);
743
+ }
744
+ vnode.el = el;
745
+ patchProps(el, null, props);
746
+ if ("innerHTML" in props) {
747
+ } else {
748
+ const childCursor = { parent: el, node: el.firstChild };
749
+ const children = flattenChildren(props.children);
750
+ for (const child of children) {
751
+ const n = await renderValueHydrating(child, ctx, childCursor);
752
+ if (n != null && n.parentNode !== el) el.appendChild(n);
753
+ if (child && typeof child === "object" && typeof child.type === "function") {
754
+ const childVNode = child;
755
+ if (!childVNode._parentNode) {
756
+ childVNode._parentNode = el;
757
+ childVNode._refNode = n;
758
+ }
759
+ }
760
+ }
761
+ while (childCursor.node) {
762
+ const n = childCursor.node;
763
+ childCursor.node = n.nextSibling;
764
+ n.parentNode?.removeChild(n);
765
+ }
766
+ }
767
+ if ("value" in props && el instanceof HTMLSelectElement) {
768
+ ;
769
+ el.value = String(props.value ?? "");
770
+ }
771
+ if (typeof props.ref === "function") props.ref(el);
772
+ return el;
773
+ }
774
+ async function renderComponentHydrating(vnode, ctx, c) {
775
+ ;
776
+ ctx.ui = ctx.ui ?? {};
777
+ if (!vnode._id) {
778
+ vnode._id = `_wf_${_idCounter++}`;
779
+ idRegistry.set(vnode._id, vnode);
780
+ }
781
+ const childCtx = Object.create(ctx);
782
+ childCtx.ui = Object.create(ctx.ui);
783
+ childCtx.ui._selfId = vnode._id;
784
+ childCtx.ui._selfVNode = vnode;
785
+ vnode._ctxVersion = childCtx.ui._ctxVersion ?? 0;
786
+ const Comp = vnode.type;
787
+ let childVNode;
788
+ try {
789
+ let def;
790
+ if (isAsyncComponent(Comp)) {
791
+ def = await resolveAsyncFactory(Comp, childCtx);
792
+ } else {
793
+ def = Comp;
794
+ }
795
+ const renderFn = def(vnode.props ?? {}, childCtx);
796
+ if (typeof renderFn !== "function") {
797
+ throw new Error(
798
+ `Component ${Comp.name || "anonymous"} must return a render function. Use (init_props, ctx) => (props) => VNode pattern.`
799
+ );
800
+ }
801
+ vnode._render = renderFn;
802
+ childVNode = renderFn(vnode.props ?? {});
803
+ } catch (e) {
804
+ const errHandler = ctx.ui?._errorHandler;
805
+ if (errHandler) {
806
+ errHandler(e);
807
+ childVNode = null;
808
+ } else {
809
+ console.error(
810
+ `[weifuwu] Component hydration error in <${Comp.name || "anonymous"}> (id: ${vnode._id ?? "?"})`,
811
+ e
812
+ );
813
+ childVNode = null;
814
+ }
815
+ }
816
+ if (childVNode == null) {
817
+ vnode._child = null;
818
+ return null;
819
+ }
820
+ vnode._child = childVNode;
821
+ const domNode = await renderValueHydrating(childVNode, childCtx, c);
822
+ if (!vnode._refNode) {
823
+ ;
824
+ vnode._refNode = domNode;
825
+ }
826
+ return domNode;
827
+ }
828
+ async function hydrateVNode(container, vnode, ctx) {
829
+ const cursor = { parent: container, node: container.firstChild };
830
+ await renderValueHydrating(vnode, ctx, cursor);
831
+ while (cursor.node) {
832
+ const n = cursor.node;
833
+ cursor.node = n.nextSibling;
834
+ n.parentNode?.removeChild(n);
835
+ }
836
+ }
837
+
838
+ // src/client/reactive.ts
839
+ function createReactiveState(dirty) {
840
+ const proxyCache = /* @__PURE__ */ new WeakMap();
841
+ const reactive = (target) => {
842
+ if (target === null || typeof target !== "object") return target;
843
+ if (proxyCache.has(target)) return proxyCache.get(target);
844
+ const proxy = new Proxy(target, {
845
+ set(target2, key, value) {
846
+ const old = Reflect.get(target2, key);
847
+ if (old === value) return true;
848
+ Reflect.set(target2, key, value);
849
+ dirty();
850
+ return true;
851
+ },
852
+ get(target2, key) {
853
+ const value = Reflect.get(target2, key);
854
+ if (typeof value === "object" && value !== null) return reactive(value);
855
+ return value;
856
+ },
857
+ deleteProperty(target2, key) {
858
+ if (Reflect.has(target2, key)) {
859
+ Reflect.deleteProperty(target2, key);
860
+ dirty();
861
+ }
862
+ return true;
863
+ }
864
+ });
865
+ proxyCache.set(target, proxy);
866
+ return proxy;
867
+ };
868
+ return reactive({});
869
+ }
622
870
 
623
871
  // src/client/app.ts
624
872
  function createApp() {
@@ -715,7 +963,7 @@ function createApp() {
715
963
  middlewares.push(mw);
716
964
  return app;
717
965
  },
718
- async mount(rootSelector, RootComponent) {
966
+ async mount(rootSelector, RootComponent, options) {
719
967
  rootComponent = RootComponent;
720
968
  for (const mw of middlewares) {
721
969
  ctx = await mw(ctx);
@@ -723,7 +971,36 @@ function createApp() {
723
971
  const el = typeof rootSelector === "string" ? document.querySelector(rootSelector) : rootSelector;
724
972
  if (!el) throw new Error(`mount target not found: ${rootSelector}`);
725
973
  container = el;
726
- container.innerHTML = "";
974
+ const hydrating = !!options?.hydrate;
975
+ if (!hydrating) container.innerHTML = "";
976
+ const dataCache = /* @__PURE__ */ new Map();
977
+ const hydratedData = globalThis.__DATA__;
978
+ if (hydratedData && typeof hydratedData === "object") {
979
+ for (const [k, v] of Object.entries(hydratedData)) {
980
+ dataCache.set(k, { value: v });
981
+ }
982
+ }
983
+ ;
984
+ ctx.data = {
985
+ async get(key, fetcher) {
986
+ const entry = dataCache.get(key);
987
+ if (entry && "value" in entry) return entry.value;
988
+ if (entry?.promise) return entry.promise;
989
+ if (!fetcher) return void 0;
990
+ const promise = Promise.resolve().then(() => fetcher()).then((val) => {
991
+ dataCache.set(key, { value: val });
992
+ return val;
993
+ });
994
+ dataCache.set(key, { promise });
995
+ return promise;
996
+ },
997
+ set(key, value) {
998
+ dataCache.set(key, { value });
999
+ },
1000
+ has(key) {
1001
+ return dataCache.has(key);
1002
+ }
1003
+ };
727
1004
  ctx.ui = {
728
1005
  _selfId: "_wf_root",
729
1006
  // ── ctx 版本号(供三态 skip 判定) ──
@@ -843,16 +1120,16 @@ function createApp() {
843
1120
  * pos 是稳定对象,render 闭包直接读取 top/left;
844
1121
  * 滚动/resize 时自动重算并定向刷新;打开弹层瞬间调用 pos.refresh()。
845
1122
  */
846
- usePopupPosition: function(options) {
1123
+ usePopupPosition: function(options2) {
847
1124
  const selfId = getSelfId(this);
848
1125
  const pos = { top: 0, left: 0, refresh: () => {
849
1126
  } };
850
1127
  if (!selfId) return pos;
851
1128
  const tracker = {
852
1129
  pos,
853
- getEl: options.el,
854
- isOpen: options.isOpen,
855
- compute: options.compute
1130
+ getEl: options2.el,
1131
+ isOpen: options2.isOpen,
1132
+ compute: options2.compute
856
1133
  };
857
1134
  _popupTrackers.set(selfId, tracker);
858
1135
  if (!_popupListenersReady) {
@@ -887,10 +1164,14 @@ function createApp() {
887
1164
  oldVNode = wrapComponent(RootComponent, ctx);
888
1165
  oldVNode._id = "_wf_root";
889
1166
  oldVNode._parentNode = container;
890
- oldVNode._refNode = null;
1167
+ oldVNode._refNode = hydrating ? container.firstChild : null;
891
1168
  idRegistry.set("_wf_root", oldVNode);
892
- const node = render(oldVNode, ctx);
893
- if (node instanceof Node) container.appendChild(node);
1169
+ if (hydrating) {
1170
+ await hydrateVNode(container, oldVNode, ctx);
1171
+ } else {
1172
+ const node = render(oldVNode, ctx);
1173
+ if (node instanceof Node) container.appendChild(node);
1174
+ }
894
1175
  oldVNode._refNode = container.firstChild;
895
1176
  _rendering = false;
896
1177
  flushDirtyBatch();
@@ -912,52 +1193,8 @@ function createApp() {
912
1193
  function wrapComponent(Comp, _ctx) {
913
1194
  return { type: Comp, props: {}, key: void 0 };
914
1195
  }
915
- function createReactiveState(dirty) {
916
- const proxyCache = /* @__PURE__ */ new WeakMap();
917
- const reactive = (target) => {
918
- if (target === null || typeof target !== "object") return target;
919
- if (proxyCache.has(target)) return proxyCache.get(target);
920
- const proxy = new Proxy(target, {
921
- set(target2, key, value) {
922
- const old = Reflect.get(target2, key);
923
- if (old === value) return true;
924
- Reflect.set(target2, key, value);
925
- dirty();
926
- return true;
927
- },
928
- get(target2, key) {
929
- const value = Reflect.get(target2, key);
930
- if (typeof value === "object" && value !== null) return reactive(value);
931
- return value;
932
- },
933
- deleteProperty(target2, key) {
934
- if (Reflect.has(target2, key)) {
935
- Reflect.deleteProperty(target2, key);
936
- dirty();
937
- }
938
- return true;
939
- }
940
- });
941
- proxyCache.set(target, proxy);
942
- return proxy;
943
- };
944
- return reactive({});
945
- }
946
1196
 
947
- // src/client/router.ts
948
- function flattenRoutes(routes, basePath = "", chain = []) {
949
- const result = [];
950
- for (const route of routes) {
951
- const fullPath = joinPaths(basePath, route.path);
952
- const { re, keys } = compilePath(fullPath);
953
- const fullChain = [...chain, route];
954
- result.push({ re, keys, def: route, chain: fullChain });
955
- if (route.children) {
956
- result.push(...flattenRoutes(route.children, fullPath, fullChain));
957
- }
958
- }
959
- return result;
960
- }
1197
+ // src/client/route-match.ts
961
1198
  function joinPaths(a, b) {
962
1199
  if (!b || b === "/") return a || "/";
963
1200
  const left = a.endsWith("/") ? a.slice(0, -1) : a;
@@ -972,6 +1209,19 @@ function compilePath(path) {
972
1209
  }).replace(/\*/g, ".*");
973
1210
  return { re: new RegExp(`^${reStr}$`), keys };
974
1211
  }
1212
+ function flattenRoutes(routes, basePath = "", chain = []) {
1213
+ const result = [];
1214
+ for (const route of routes) {
1215
+ const fullPath = joinPaths(basePath, route.path);
1216
+ const { re, keys } = compilePath(fullPath);
1217
+ const fullChain = [...chain, route];
1218
+ result.push({ re, keys, def: route, chain: fullChain });
1219
+ if (route.children) {
1220
+ result.push(...flattenRoutes(route.children, fullPath, fullChain));
1221
+ }
1222
+ }
1223
+ return result;
1224
+ }
975
1225
  function matchRoute(path, routes) {
976
1226
  let best = null;
977
1227
  for (const fr of routes) {
@@ -981,6 +1231,18 @@ function matchRoute(path, routes) {
981
1231
  }
982
1232
  return best;
983
1233
  }
1234
+ function extractParams(path, match) {
1235
+ const params = {};
1236
+ const m = path.match(match.re);
1237
+ if (m) {
1238
+ for (let i = 0; i < match.keys.length; i++) {
1239
+ params[match.keys[i]] = decodeURIComponent(m[i + 1]);
1240
+ }
1241
+ }
1242
+ return params;
1243
+ }
1244
+
1245
+ // src/client/router.ts
984
1246
  function router(opts) {
985
1247
  const flatRoutes = flattenRoutes(opts.routes);
986
1248
  const mode = opts.mode || "history";
@@ -994,23 +1256,22 @@ function router(opts) {
994
1256
  function resolve(path) {
995
1257
  const match = matchRoute(path, flatRoutes);
996
1258
  if (match) {
997
- const params = {};
998
- const m = path.match(match.re);
999
- if (m) {
1000
- for (let i = 0; i < match.keys.length; i++) {
1001
- params[match.keys[i]] = decodeURIComponent(m[i + 1]);
1002
- }
1003
- }
1004
1259
  const last = match.chain[match.chain.length - 1];
1005
1260
  return {
1006
1261
  path: match.def.path,
1007
- params,
1262
+ params: extractParams(path, match),
1008
1263
  query: Object.fromEntries(new URLSearchParams(window.location.search)),
1009
1264
  chain: match.chain,
1010
1265
  title: last?.title ?? ""
1011
1266
  };
1012
1267
  }
1013
- return { path, params: {}, query: {}, chain: [], title: "" };
1268
+ return {
1269
+ path,
1270
+ params: {},
1271
+ query: Object.fromEntries(new URLSearchParams(window.location.search)),
1272
+ chain: opts.notFound ? [{ component: opts.notFound, title: "Not Found" }] : [],
1273
+ title: ""
1274
+ };
1014
1275
  }
1015
1276
  return (ctx) => {
1016
1277
  const resolved = resolve(getPath());
@@ -1018,6 +1279,7 @@ function router(opts) {
1018
1279
  if (resolved.title) document.title = resolved.title;
1019
1280
  if (!ctx.app) ctx.app = {};
1020
1281
  ctx.app.navigate = (path) => {
1282
+ clearAsyncComponentCache();
1021
1283
  if (mode === "hash") {
1022
1284
  window.location.hash = "#" + path;
1023
1285
  const resolved2 = resolve(path);
@@ -1032,6 +1294,7 @@ function router(opts) {
1032
1294
  ctx.ui?.render();
1033
1295
  };
1034
1296
  const onPop = () => {
1297
+ clearAsyncComponentCache();
1035
1298
  const resolved2 = resolve(getPath());
1036
1299
  ctx.route = resolved2;
1037
1300
  if (ctx.route?.title) document.title = ctx.route.title;
@@ -1580,14 +1843,17 @@ export {
1580
1843
  Portal,
1581
1844
  RouteView,
1582
1845
  api,
1846
+ asyncComponent,
1583
1847
  auth,
1584
1848
  computeFixedPos,
1849
+ computeFixedPosRect,
1585
1850
  createApp,
1586
1851
  createPortal,
1587
1852
  enUS,
1588
1853
  extendCtx,
1589
1854
  h,
1590
1855
  i18n,
1856
+ isAsyncComponent,
1591
1857
  jsx,
1592
1858
  jsxDEV,
1593
1859
  jsxs,
@@ -0,0 +1,6 @@
1
+ /**
2
+ * 创建响应式状态容器:深度 Proxy,任意层级属性赋值自动触发 dirty。
3
+ *
4
+ * 纯 JS(无 DOM),客户端(ctx.ui.$())与服务端(SSR ctx shim)共用。
5
+ */
6
+ export declare function createReactiveState(dirty: () => void): Record<string, any>;
@@ -14,6 +14,12 @@ import type { VNode } from './vnode.ts';
14
14
  import type { WfuiContext } from './types.ts';
15
15
  export declare const idRegistry: Map<string, VNode>;
16
16
  export declare function render(input: any, ctx: WfuiContext): Node | null;
17
+ /**
18
+ * 清空 async 工厂缓存。
19
+ * 页面上下文切换时调用(路由导航/登录登出)——工厂内 ctx.data.get 的 key 依赖 ctx(如 route.params),
20
+ * 上下文变化后旧缓存定义的数据已失效,需要让工厂以新 ctx 重新执行。
21
+ */
22
+ export declare function clearAsyncComponentCache(): void;
17
23
  /** 首次渲染 Portal:创建远程容器、渲染子节点(不返回占位节点) */
18
24
  export declare function renderPortal(vnode: VNode, ctx: WfuiContext): void;
19
25
  /** 更新 Portal:复用远程容器,patch 子节点(不操作父 DOM) */
@@ -22,3 +28,8 @@ export declare function patchValue(parent: Node, oldNode: Node | null, oldInput:
22
28
  /** 通知 ref 清理 + Portal 子容器清理 */
23
29
  export declare function callRefCleanup(input: any): void;
24
30
  export declare function mountVNode(container: Element, vnode: VNode, ctx: WfuiContext): void;
31
+ /**
32
+ * Hydration 挂载入口:收养 container 内现有服务端 HTML。
33
+ * 渲染完收尾:删除服务端有、客户端没有的残留 DOM。
34
+ */
35
+ export declare function hydrateVNode(container: Element, vnode: VNode, ctx: WfuiContext): Promise<void>;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * 路由匹配纯函数 — 客户端 router 与服务端 uiSsr 共用(无 DOM 依赖)
3
+ *
4
+ * 支持路径参数 :id、通配符 *、嵌套路由(children → chain)。
5
+ * 与后端 Router(Trie)独立——本模块服务于"前端路由表驱动的页面"。
6
+ */
7
+ import type { RouteDef } from './types.ts';
8
+ export interface FlattenedRoute {
9
+ re: RegExp;
10
+ keys: string[];
11
+ def: RouteDef;
12
+ chain: RouteDef[];
13
+ }
14
+ export declare function joinPaths(a: string, b: string): string;
15
+ export declare function compilePath(path: string): {
16
+ re: RegExp;
17
+ keys: string[];
18
+ };
19
+ export declare function flattenRoutes(routes: RouteDef[], basePath?: string, chain?: RouteDef[]): FlattenedRoute[];
20
+ export declare function matchRoute(path: string, routes: FlattenedRoute[]): FlattenedRoute | null;
21
+ /** 从匹配结果提取路径参数 */
22
+ export declare function extractParams(path: string, match: FlattenedRoute): Record<string, string>;
@@ -70,6 +70,23 @@ export interface WfuiContext {
70
70
  delete: <T = any>(url: string, opts?: any) => Promise<T>;
71
71
  [key: string]: any;
72
72
  };
73
+ /** 数据管道(由 createApp 注入):ctx.data.get(key, fetcher) */
74
+ data?: {
75
+ /**
76
+ * 获取数据:
77
+ * - SSR:服务端真 fetch,结果序列化进 __DATA__
78
+ * - hydration:从 __DATA__ 缓存同步命中(工厂 await 微任务即 resolve)
79
+ * - SPA:未命中则触发 fetcher,同 key 并发请求合并
80
+ *
81
+ * key 约定即 URL(`/api/posts/1`),天然唯一。
82
+ */
83
+ get: <T = any>(key: string, fetcher?: () => Promise<T>) => Promise<T>;
84
+ /** 向缓存写入值(如 hydration 种子) */
85
+ set: (key: string, value: unknown) => void;
86
+ /** 是否存在缓存(未触发 fetch) */
87
+ has: (key: string) => boolean;
88
+ [key: string]: any;
89
+ };
73
90
  /** 认证状态 */
74
91
  auth?: {
75
92
  token: string | null;