@fulgurjs/federation 5.4.0 → 5.4.2

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 (60) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.en.md +6 -4
  3. package/README.md +15 -10
  4. package/dist/bridge-app-react.d.cts +1 -1
  5. package/dist/bridge-app-react.d.ts +1 -1
  6. package/dist/bridge-app-react.js +1 -1
  7. package/dist/bridge-app-vue.d.cts +1 -1
  8. package/dist/bridge-app-vue.d.ts +1 -1
  9. package/dist/bridge-app-vue.js +1 -1
  10. package/dist/{bridge-core-BOWvVUxO.d.ts → bridge-core-ejlY9ddN.d.ts} +3 -1
  11. package/dist/bridge-core.d.cts +1 -1
  12. package/dist/bridge-core.d.ts +1 -1
  13. package/dist/bridge-core.js +2 -2
  14. package/dist/bridge-errors.cjs +5 -4
  15. package/dist/bridge-errors.d.cts +1 -1
  16. package/dist/bridge-errors.d.ts +1 -1
  17. package/dist/bridge-errors.js +1 -1
  18. package/dist/{bridge-host-react-CMVd-ym0.d.ts → bridge-host-react-Bua1TCVh.d.ts} +1 -1
  19. package/dist/bridge-host-react.cjs +54 -45
  20. package/dist/bridge-host-react.d.cts +1 -1
  21. package/dist/bridge-host-react.d.ts +1 -1
  22. package/dist/bridge-host-react.js +3 -3
  23. package/dist/{bridge-host-vue-BuCGlLdS.d.ts → bridge-host-vue-Dd1kkwDb.d.ts} +1 -1
  24. package/dist/bridge-host-vue.cjs +54 -45
  25. package/dist/bridge-host-vue.js +3 -3
  26. package/dist/bridge-react.d.ts +4 -4
  27. package/dist/{bridge-router-core-BxBx-reP.d.ts → bridge-router-core-BRZQYDPm.d.cts} +3 -1
  28. package/dist/{bridge-router-core-BxBx-reP.d.cts → bridge-router-core-BRZQYDPm.d.ts} +3 -1
  29. package/dist/bridge-router-react.d.ts +25 -42
  30. package/dist/bridge-router-react.js +213 -36
  31. package/dist/bridge-router-vue.d.ts +18 -37
  32. package/dist/bridge-router-vue.js +188 -44
  33. package/dist/bridge-vue.d.ts +2 -2
  34. package/dist/bridge.d.ts +5 -5
  35. package/dist/{chunk-GF6KOH67.js → chunk-43UX3UZE.js} +1 -1
  36. package/dist/{chunk-WOTR2FUQ.js → chunk-7CQOVLSI.js} +50 -42
  37. package/dist/{chunk-JQF7KYZF.js → chunk-HZGQOW35.js} +5 -4
  38. package/dist/{chunk-OXEAGRPY.js → chunk-KT3YARK5.js} +1 -1
  39. package/dist/{chunk-PRUYHYX2.js → chunk-RXXBTPAY.js} +1 -1
  40. package/dist/{chunk-BTJGYDHT.js → chunk-UFWZPE36.js} +55 -55
  41. package/dist/index.cjs +42 -10
  42. package/dist/index.js +42 -10
  43. package/dist/runtime.js +9 -9
  44. package/examples/bridge/react-host/package-lock.json +13 -5
  45. package/examples/bridge/react-host/package.json +1 -1
  46. package/examples/bridge/react-remote/package-lock.json +13 -5
  47. package/examples/bridge/react-remote/package.json +1 -1
  48. package/examples/bridge/vue-host/package-lock.json +13 -5
  49. package/examples/bridge/vue-host/package.json +1 -1
  50. package/examples/bridge/vue-remote/package-lock.json +13 -5
  51. package/examples/bridge/vue-remote/package.json +1 -1
  52. package/examples/react/host/package-lock.json +13 -4
  53. package/examples/react/host/package.json +1 -1
  54. package/examples/react/remote/package-lock.json +13 -4
  55. package/examples/react/remote/package.json +1 -1
  56. package/examples/vue/host/package-lock.json +13 -4
  57. package/examples/vue/host/package.json +1 -1
  58. package/examples/vue/remote/package-lock.json +13 -4
  59. package/examples/vue/remote/package.json +1 -1
  60. package/package.json +1 -1
