@fulgurjs/federation 5.3.2 → 5.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.3.3
4
+
5
+ - **修复:loadShare × pinLoadedShare 并发窗口的单例双实例风险(F9)**——`loadShare` 设置 `entry.loaded` 后等待 `entry.get()` 期间,CJS 垫片的 `pinLoadedShare` 可同步写入本地实例;getter 完成后原实现直接覆盖 `entry.value`,同步 CJS 消费者与异步消费者各持一份实例。现在实行条目实例 first-wins(协商结果让位于先写入的 pin/fallback 实例,loadShare 返回条目当前 value)、条目级 in-flight get(并发 loadShare 共享同一次加载,getter 恰好调用一次)、getter 拒绝后回滚 `entry.loaded`(修复前死条目残留「已加载」标记会永久抢占单例的已加载优先)——失败窗口内已有 pin 时收敛到 pin 实例。8 个确定性回归(可控 Promise 门)覆盖挂起中 pin / pin 先行 / 并发去重 / 拒绝重试与状态恢复 / resolveShare 组合 / 多版本边界;单元 558 → 566。`runtime.js` gzip 9204B ≤ 9216B。
6
+ - **结论修正(Vite 8 与 dev 冷启动)**:Vite 8(rolldown)dev / 生产构建 / 生产页面挂载自本版起均有完整验收证据(双向桥接 11 步交互矩阵 176/176,5.3.2 时期的「生产页面 BLOCKED(上游)」已在独立验收轮定位为插件层四个缺陷并全部修复)。dev 冷启动依赖预构建窗口(DEV-010)如实声明为使用边界:首轮 30~60s 内首开可能出现瞬时 504/请求挂起并由 vite reload 自愈,自动化验收请先按文档预热。
7
+ - 插件分组的 Vite 8 方案(原生 codeSplitting 保护组 + 共享提供/协商/垫片/运行时分层)与共享选择共用、CJS 垫片同步形态、宿主提供闭包不改写等修复随本版一并正式发布(前两轮候选 rc.1/rc.2 的完整门禁见验收报告 §9/§10)。
8
+
3
9
  ## 5.3.2
4
10
 
5
11
  - **修正:回滚 5.3.1 中「宿主入口 init 注入改为 `import "virtual:fulgurjs-init"`」的实验性变更**——该变更本意是修复 vite 8 生产运行死锁(未成功,vite 8 运行期仍单列 rolldown 上游 BLOCKED),但在大型宿主(jeecg 系,vite 6)生产实测存在破坏地图渲染的风险面。5.3.2 恢复 5.3.0 的「init 代码内联入口模块」形态(原始注释「rollup 会摇树剥离独立模块的顶层调用」仍然成立),其余 5.3.1 修复(同步 CJS-NS 垫片 / BN09 容器封锁 / BN08 getContext 诊断 / publisher 守卫)全部保留。单元 540/540、e2e dev 43 + prod 28 复验通过。
package/README.en.md CHANGED
@@ -48,11 +48,13 @@
48
48
  pnpm add -D @fulgurjs/federation
