@fulgurjs/federation 5.3.1 → 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 +10 -0
- package/README.en.md +3 -1
- package/README.md +2 -0
- package/dist/{chunk-MMXKQN2H.js → chunk-GWJKABE7.js} +1 -1
- package/dist/{chunk-TOT3P7AK.js → chunk-WD2ON7SN.js} +109 -25
- package/dist/{chunk-X4F2BHNM.js → chunk-ZTRJLCOF.js} +1 -1
- package/dist/index.cjs +138 -16
- package/dist/index.js +138 -16
- package/dist/react.d.ts +5 -0
- package/dist/runtime-entry.d.ts +5 -0
- package/dist/runtime.js +18 -18
- package/examples/bridge/react-host/package-lock.json +3 -3
- package/examples/bridge/react-host/package.json +1 -1
- package/examples/bridge/react-remote/package-lock.json +3 -3
- package/examples/bridge/react-remote/package.json +1 -1
- package/examples/bridge/vue-host/package-lock.json +3 -3
- package/examples/bridge/vue-host/package.json +1 -1
- package/examples/bridge/vue-remote/package-lock.json +3 -3
- package/examples/bridge/vue-remote/package.json +1 -1
- package/examples/react/host/package-lock.json +2 -2
- package/examples/react/host/package.json +1 -1
- package/examples/react/remote/package-lock.json +2 -2
- package/examples/react/remote/package.json +1 -1
- package/examples/vue/host/package-lock.json +2 -2
- package/examples/vue/host/package.json +1 -1
- package/examples/vue/remote/package-lock.json +2 -2
- package/examples/vue/remote/package.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
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
|
+
|
|
9
|
+
## 5.3.2
|
|
10
|
+
|
|
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 复验通过。
|
|
12
|
+
|
|
3
13
|
## 5.3.1
|
|
4
14
|
|
|
5
15
|
- **修复:Vite 8(rolldown)下 React 联邦生产构建 REQUIRE_TLA 失败**——React 生态的本体为 CJS(`react-dom/cjs/*` 内部 `require("react")`),插件的 CJS require 重定向此前把目标指向与共享协商门面同体的垫片(`await loadShare(...)` 顶层 await 形态);rolldown 按 Node 语义在构建期拒绝「CJS require 含顶层 await 的模块」(vite 5-7 的 rollup 无此限制,属存量兼容缺口,5.2.5 无桥接工程在 vite8 下同样复现)。现在 CJS 垫片改为**同步形态**:运行时新增 `getLoadedShare` 同步查询(loadShare 成功后缓存实例值,已协商加载的实例优先命中,跨端单例语义与 TLA 版 loadShare 对齐),未就绪时直连本应用本体(与 provide/fallback 同一模块,构建期单份 → 实例恒同)。vite 5.1.8/6.4.3/7.3.6/8.3.1 × {vue,react}×{host,remote} 十六个真实工程构建全部通过;`runtime.js` gzip 8812B(门禁 ≤9216B)。**已知边界(如实声明)**:vite 8.3.1 的桥接/React 生产**页面运行**在本轮仍无法通过挂载验收(rolldown 对「TLA 协商门面 × 动态目标透传重定向 × async chunk 强制拆分」的组合行为导致入口求值死锁,插件层六种分组/时序变体均无法完全规避;dev 全功能正常),归因为 rolldown-vite 上游行为,详见验收报告。
|
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
|
|
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-
|
|
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
|
-
|
|
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
|
|
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:
|
|
522
|
-
throw
|
|
531
|
+
emitError({ remote: shareKey, error: err });
|
|
532
|
+
throw err;
|
|
523
533
|
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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:
|
|
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
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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;
|