@@ -169,13 +169,14 @@ function routingNavigationError(spec, reason) {
169
169
  { spec }
170
170
  );
171
171
  }
172
- function routingSyncError(spec, reason, chain) {
172
+ function routingSyncError(spec, reason, chain, cause) {
173
173
  return new FgError(
174
174
  BridgeErrorCodes.ROUTING_SYNC_FAILED,
175
- `\u73B0\u8C61\uFF1A\u6865\u63A5\u5E94\u7528 "${spec}" \u7684 URL \u540C\u6B65\u5728\u91CD\u5B9A\u5411\u94FE\u4E0A\u8D85\u51FA\u4E0A\u9650\uFF08${chain.length} \u6B21\uFF09\u3002
175
+ `\u73B0\u8C61\uFF1A\u6865\u63A5\u5E94\u7528 "${spec}" \u7684 URL \u540C\u6B65\u5931\u8D25\u3002
176
176
  \u539F\u56E0\uFF1A${reason}
177
- \u4FEE\u6CD5\uFF1A\u68C0\u67E5\u5B50\u5E94\u7528\u8DEF\u7531\u7684\u91CD\u5B9A\u5411\u89C4\u5219\u662F\u5426\u4E92\u76F8\u6210\u73AF\uFF1B\u540C\u6B65\u91CD\u5B9A\u5411\u4F7F\u7528 replace \u4E14\u4E0D\u5F97\u5BF9\u540C\u4E00\u4F4D\u7F6E\u91CD\u590D\u53D1\u8D77\u3002\u76EE\u6807\u94FE\uFF1A${chain.join(" \u2192 ")}\u3002`,
178
- { spec, chain: [...chain] }
177
+ \u4FEE\u6CD5\uFF1A\u68C0\u67E5\u8DEF\u7531\u5B88\u536B\u3001\u52A0\u8F7D\u5668\u4E0E\u5BFC\u822A\u5F02\u5E38\uFF1B\u91CD\u5B9A\u5411\u94FE\u4E0D\u5E94\u6210\u73AF\u3002\u76EE\u6807\u94FE\uFF1A${chain.join(" \u2192 ")}\u3002`,
178
+ { spec, chain: [...chain] },
179
+ { cause }
179
180
  );
180
181
  }
181
182
 
@@ -288,10 +289,11 @@ var MAX_GO_DELTA = 50;
288
289
  function normalizeBasePath(basePath, spec) {
289
290
  const bad = (reason) => routingConfigError(spec, reason);
290
291
  if (typeof basePath !== "string" || basePath === "") throw bad("basePath \u4E0D\u80FD\u4E3A\u7A7A\u3002");
291
- if (basePath === "/") throw bad('basePath \u4E0D\u80FD\u662F\u6839 "/"\u2014\u2014\u6839\u8DEF\u5F84\u5C5E\u4E8E\u5BBF\u4E3B\u81EA\u8EAB\u9875\u9762\uFF0C\u8BF7\u4E3A\u540C\u6B65\u5B9E\u4F8B\u5206\u914D\u72EC\u7ACB\u524D\u7F00\u3002');
292
+ if (/^\/+$/u.test(basePath)) throw bad('basePath \u4E0D\u80FD\u662F\u6839 "/"\u2014\u2014\u6839\u8DEF\u5F84\u5C5E\u4E8E\u5BBF\u4E3B\u81EA\u8EAB\u9875\u9762\uFF0C\u8BF7\u4E3A\u540C\u6B65\u5B9E\u4F8B\u5206\u914D\u72EC\u7ACB\u524D\u7F00\u3002');
292
293
  if (!basePath.startsWith("/")) throw bad(`basePath \u5FC5\u987B\u4EE5 "/" \u5F00\u5934\uFF1A\u5F53\u524D\u4E3A "${basePath}"\u3002`);
293
294
  if (/[?#]/.test(basePath) || basePath.includes("://")) throw bad(`basePath \u4E0D\u80FD\u5305\u542B query/hash/\u534F\u8BAE\uFF1A\u5F53\u524D\u4E3A "${basePath}"\u3002`);
294
295
  if (basePath.includes("*") || basePath.includes(":")) throw bad(`basePath \u4E0D\u63A5\u53D7\u901A\u914D\u7B26\u6216\u53C2\u6570\u6A21\u677F\uFF1A\u5F53\u524D\u4E3A "${basePath}"\u3002\u8BF7\u4F7F\u7528\u9759\u6001\u7EDD\u5BF9\u8DEF\u5F84\u3002`);
296
+ if (/\\|\/\/|%(?:2e|2f|5c)/i.test(basePath) || basePath.split("/").some((part) => part === "." || part === "..")) throw bad("basePath \u4E0D\u80FD\u5305\u542B\u7A7A\u8DEF\u5F84\u6BB5\u3001\u8F6C\u4E49\u5206\u9694\u7B26\u6216\u76F8\u5BF9\u8DEF\u5F84\u6BB5\u3002");
295
297
  return basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
296
298
  }
297
299
  function matchesBasePath(hostPathname, basePath) {
@@ -315,7 +317,8 @@ function sameLocation(a, b) {
315
317
  return a.pathname === b.pathname && a.search === b.search && a.hash === b.hash;
316
318
  }
317
319
  function assertChildTargetInScope(target, basePath, spec) {
318
- if (target.pathname.includes("\0") || target.pathname.split("/").includes("..")) {
320
+ if (!target || typeof target.pathname !== "string" || typeof target.search !== "string" || typeof target.hash !== "string" || target.search !== "" && !target.search.startsWith("?") || target.hash !== "" && !target.hash.startsWith("#")) throw routingNavigationError(spec, "\u5BFC\u822A\u76EE\u6807\u5FC5\u987B\u63D0\u4F9B pathname\u3001search\u3001hash \u4E09\u6BB5\uFF0Csearch/hash \u5E94\u4E3A\u7A7A\u6216\u4EE5 ?/# \u5F00\u5934\u3002");
321
+ if (/[\x00-\x20\\?#]|%(?:2e|2f|5c)/i.test(target.pathname) || target.pathname.includes("//") || target.pathname.split("/").some((part) => part === "." || part === "..")) {
319
322
  throw routingNavigationError(spec, `\u5BFC\u822A\u76EE\u6807\u8BD5\u56FE\u9003\u9038\u81EA\u8EAB\u524D\u7F00\uFF1Apathname "${target.pathname}"\u3002\u8DE8\u524D\u7F00\u5BFC\u822A\u8BF7\u901A\u8FC7\u5BBF\u4E3B\u83DC\u5355\u7B49\u5BBF\u4E3B\u80FD\u529B\u5B8C\u6210\u3002`);
320
323
  }
321
324
  const host = toHostLocation(target, basePath, spec);
@@ -357,6 +360,8 @@ var RoutingChannel = class {
357
360
  lastConfirmed;
358
361
  seq = 0;
359
362
  inflight = null;
363
+ queue;
364
+ generation = 0;
360
365
  disposed = false;
361
366
  active = true;
362
367
  releasePrefix;
@@ -371,7 +376,8 @@ var RoutingChannel = class {
371
376
  this.spec = spec;
372
377
  this.basePath = normalizeBasePath(basePath, spec);
373
378
  this.host = host;
374
- this.current = toChildLocation(host.getLocation(), this.basePath);
379
+ const initial = host.getLocation();
380
+ this.current = matchesBasePath(initial.pathname, this.basePath) ? toChildLocation(initial, this.basePath) : { pathname: "/", search: "", hash: "" };
375
381
  this.lastConfirmed = { ...this.current };
376
382
  this.releasePrefix = acquireRoutingPrefix(this.basePath, spec);
377
383
  this.unsubHost = host.subscribe((loc) => this.handleHostLocation(loc));
@@ -389,6 +395,7 @@ var RoutingChannel = class {
389
395
  handleHostLocation(hostLoc) {
390
396
  if (this.disposed) return;
391
397
  if (!matchesBasePath(hostLoc.pathname, this.basePath)) {
398
+ this.cancelPending();
392
399
  this.onEvent?.({ type: "prefix-miss", pathname: hostLoc.pathname });
393
400
  return;
394
401
  }
@@ -398,23 +405,21 @@ var RoutingChannel = class {
398
405
  }
399
406
  const child = toChildLocation(hostLoc, this.basePath);
400
407
  if (this.inflight && sameLocation(child, this.inflight.target)) {
401
- const { seq, resolve } = this.inflight;
402
- this.inflight = null;
408
+ const { seq } = this.inflight;
403
409
  this.lastConfirmed = child;
404
410
  this.emit(child);
405
411
  this.onEvent?.({ type: "request-confirmed", seq });
406
- resolve({ status: "committed", location: { ...child } });
407
412
  return;
408
413
  }
409
414
  if (this.inflight) {
410
- const { seq, resolve } = this.inflight;
411
- this.inflight = null;
415
+ const seq = this.inflight.seq;
412
416
  this.lastConfirmed = child;
417
+ this.cancelPending();
413
418
  this.emit(child);
414
419
  this.onEvent?.({ type: "request-superseded", seq });
415
- resolve({ status: "cancelled", location: { ...child } });
416
420
  return;
417
421
  }
422
+ if (!sameLocation(child, this.lastConfirmed)) this.generation++;
418
423
  this.lastConfirmed = child;
419
424
  this.emit(child);
420
425
  this.onEvent?.({ type: "host-broadcast" });
@@ -431,8 +436,31 @@ var RoutingChannel = class {
431
436
  this.listeners.delete(listener);
432
437
  };
433
438
  }
434
- async navigate(target, action) {
435
- if (this.disposed || !this.active) {
439
+ cancelPending() {
440
+ this.generation++;
441
+ const pending = this.inflight;
442
+ this.inflight = null;
443
+ if (pending) {
444
+ pending.abort.abort();
445
+ pending.resolve({ status: "cancelled", location: { ...this.lastConfirmed } });
446
+ }
447
+ }
448
+ navigate(target, action) {
449
+ const generation = this.generation;
450
+ const snapshot = { ...target };
451
+ const run = () => generation !== this.generation ? Promise.resolve({ status: "cancelled", location: { ...this.lastConfirmed } }) : this.performNavigation(snapshot, action);
452
+ const task = this.queue ? this.queue.then(run) : run();
453
+ const tail = task.then(() => {
454
+ }, () => {
455
+ });
456
+ this.queue = tail;
457
+ void tail.then(() => {
458
+ if (this.queue === tail) this.queue = void 0;
459
+ });
460
+ return task;
461
+ }
462
+ async performNavigation(target, action) {
463
+ if (this.disposed || !this.active || !matchesBasePath(this.host.getLocation().pathname, this.basePath)) {
436
464
  this.onEvent?.({ type: this.disposed ? "rejected-disposed" : "rejected-inactive" });
437
465
  return { status: "cancelled", location: { ...this.lastConfirmed } };
438
466
  }
@@ -455,28 +483,12 @@ var RoutingChannel = class {
455
483
  this.redirectChain = [];
456
484
  throw routingSyncError(this.spec, "\u540C\u4E00\u901A\u9053\u7684\u8FDE\u7EED replace \u91CD\u5B9A\u5411\u8D85\u8FC7\u4E0A\u9650\uFF08\u7591\u4F3C\u5B50\u5E94\u7528\u8DEF\u7531\u4E92\u76F8\u6210\u73AF\uFF09\u3002", chain);
457
485
  }
458
- if (this.inflight) {
459
- const prev = this.inflight;
460
- await new Promise((resolve) => {
461
- const timer = setInterval(() => {
462
- if (this.inflight !== prev) {
463
- clearInterval(timer);
464
- resolve();
465
- }
466
- }, 0);
467
- });
468
- if (this.disposed || !this.active) {
469
- return { status: "cancelled", location: { ...this.lastConfirmed } };
470
- }
471
- if (sameLocation(target, this.lastConfirmed)) {
472
- return { status: "committed", location: { ...this.lastConfirmed } };
473
- }
474
- }
475
486
  const seq = ++this.seq;
476
487
  this.onEvent?.({ type: "request-start", seq, target: { ...target }, action });
477
- return new Promise((resolve) => {
478
- this.inflight = { seq, target: { ...target }, resolve };
479
- this.host.navigate(toHostLocation(target, this.basePath, this.spec), action).then((result) => {
488
+ return new Promise((resolve, reject) => {
489
+ const abort = new AbortController();
490
+ this.inflight = { seq, target: { ...target }, resolve, abort };
491
+ Promise.resolve().then(() => abort.signal.aborted ? { status: "cancelled", location: this.host.getLocation() } : this.host.navigate(toHostLocation(target, this.basePath, this.spec), action, { signal: abort.signal })).then((result) => {
480
492
  if (!this.inflight || this.inflight.seq !== seq) return;
481
493
  if (this.disposed || !this.active) {
482
494
  this.inflight = null;
@@ -484,7 +496,7 @@ var RoutingChannel = class {
484
496
  return;
485
497
  }
486
498
  this.inflight = null;
487
- if (result.status === "committed") {
499
+ if (result.status === "committed" && matchesBasePath(result.location.pathname, this.basePath)) {
488
500
  const child = toChildLocation(result.location, this.basePath);
489
501
  this.lastConfirmed = child;
490
502
  this.emit(child);
@@ -493,21 +505,21 @@ var RoutingChannel = class {
493
505
  }
494
506
  this.onEvent?.({ type: "host-verdict", seq, status: result.status });
495
507
  resolve(
496
- result.status === "committed" ? { status: "committed", location: { ...this.lastConfirmed } } : { status: "cancelled", location: { ...this.lastConfirmed } }
508
+ result.status === "committed" && matchesBasePath(result.location.pathname, this.basePath) ? { status: "committed", location: { ...this.lastConfirmed } } : { status: "cancelled", location: { ...this.lastConfirmed } }
497
509
  );
498
- }).catch(() => {
510
+ }).catch((cause) => {
499
511
  if (this.inflight?.seq === seq) {
500
512
  this.inflight = null;
501
513
  this.onEvent?.({ type: "host-error", seq });
502
514
  this.emit({ ...this.lastConfirmed });
503
- resolve({ status: "cancelled", location: { ...this.lastConfirmed } });
515
+ reject(routingSyncError(this.spec, `\u5BBF\u4E3B\u5BFC\u822A\u6267\u884C\u5931\u8D25\uFF1A${cause instanceof Error ? cause.message : String(cause)}`, [], cause));
504
516
  }
505
517
  });
506
518
  });
507
519
  }
508
520
  /** 委托宿主历史(有限整数);不创建第二条独立历史 */
509
521
  go(delta) {
510
- if (this.disposed || !this.active) return;
522
+ if (this.disposed || !this.active || !matchesBasePath(this.host.getLocation().pathname, this.basePath)) return;
511
523
  if (typeof delta !== "number" || !Number.isInteger(delta) || Math.abs(delta) > MAX_GO_DELTA) {
512
524
  throw routingNavigationError(this.spec, `go \u53C2\u6570\u5FC5\u987B\u662F\u6709\u9650\u6574\u6570\uFF08|delta| \u2264 ${MAX_GO_DELTA}\uFF09\uFF1A\u5F53\u524D\u4E3A ${String(delta)}\u3002`);
513
525
  }
@@ -516,6 +528,7 @@ var RoutingChannel = class {
516
528
  /** KeepAlive deactivate:暂停广播与写入;通道与订阅保留(activate 后 resync) */
517
529
  setActive(active) {
518
530
  if (this.disposed) return;
531
+ if (!active) this.cancelPending();
519
532
  this.active = active;
520
533
  this.onEvent?.({ type: active ? "resumed" : "paused" });
521
534
  if (active) this.handleHostLocation(this.host.getLocation());
@@ -527,11 +540,7 @@ var RoutingChannel = class {
527
540
  if (this.redirectTimer) clearTimeout(this.redirectTimer);
528
541
  this.unsubHost();
529
542
  this.releasePrefix();
530
- if (this.inflight) {
531
- const { resolve } = this.inflight;
532
- this.inflight = null;
533
- resolve({ status: "cancelled", location: { ...this.lastConfirmed } });
534
- }
543
+ this.cancelPending();
535
544
  this.listeners.clear();
536
545
  this.onEvent?.({ type: "disposed" });
537
546
  }
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  RoutingChannel,
3
3
  assertBridgeRoutingProtocol
4
- } from "./chunk-WOTR2FUQ.js";
4
+ } from "./chunk-7CQOVLSI.js";
5
5
  import {
6
6
  acquireBridgeSession,
7
7
  assertBridgeContract,
@@ -10,10 +10,10 @@ import {
10
10
  releaseBridgeSession,
11
11
  resolveBridgeContext,
12
12
  withBridgeTimeout
13
- } from "./chunk-PRUYHYX2.js";
13
+ } from "./chunk-RXXBTPAY.js";
14
14
  import {
15
15
  bridgeLifecycleError
16
- } from "./chunk-JQF7KYZF.js";
16
+ } from "./chunk-HZGQOW35.js";
17
17
  import {
18
18
  provideAppContext
19
19
  } from "./chunk-TSYLDY6H.js";
@@ -1,8 +1,8 @@
1
1
  import * as react from 'react';
2
- import { B as BridgeHostRouting } from './bridge-core-BOWvVUxO.js';
3
- export { A as AppContext, a as BridgeApp } from './bridge-core-BOWvVUxO.js';
4
- import { R as ReactBridgeAppOptions } from './bridge-host-react-CMVd-ym0.js';
5
- export { B as BridgeErrorFallback } from './bridge-host-react-CMVd-ym0.js';
2
+ import { B as BridgeHostRouting } from './bridge-core-ejlY9ddN.js';
3
+ export { A as AppContext, a as BridgeApp } from './bridge-core-ejlY9ddN.js';
4
+ import { R as ReactBridgeAppOptions } from './bridge-host-react-Bua1TCVh.js';
5
+ export { B as BridgeErrorFallback } from './bridge-host-react-Bua1TCVh.js';
6
6
 
7
7
  /** React 宿主桥接工厂:返回 `ComponentType<{ appProps: P; sessionKey?: string | null }>` */
8
8
  declare const createReactBridgeApp: <P = Record<string, unknown>>(spec: string, options?: ReactBridgeAppOptions) => react.ComponentType<{
@@ -15,7 +15,9 @@ type BridgeNavigationResult = {
15
15
  interface BridgeHostNavigation {
16
16
  getLocation(): BridgeLocation;
17
17
  subscribe(listener: (location: BridgeLocation) => void): () => void;
18
- navigate(target: BridgeLocation, action: 'push' | 'replace'): Promise<BridgeNavigationResult>;
18
+ navigate(target: BridgeLocation, action: 'push' | 'replace', options?: {
19
+ signal?: AbortSignal;
20
+ }): Promise<BridgeNavigationResult>;
19
21
  go(delta: number): void;
20
22
  }
21
23
  /** 子应用路由通道(桥接宿主按挂载代次创建,随 mount 第三参数交给子应用) */
@@ -15,7 +15,9 @@ type BridgeNavigationResult = {
15
15
  interface BridgeHostNavigation {
16
16
  getLocation(): BridgeLocation;
17
17
  subscribe(listener: (location: BridgeLocation) => void): () => void;
18
- navigate(target: BridgeLocation, action: 'push' | 'replace'): Promise<BridgeNavigationResult>;
18
+ navigate(target: BridgeLocation, action: 'push' | 'replace', options?: {
19
+ signal?: AbortSignal;
20
+ }): Promise<BridgeNavigationResult>;
19
21
  go(delta: number): void;
20
22
  }
21
23
  /** 子应用路由通道(桥接宿主按挂载代次创建,随 mount 第三参数交给子应用) */
@@ -18,7 +18,9 @@ type BridgeNavigationResult = {
18
18
  interface BridgeHostNavigation {
19
19
  getLocation(): BridgeLocation;
20
20
  subscribe(listener: (location: BridgeLocation) => void): () => void;
21
- navigate(target: BridgeLocation, action: 'push' | 'replace'): Promise<BridgeNavigationResult>;
21
+ navigate(target: BridgeLocation, action: 'push' | 'replace', options?: {
22
+ signal?: AbortSignal;
23
+ }): Promise<BridgeNavigationResult>;
22
24
  go(delta: number): void;
23
25
  }
24
26
  /** 子应用路由通道(桥接宿主按挂载代次创建,随 mount 第三参数交给子应用) */
@@ -28,69 +30,50 @@ interface BridgeChildRoute {
28
30
  navigate(target: BridgeLocation, action: 'push' | 'replace'): Promise<BridgeNavigationResult>;
29
31
  go(delta: number): void;
30
32
  }
33
+ /** 宿主组件 routing prop(独立控制通道,不进 appProps/Context) */
34
+ interface BridgeHostRouting {
35
+ /** 宿主路由视角的静态绝对路径,如 "/approval";同页各同步实例不得相同或重叠 */
36
+ basePath: string;
37
+ navigation: BridgeHostNavigation;
38
+ }
31
39
 
32
- /**
33
- * React Router 路由适配(/bridge/router/react 按需入口;URL 同步任务书 §3.1/§3.3)。
34
- *
35
- * 实际支持模式(原型实证,任务书 §1 结论 2):
36
- * - 宿主:data router(createBrowserRouter / createHashRouter + RouterProvider)。
37
- * router.navigate 被 blocker 拦截时静默返回 void(dist 源码核实)——取消用
38
- * 「与 useBlocker 同一取消策略预判」:canNavigate(target) 返回 false 即 cancelled,
39
- * URL/历史零副作用;菜单/POP 等其他来源仍由树内 useBlocker 真实拦截。
40
- * - 子应用:createMemoryRouter(RR 6.11+)受控路由——Link/useNavigate 全兼容,
41
- * 通道广播与本地导航双向桥接,取消时乐观回滚。
42
- * - declarative 模式(BrowserRouter)无 blocker/提交语义,不支持 URL 同步(文档声明)。
43
- */
40
+ /** React Router data router 的宿主端口与子应用 memory router 接线(按需入口)。 */
44
41
 
45
- /** 宿主取消策略(与树内 useBlocker 共用同一谓词,单一事实源) */
46
42
  type ReactBridgeCancelPolicy = (next: BridgeLocation) => boolean;
47
- /** data router 的最小结构面(避免耦合 RR 内部类型版本) */
48
43
  interface ReactDataRouterLike {
49
44
  state: {
50
45
  location: {
51
46
  pathname: string;
52
47
  search: string;
53
48
  hash: string;
49
+ key?: string;
50
+ };
51
+ navigation?: {
52
+ state: string;
53
+ location?: BridgeLocation;
54
54
  };
55
+ blockers?: ReadonlyMap<string, {
56
+ state: string;
57
+ reset?: () => void;
58
+ }>;
55
59
  };
56
60
  subscribe(cb: () => void): () => void;
57
61
  navigate(to: string | number, opts?: {
58
62
  replace?: boolean;
59
63
  }): void | Promise<void>;
60
64
  }
61
- /**
62
- * 宿主侧:把 RR data router 包装为宿主导航端口。
63
- *
64
- * ```tsx
65
- * const router = createBrowserRouter(routes, { basename })
66
- * const navigation = createReactBridgeNavigation(router, {
67
- * basename, // = createBrowserRouter 的 basename(含它;RR location.pathname 不剥 basename)
68
- * canNavigate: (next) => !next.pathname.startsWith('/approval/secret'), // 与 useBlocker 同一谓词
69
- * })
70
- * ```
71
- */
65
+ /** canNavigate 是可选预判;最终结果同时核对真实 blocker 与已提交位置。 */
72
66
  declare function createReactBridgeNavigation(router: ReactDataRouterLike, options?: {
73
67
  canNavigate?: ReactBridgeCancelPolicy;
74
68
  basename?: string;
75
69
  }): BridgeHostNavigation;
76
70
  interface ReactBridgeRouterConnection {
77
- /** RouterProvider 元素:子应用在自己的 root 内渲染 */
78
71
  element: ReactElement;
79
- /** 清理本接线(通道订阅与本地监听;通道本身由宿主生命周期销毁) */
80
72
  dispose(): void;
81
73
  }
82
- /**
83
- * 子应用侧:把子应用自己的 routes 接到通道,返回 RouterProvider 元素。
84
- *
85
- * ```tsx
86
- * export default defineBridgeApp((props, ctx) => {
87
- * const conn = createReactBridgeRouter(ctx.routing, routes)
88
- * const root = createRoot(el)
89
- * root.render(conn.element)
90
- * return { mount: () => {}, unmount: () => { conn.dispose(); root.unmount() } }
91
- * }, { routing: true })
92
- * ```
93
- */
94
- declare function createReactBridgeRouter(routing: BridgeChildRoute, routes: RouteObject[]): ReactBridgeRouterConnection;
74
+ /** 返回 RouterProvider 元素,直接作为 defineBridgeApp 工厂的 ReactElement 返回值。 */
75
+ declare function createReactBridgeRouter(routing: BridgeChildRoute, routes: RouteObject[], options?: {
76
+ signal?: AbortSignal;
77
+ }): ReactBridgeRouterConnection;
95
78
 
96
- export { type ReactBridgeCancelPolicy, type ReactBridgeRouterConnection, type ReactDataRouterLike, createReactBridgeNavigation, createReactBridgeRouter };
79
+ export { type BridgeHostRouting, type ReactBridgeCancelPolicy, type ReactBridgeRouterConnection, type ReactDataRouterLike, createReactBridgeNavigation, createReactBridgeRouter };