49
49
  ```
50
50
 
51
- - Vite ≥ 5.1 (tested through 8.x; Vite 8 uses the rolldown dep-optimizer path automatically)
51
+ - Vite ≥ 5.1 (tested through 8.x)
52
52
  - Node ≥ 18
53
53
  - Vue ≥ 3.2.0 and/or React `>=18.0.0 <20` — all three are **optional peers**; install only the framework you use
54
54
  - Chrome 108+ (native top-level await)
55
55
 
56
+ > Vite 8 (rolldown): as of 5.3.3, dev, production builds and production page mounting all pass full acceptance (bidirectional bridge 11-step interaction matrix, see the acceptance report). The first page open on a cold dev cache still falls inside the dependency pre-bundling window (DEV-010; it self-recovers via reload). Warm up before acceptance runs or manual judgement, as documented.
57
+
56
58
  `@fulgurjs/federation/runtime` and `@fulgurjs/federation/react` are **ESM-only** browser entries (no `require()`). The build-time main entry supports both ESM and CJS.
57
59
 
58
60
  ## 4. Project shape: two files per app
package/README.md CHANGED
@@ -51,6 +51,8 @@ pnpm add -D @fulgurjs/federation
51
51
 
52
52
  要求:Vite ≥ 5.1(实测至 8.x)、Node ≥ 18、Vue 3 和/或 React 18–19(均为可选 peer——按所用框架安装)、浏览器 Chrome 108+(TLA 原生支持)。
53
53
 
54
+ > Vite 8(rolldown):5.3.3 起 dev 与生产构建、生产页面挂载均已通过完整验收(双向桥接 11 步交互矩阵,见验收报告)。dev 冷启动首开仍受依赖预构建窗口影响(DEV-010,首轮打开自动恢复),验收与人工判断请按文档先预热。
55
+
54
56
  ## 快速开始(React 应用)
55
57
 
56
58
  React 宿主与远程使用同一套「每应用两份文件」的配置形态,唯一区别是浏览器导入点:`@fulgurjs/federation/react`(同时提供通用运行时 API 与 React 适配 API,纯 React 项目不需要安装 Vue)。
@@ -8,7 +8,7 @@ import {
8
8
  releaseBridgeSession,
9
9
  resolveBridgeContext,
10
10
  withBridgeTimeout
11
- } from "./chunk-TOT3P7AK.js";
11
+ } from "./chunk-WD2ON7SN.js";
12
12
 
13
13
  // src/bridge-host-vue.ts
14
14
  import { defineComponent, h, onBeforeUnmount, onMounted, ref, shallowRef, toRaw, watch } from "vue";
@@ -252,6 +252,14 @@ function createRuntime() {
252
252
  const remotes = /* @__PURE__ */ new Map();
253
253
  const plugins = [];
254
254
  const reportedSingletonSkews = /* @__PURE__ */ new Set();
255
+ const resolvedShareSnapshots = /* @__PURE__ */ new Map();
256
+ const shareRequestKey = (name, opts) => JSON.stringify([
257
+ opts.shareScope || "default",
258
+ opts.shareKey || name,
259
+ opts.requiredVersion ?? null,
260
+ !!opts.singleton,
261
+ !!opts.strictVersion
262
+ ]);
255
263
  const loadedModules = /* @__PURE__ */ new Map();
256
264
  const remoteManifests = /* @__PURE__ */ new Map();
257
265
  const stylesheetLoads = /* @__PURE__ */ new WeakMap();
@@ -472,14 +480,16 @@ function createRuntime() {
472
480
  }
473
481
  function registerPlugins2(list) {
474
482
  plugins.push(...list);
483
+ resolvedShareSnapshots.clear();
475
484
  applyPlugins();
476
485
  }
477
- async function loadShare2(name, opts = {}) {
486
+ function selectShareEntry(name, opts, readyOnly = false) {
478
487
  const scopeName = opts.shareScope || "default";
479
488
  const shareKey = opts.shareKey || name;
480
489
  const scope = getScope(scopeName);
481
490
  const byName = scope[shareKey] || {};
482
- const versions = Object.keys(byName);
491
+ const versions = Object.keys(byName).filter((v) => !readyOnly || opts.singleton || byName[v].value !== void 0);
492
+ if (readyOnly && versions.length === 0) return void 0;
483
493
  const req = opts.requiredVersion;
484
494
  const satisfying = versions.filter(
485
495
  (v) => req === void 0 || req === false || satisfies(v, req)
@@ -512,49 +522,121 @@ function createRuntime() {
512
522
  pick = [...satisfying].sort(compareVersions).pop();
513
523
  if (!pick) {
514
524
  if (opts.strictVersion) {
515
- const err2 = new FgError(
525
+ const err = new FgError(
516
526
  ErrorCodes.SHARE_STRICT_VERSION,
517
527
  `\u73B0\u8C61\uFF1A\u5171\u4EAB\u4F5C\u7528\u57DF "${scopeName}" \u4E2D\u627E\u4E0D\u5230\u7B26\u5408\u8981\u6C42\u7684 "${shareKey}"\u3002
518
528
  \u539F\u56E0\uFF1A\u8981\u6C42\u7248\u672C ${req}\uFF0C\u5F53\u524D\u53EF\u7528\u7248\u672C\u4E3A ${versions.join("\u3001") || "\u65E0"}\u3002
519
529
  \u4FEE\u6CD5\uFF1A\u7EDF\u4E00\u5BBF\u4E3B\u4E0E\u8FDC\u7A0B\u7684\u4F9D\u8D56\u7248\u672C\uFF0C\u6216\u4FEE\u6B63 shared.requiredVersion\u3002`
520
530
  );
521
- emitError({ remote: shareKey, error: err2 });
522
- throw err2;
531
+ emitError({ remote: shareKey, error: err });
532
+ throw err;
523
533
  }
524
- if (opts.fallback) return opts.fallback();
525
- const err = new FgError(
526
- ErrorCodes.SHARE_NOT_AVAILABLE,
527
- `\u73B0\u8C61\uFF1A\u65E0\u6CD5\u52A0\u8F7D\u5171\u4EAB\u4F9D\u8D56 "${shareKey}"\u3002
528
- \u539F\u56E0\uFF1A\u4F5C\u7528\u57DF "${scopeName}" \u4E2D\u6CA1\u6709\u7B26\u5408 ${req ?? "\u4EFB\u610F\u7248\u672C"} \u8981\u6C42\u7684\u7248\u672C\uFF0C\u4E14\u672A\u914D\u7F6E\u672C\u5730\u526F\u672C\u3002
529
- \u4FEE\u6CD5\uFF1A\u5728\u5BBF\u4E3B\u6216\u8FDC\u7A0B\u6CE8\u518C\u8BE5\u4F9D\u8D56\uFF0C\u6216\u4E3A shared \u914D\u7F6E\u53EF\u7528\u7684\u672C\u5730\u526F\u672C\u3002`,
530
- { shareKey, requiredVersion: req }
531
- );
532
- emitError({ remote: shareKey, error: err });
533
- throw err;
534
+ return void 0;
535
+ }
536
+ }
537
+ return byName[pick];
538
+ }
539
+ function registerFallbackInstance(name, opts, instance) {
540
+ const localVersion = opts.localVersion;
541
+ if (!localVersion || instance === void 0) return;
542
+ const scopeName = opts.shareScope || "default";
543
+ const key = opts.shareKey || name;
544
+ const scope = getScope(scopeName);
545
+ const byName = Object.hasOwn(scope, key) ? scope[key] : void 0;
546
+ if (byName && Object.keys(byName).length > 0) {
547
+ try {
548
+ const pick = selectShareEntry(name, opts);
549
+ if (pick && pick.value === void 0 && pick.version === localVersion) {
550
+ pick.value = instance;
551
+ pick.loaded = true;
552
+ }
553
+ } catch {
534
554
  }
555
+ return;
556
+ }
557
+ registerShare2(scopeName, key, localVersion, () => Promise.resolve(instance), {
558
+ from: "fulgurjs:local-fallback",
559
+ loaded: true
560
+ });
561
+ const fresh = scope[key]?.[localVersion];
562
+ if (fresh) fresh.value = instance;
563
+ }
564
+ async function loadShare2(name, opts = {}) {
565
+ const scopeName = opts.shareScope || "default";
566
+ const shareKey = opts.shareKey || name;
567
+ const byName = getScope(scopeName)[shareKey] || {};
568
+ let entry = selectShareEntry(name, opts);
569
+ if (!entry) {
570
+ if (opts.fallback) {
571
+ const instance2 = await opts.fallback();
572
+ registerFallbackInstance(name, opts, instance2);
573
+ return instance2;
574
+ }
575
+ const err = new FgError(
576
+ ErrorCodes.SHARE_NOT_AVAILABLE,
577
+ `\u73B0\u8C61\uFF1A\u65E0\u6CD5\u52A0\u8F7D\u5171\u4EAB\u4F9D\u8D56 "${shareKey}"\u3002
578
+ \u539F\u56E0\uFF1A\u4F5C\u7528\u57DF "${scopeName}" \u4E2D\u6CA1\u6709\u7B26\u5408 ${opts.requiredVersion ?? "\u4EFB\u610F\u7248\u672C"} \u8981\u6C42\u7684\u7248\u672C\uFF0C\u4E14\u672A\u914D\u7F6E\u672C\u5730\u526F\u672C\u3002
579
+ \u4FEE\u6CD5\uFF1A\u5728\u5BBF\u4E3B\u6216\u8FDC\u7A0B\u6CE8\u518C\u8BE5\u4F9D\u8D56\uFF0C\u6216\u4E3A shared \u914D\u7F6E\u53EF\u7528\u7684\u672C\u5730\u526F\u672C\u3002`,
580
+ { shareKey, requiredVersion: opts.requiredVersion }
581
+ );
582
+ emitError({ remote: shareKey, error: err });
583
+ throw err;
535
584
  }
536
- let entry = byName[pick];
537
585
  if (hooks.resolveShare) {
538
586
  const picked = await hooks.resolveShare({
539
587
  shareKey,
540
588
  shareScope: scopeName,
541
- requiredVersion: req,
589
+ requiredVersion: opts.requiredVersion,
542
590
  picked: entry,
543
591
  available: Object.values(byName)
544
592
  });
545
593
  if (picked) entry = picked;
546
594
  }
595
+ if (entry.value !== void 0) {
596
+ resolvedShareSnapshots.set(shareRequestKey(name, opts), entry);
597
+ return entry.value;
598
+ }
547
599
  entry.loaded = true;
548
- const instance = await entry.get();
549
- entry.value = instance;
550
- return instance;
600
+ if (!entry.pendingGet) {
601
+ try {
602
+ entry.pendingGet = entry.get();
603
+ } catch (e) {
604
+ entry.pendingGet = Promise.reject(e);
605
+ }
606
+ }
607
+ const pending = entry.pendingGet;
608
+ let instance;
609
+ try {
610
+ instance = await pending;
611
+ } catch (e) {
612
+ if (entry.value !== void 0) {
613
+ resolvedShareSnapshots.set(shareRequestKey(name, opts), entry);
614
+ return entry.value;
615
+ }
616
+ entry.loaded = false;
617
+ throw e;
618
+ } finally {
619
+ if (entry.pendingGet === pending) entry.pendingGet = void 0;
620
+ }
621
+ if (entry.value === void 0) entry.value = instance;
622
+ resolvedShareSnapshots.set(shareRequestKey(name, opts), entry);
623
+ return entry.value;
551
624
  }
552
625
  function getLoadedShare2(name, opts = {}) {
553
- const shareKey = opts.shareKey || name;
554
- const byName = getScope(opts.shareScope || "default")[shareKey] || {};
555
- const ready = Object.keys(byName).filter((v) => byName[v].value !== void 0);
556
- if (ready.length === 0) return void 0;
557
- return byName[ready.sort(compareVersions).pop()].value;
626
+ if (hooks.resolveShare) return resolvedShareSnapshots.get(shareRequestKey(name, opts))?.value;
627
+ return selectShareEntry(name, opts, true)?.value;
628
+ }
629
+ function pinLoadedShare2(name, opts, localVersion, instance) {
630
+ if (instance === void 0) return;
631
+ let entry;
632
+ try {
633
+ entry = selectShareEntry(name, opts);
634
+ } catch {
635
+ return;
636
+ }
637
+ if (!entry || entry.value !== void 0 || entry.version !== localVersion) return;
638
+ entry.value = instance;
639
+ entry.loaded = true;
558
640
  }
559
641
  function sanitizeUrl(u) {
560
642
  try {
@@ -986,6 +1068,7 @@ ${hints.join("\n")}`,
986
1068
  registerPlugins: registerPlugins2,
987
1069
  loadShare: loadShare2,
988
1070
  getLoadedShare: getLoadedShare2,
1071
+ pinLoadedShare: pinLoadedShare2,
989
1072
  loadRemote: loadRemote2,
990
1073
  getContainer: getContainer2,
991
1074
  preloadRemote: preloadRemote2,
@@ -1007,6 +1090,7 @@ var registerRemote = runtime.registerRemote;
1007
1090
  var registerPlugins = runtime.registerPlugins;
1008
1091
  var loadShare = runtime.loadShare;
1009
1092
  var getLoadedShare = runtime.getLoadedShare;
1093
+ var pinLoadedShare = runtime.pinLoadedShare;
1010
1094
  var loadRemote = runtime.loadRemote;
1011
1095
  var getContainer = runtime.getContainer;
1012
1096
  var preloadRemote = runtime.preloadRemote;
@@ -8,7 +8,7 @@ import {
8
8
  releaseBridgeSession,
9
9
  resolveBridgeContext,
10
10
  withBridgeTimeout
11
- } from "./chunk-TOT3P7AK.js";
11
+ } from "./chunk-WD2ON7SN.js";
12
12
 
13
13
  // src/bridge-host-react.ts
14
14
  import {