@fulgurjs/federation 3.0.1 → 4.1.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.
- package/CHANGELOG.md +35 -0
- package/README.md +303 -110
- package/dist/{chunk-TMSMEZPM.js → chunk-75PP6EAK.js} +1 -1
- package/dist/chunk-NUZKZUTD.js +144 -0
- package/dist/chunk-TSXZ7TWA.js +144 -0
- package/dist/{chunk-3VBYOVH4.js → chunk-WBCXLR2N.js} +9 -1
- package/dist/cli.js +584 -127
- package/dist/config.cjs +63 -4
- package/dist/config.d.cts +31 -4
- package/dist/config.d.ts +31 -4
- package/dist/config.js +62 -4
- package/dist/context.cjs +12 -2
- package/dist/context.d.cts +21 -5
- package/dist/context.d.ts +21 -5
- package/dist/context.js +12 -3
- package/dist/index.cjs +149 -59
- package/dist/index.d.cts +2 -92
- package/dist/index.d.ts +2 -92
- package/dist/index.js +149 -59
- package/dist/options-CRGpSmVj.d.cts +100 -0
- package/dist/options-CRGpSmVj.d.ts +100 -0
- package/dist/pages.js +4 -139
- package/dist/runtime-entry.d.ts +360 -0
- package/dist/runtime-entry.js +5 -0
- package/dist/runtime.js +10 -3
- package/dist/vue-adapter.cjs +307 -0
- package/dist/vue-adapter.d.cts +62 -0
- package/dist/vue-adapter.d.ts +62 -0
- package/dist/vue-adapter.js +9 -0
- package/dist/vue.cjs +419 -21
- package/dist/vue.d.cts +8 -33
- package/dist/vue.d.ts +8 -33
- package/dist/vue.js +4 -845
- package/docs//350/277/201/347/247/273/346/214/207/345/215/227.md +21 -16
- package/package.json +13 -5
- package/client.d.ts +0 -180
package/dist/vue.cjs
CHANGED
|
@@ -20,10 +20,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/vue.ts
|
|
21
21
|
var vue_exports = {};
|
|
22
22
|
__export(vue_exports, {
|
|
23
|
+
createHostPages: () => createHostPages2,
|
|
23
24
|
remoteComponent: () => remoteComponent
|
|
24
25
|
});
|
|
25
26
|
module.exports = __toCommonJS(vue_exports);
|
|
26
|
-
var import_vue = require("vue");
|
|
27
27
|
|
|
28
28
|
// src/semver.ts
|
|
29
29
|
var VERSION_RE = /^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-((?:[0-9A-Za-z-]+)(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
@@ -254,10 +254,20 @@ var ErrorCodes = {
|
|
|
254
254
|
/** 加载到的模块没有任何导出(常见:exposes 指向了不导出内容的文件) */
|
|
255
255
|
EMPTY_EXPORTS: "MFU-009",
|
|
256
256
|
/** singleton 共享协商版本与消费方 requiredVersion 不一致(使用作用域版本,仅告警) */
|
|
257
|
-
SINGLETON_SKEW: "MFU-010"
|
|
257
|
+
SINGLETON_SKEW: "MFU-010",
|
|
258
|
+
/** setup 生命周期入口导出形态非法(默认导出不是函数,或具名 onSession 不是函数) */
|
|
259
|
+
SETUP_INVALID_EXPORT: "MFU-011",
|
|
260
|
+
/** setup/onSession 执行抛错(该次 loadRemote 拒绝;仅清失败阶段缓存,允许重试) */
|
|
261
|
+
SETUP_RUN_FAILED: "MFU-012",
|
|
262
|
+
/** 远程声明了 onSession 但宿主 AppContext 未提供 sessionKey(登录代次) */
|
|
263
|
+
SETUP_SESSION_KEY_MISSING: "MFU-013",
|
|
264
|
+
/** setup/onSession 执行的同步段内递归 loadRemote 同一远程 */
|
|
265
|
+
SETUP_RECURSION: "MFU-014"
|
|
258
266
|
};
|
|
259
267
|
|
|
260
268
|
// src/runtime/index.ts
|
|
269
|
+
var SETUP_META_KEY = "__fulgurjsSetup";
|
|
270
|
+
var APP_CONTEXT_GLOBAL_KEY = "__FULGURJS_APP_CONFIG__";
|
|
261
271
|
var DEFAULT_TIMEOUT = 15e3;
|
|
262
272
|
var DEFAULT_RETRIES = 2;
|
|
263
273
|
var BREAKER_THRESHOLD = 5;
|
|
@@ -282,6 +292,140 @@ function createRuntime() {
|
|
|
282
292
|
}
|
|
283
293
|
}
|
|
284
294
|
};
|
|
295
|
+
const lifecycleStates = /* @__PURE__ */ new Map();
|
|
296
|
+
let lifecycleSyncRemote;
|
|
297
|
+
const lifecycleOf = (name) => {
|
|
298
|
+
let st = lifecycleStates.get(name);
|
|
299
|
+
if (!st) lifecycleStates.set(name, st = {});
|
|
300
|
+
return st;
|
|
301
|
+
};
|
|
302
|
+
const currentAppContext = () => globalThis[APP_CONTEXT_GLOBAL_KEY] ?? {};
|
|
303
|
+
async function invokeLifecycleFn(remoteName, fn, ctx, phase) {
|
|
304
|
+
const wrap = (err) => {
|
|
305
|
+
if (err instanceof FgError) return err;
|
|
306
|
+
return new FgError(
|
|
307
|
+
ErrorCodes.SETUP_RUN_FAILED,
|
|
308
|
+
`${phase} of "${remoteName}" threw: ${String(err?.message ?? err)}
|
|
309
|
+
\u4FEE\u6CD5: \u4FEE\u590D\u8BE5 ${phase} \u7684\u521D\u59CB\u5316\u903B\u8F91\u540E\u91CD\u65B0\u52A0\u8F7D\uFF08\u5931\u8D25\u7684\u521D\u59CB\u5316\u7F13\u5B58\u5DF2\u6E05\u9664\uFF0C\u53EF\u76F4\u63A5\u91CD\u8BD5\uFF09`,
|
|
310
|
+
{ remote: remoteName, phase }
|
|
311
|
+
);
|
|
312
|
+
};
|
|
313
|
+
lifecycleSyncRemote = remoteName;
|
|
314
|
+
let result;
|
|
315
|
+
try {
|
|
316
|
+
result = fn(ctx);
|
|
317
|
+
} catch (err) {
|
|
318
|
+
throw wrap(err);
|
|
319
|
+
} finally {
|
|
320
|
+
lifecycleSyncRemote = void 0;
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
await result;
|
|
324
|
+
} catch (err) {
|
|
325
|
+
throw wrap(err);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
async function runSetupPhase(remote, container, setupKey, st) {
|
|
329
|
+
remote.debug.setup = "pending";
|
|
330
|
+
try {
|
|
331
|
+
if (remote.manifestUrl) {
|
|
332
|
+
try {
|
|
333
|
+
await preloadRemote2(`${remote.name}/${setupKey}`);
|
|
334
|
+
} catch {
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const mod = await container.get(setupKey);
|
|
338
|
+
const setupFn = mod?.default;
|
|
339
|
+
if (typeof setupFn !== "function") {
|
|
340
|
+
throw new FgError(
|
|
341
|
+
ErrorCodes.SETUP_INVALID_EXPORT,
|
|
342
|
+
`setup module "${setupKey}" of "${remote.name}": default export is ${mod == null ? String(mod) : typeof setupFn}, expected a function
|
|
343
|
+
expected: export default async function setup(context: RemoteSetupContext) { ... }
|
|
344
|
+
\u4FEE\u6CD5: \u5728 federation({ setup }) \u6307\u5411\u7684\u6587\u4EF6\u8865\u9ED8\u8BA4\u5BFC\u51FA\u51FD\u6570\uFF08\u5177\u540D onSession \u53EF\u9009\uFF1B\u5176\u4ED6\u5BFC\u51FA\u4E0D\u4F5C\u4E3A\u751F\u547D\u5468\u671F\u5165\u53E3\uFF09`,
|
|
345
|
+
{ remote: remote.name, module: setupKey }
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
if (mod.onSession !== void 0 && typeof mod.onSession !== "function") {
|
|
349
|
+
throw new FgError(
|
|
350
|
+
ErrorCodes.SETUP_INVALID_EXPORT,
|
|
351
|
+
`setup module "${setupKey}" of "${remote.name}": export "onSession" is ${typeof mod.onSession}, expected a function
|
|
352
|
+
\u4FEE\u6CD5: \u5C06 onSession \u4FEE\u6B63\u4E3A\u51FD\u6570\u5BFC\u51FA\uFF0C\u6216\u5220\u9664\u8BE5\u5177\u540D\u5BFC\u51FA\uFF08\u5176\u4ED6\u5BFC\u51FA\u540D\u4E0D\u4F5C\u4E3A\u751F\u547D\u5468\u671F\u5165\u53E3\uFF09`,
|
|
353
|
+
{ remote: remote.name, module: setupKey }
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
st.setupExports = mod;
|
|
357
|
+
await invokeLifecycleFn(
|
|
358
|
+
remote.name,
|
|
359
|
+
setupFn,
|
|
360
|
+
{ appContext: currentAppContext(), signal: (st.controller ??= new AbortController()).signal },
|
|
361
|
+
"setup"
|
|
362
|
+
);
|
|
363
|
+
remote.debug.setup = "ready";
|
|
364
|
+
} catch (err) {
|
|
365
|
+
remote.debug.setup = "failed";
|
|
366
|
+
st.setupExports = void 0;
|
|
367
|
+
throw err;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
async function ensureSessionPhase(remote, st, onSession) {
|
|
371
|
+
const appContext = currentAppContext();
|
|
372
|
+
const sessionKey = appContext?.sessionKey;
|
|
373
|
+
if (typeof sessionKey !== "string" || sessionKey === "") {
|
|
374
|
+
throw new FgError(
|
|
375
|
+
ErrorCodes.SETUP_SESSION_KEY_MISSING,
|
|
376
|
+
`remote "${remote.name}" declares onSession but AppContext.sessionKey is ${sessionKey === void 0 ? "missing" : JSON.stringify(sessionKey)}
|
|
377
|
+
expected: \u5BBF\u4E3B\u767B\u5F55\u540E provideAppContext({ ..., sessionKey: '<\u975E\u654F\u611F\u767B\u5F55\u4EE3\u6B21 ID>' })
|
|
378
|
+
\u4FEE\u6CD5: \u5BBF\u4E3B\u767B\u5F55\u6D41\u7A0B\u6BCF\u6B21\u6210\u529F\u767B\u5F55/\u91CD\u767B\u751F\u6210\u65B0\u4EE3\u6B21 ID\uFF08\u7981\u6B62\u7528 token \u5145\u5F53\uFF09\uFF0C\u5E76\u5728 loadRemote \u524D\u63D0\u4F9B`,
|
|
379
|
+
{ remote: remote.name }
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
if (st.sessionPromise && st.sessionKey === sessionKey) return st.sessionPromise;
|
|
383
|
+
st.controller?.abort();
|
|
384
|
+
const controller = new AbortController();
|
|
385
|
+
st.controller = controller;
|
|
386
|
+
st.sessionKey = sessionKey;
|
|
387
|
+
const prev = st.sessionPromise ? st.sessionPromise.catch(() => {
|
|
388
|
+
}) : Promise.resolve();
|
|
389
|
+
const run = (async () => {
|
|
390
|
+
await prev;
|
|
391
|
+
if (controller.signal.aborted) return;
|
|
392
|
+
await invokeLifecycleFn(remote.name, onSession, { appContext, sessionKey, signal: controller.signal }, "onSession");
|
|
393
|
+
})();
|
|
394
|
+
st.sessionPromise = run;
|
|
395
|
+
run.catch(() => {
|
|
396
|
+
if (st.sessionKey === sessionKey) {
|
|
397
|
+
st.sessionKey = void 0;
|
|
398
|
+
st.sessionPromise = void 0;
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
return run;
|
|
402
|
+
}
|
|
403
|
+
async function ensureLifecycle(remote, container) {
|
|
404
|
+
const setupKey = container?.[SETUP_META_KEY];
|
|
405
|
+
if (typeof setupKey !== "string" || setupKey === "") return;
|
|
406
|
+
const st = lifecycleOf(remote.name);
|
|
407
|
+
if (!st.setupPromise) {
|
|
408
|
+
st.setupPromise = runSetupPhase(remote, container, setupKey, st).catch((err) => {
|
|
409
|
+
st.setupPromise = void 0;
|
|
410
|
+
throw err;
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
await st.setupPromise;
|
|
414
|
+
const onSession = st.setupExports?.onSession;
|
|
415
|
+
if (typeof onSession === "function") {
|
|
416
|
+
await ensureSessionPhase(remote, st, onSession);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
function clearSessionState2() {
|
|
420
|
+
for (const st of lifecycleStates.values()) {
|
|
421
|
+
try {
|
|
422
|
+
st.controller?.abort();
|
|
423
|
+
} catch {
|
|
424
|
+
}
|
|
425
|
+
st.sessionKey = void 0;
|
|
426
|
+
st.sessionPromise = void 0;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
285
429
|
const getScope = (name) => shareScopeMap2[name] ??= /* @__PURE__ */ Object.create(null);
|
|
286
430
|
function emitError(info) {
|
|
287
431
|
try {
|
|
@@ -341,7 +485,7 @@ function createRuntime() {
|
|
|
341
485
|
state: prev?.state ?? "idle",
|
|
342
486
|
breakerState: prev?.breakerState ?? { fails: 0, openUntil: 0 },
|
|
343
487
|
entryInflight: prev?.entryInflight ?? /* @__PURE__ */ new Map(),
|
|
344
|
-
debug: prev?.debug ?? { entry: r.entry, status: "idle" }
|
|
488
|
+
debug: prev?.debug ?? { entry: r.entry, status: "idle", setup: "none" }
|
|
345
489
|
});
|
|
346
490
|
}
|
|
347
491
|
}
|
|
@@ -610,6 +754,14 @@ function createRuntime() {
|
|
|
610
754
|
}
|
|
611
755
|
async function loadRemote2(spec, opts) {
|
|
612
756
|
const { remote: name, module: module2 } = parseSpec2(spec);
|
|
757
|
+
if (module2 && lifecycleSyncRemote === name) {
|
|
758
|
+
throw new FgError(
|
|
759
|
+
ErrorCodes.SETUP_RECURSION,
|
|
760
|
+
`loadRemote("${spec}") re-entered from setup/onSession of "${name}" \u2014 the initialization promise would await itself (deadlock)
|
|
761
|
+
\u4FEE\u6CD5: setup/onSession \u5185\u4E0D loadRemote \u540C\u4E00\u8FDC\u7A0B\u7684\u6A21\u5757\uFF1B\u9700\u8981\u7684\u4E1A\u52A1\u6A21\u5757\u6539\u4E3A\u5728\u9875\u9762\u7EC4\u4EF6\u5185\u52A0\u8F7D\uFF0C\u6216\u7ECF context \u901A\u9053\u89E3\u8026`,
|
|
762
|
+
{ remote: name }
|
|
763
|
+
);
|
|
764
|
+
}
|
|
613
765
|
try {
|
|
614
766
|
hooks.beforeLoadRemote?.({ remote: name, module: module2 });
|
|
615
767
|
} catch (hookErr) {
|
|
@@ -636,6 +788,7 @@ function createRuntime() {
|
|
|
636
788
|
throw err;
|
|
637
789
|
}
|
|
638
790
|
if (!module2) return container;
|
|
791
|
+
await ensureLifecycle(remote, container);
|
|
639
792
|
const cacheKey = `${name}@${remote.shareScope || "default"}#${module2}`;
|
|
640
793
|
if (!loadedModules.has(cacheKey)) {
|
|
641
794
|
loadedModules.set(
|
|
@@ -829,7 +982,8 @@ function createRuntime() {
|
|
|
829
982
|
loadRemote: loadRemote2,
|
|
830
983
|
getContainer: getContainer2,
|
|
831
984
|
preloadRemote: preloadRemote2,
|
|
832
|
-
parseSpec: parseSpec2
|
|
985
|
+
parseSpec: parseSpec2,
|
|
986
|
+
clearSessionState: clearSessionState2
|
|
833
987
|
};
|
|
834
988
|
}
|
|
835
989
|
var g = globalThis;
|
|
@@ -849,9 +1003,153 @@ var loadRemote = runtime.loadRemote;
|
|
|
849
1003
|
var getContainer = runtime.getContainer;
|
|
850
1004
|
var preloadRemote = runtime.preloadRemote;
|
|
851
1005
|
var parseSpec = runtime.parseSpec;
|
|
1006
|
+
var clearSessionState = runtime.clearSessionState;
|
|
852
1007
|
var shareScopeMap = runtime.shareScopeMap;
|
|
853
1008
|
|
|
854
|
-
// src/vue.ts
|
|
1009
|
+
// src/vue-adapter.ts
|
|
1010
|
+
var import_vue = require("vue");
|
|
1011
|
+
|
|
1012
|
+
// src/pages.ts
|
|
1013
|
+
var normSpec = (s) => s.replace(/^[./]+/, "").replace(/\/+$/, "");
|
|
1014
|
+
var hasParam = (route) => /(^|\/):[\w]+/.test(route);
|
|
1015
|
+
var segs = (route) => route.split("/").filter(Boolean);
|
|
1016
|
+
function defaultDeriveSpec(route) {
|
|
1017
|
+
return segs(route).slice(1).filter((seg) => !seg.startsWith(":")).join("/");
|
|
1018
|
+
}
|
|
1019
|
+
function shadows(earlier, later) {
|
|
1020
|
+
const a = segs(earlier);
|
|
1021
|
+
const b = segs(later);
|
|
1022
|
+
if (a.length !== b.length) return null;
|
|
1023
|
+
if (earlier === later) return "full";
|
|
1024
|
+
let hasParam2 = false;
|
|
1025
|
+
for (let i = 0; i < a.length; i++) {
|
|
1026
|
+
if (a[i] === b[i]) continue;
|
|
1027
|
+
if (a[i].startsWith(":")) {
|
|
1028
|
+
hasParam2 = true;
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
1031
|
+
return null;
|
|
1032
|
+
}
|
|
1033
|
+
return hasParam2 ? "param" : null;
|
|
1034
|
+
}
|
|
1035
|
+
function remoteOfRoute(route, remotes) {
|
|
1036
|
+
if (!remotes) return null;
|
|
1037
|
+
for (const [prefix, name] of Object.entries(remotes)) {
|
|
1038
|
+
if (route === prefix.replace(/\/+$/, "") || route.startsWith(prefix)) return name;
|
|
1039
|
+
}
|
|
1040
|
+
return null;
|
|
1041
|
+
}
|
|
1042
|
+
function validatePages(pages, options = {}) {
|
|
1043
|
+
const deriveSpec = options.deriveSpec ?? defaultDeriveSpec;
|
|
1044
|
+
const violations = [];
|
|
1045
|
+
const effective = pages.map((p) => normSpec(p.spec ?? deriveSpec(p.route)));
|
|
1046
|
+
pages.forEach((p, i) => {
|
|
1047
|
+
if (p.spec || !hasParam(p.route)) return;
|
|
1048
|
+
pages.forEach((q, j) => {
|
|
1049
|
+
if (i === j) return;
|
|
1050
|
+
if (effective[j] !== effective[i]) return;
|
|
1051
|
+
violations.push({
|
|
1052
|
+
rule: "R1",
|
|
1053
|
+
level: "error",
|
|
1054
|
+
message: `[R1] \u8DEF\u7531 "${p.route}" \u5265\u53C2\u63A8\u5BFC spec "${effective[i]}" \u4E0E\u8DEF\u7531 "${q.route}"${q.spec ? "\uFF08\u663E\u5F0F spec\uFF09" : ""}\u6536\u655B\u76F8\u540C\u3002
|
|
1055
|
+
\u6839\u56E0\uFF1A\u53C2\u6570\u6BB5\u5265\u79BB\u540E expose \u952E\u4E0E\u5176\u5B83\u6761\u76EE\u76F8\u540C\uFF0C\u8BE5\u8DEF\u7531\u4F1A\u9759\u9ED8\u52A0\u8F7D\u9519\u8BEF\u7EC4\u4EF6
|
|
1056
|
+
\uFF08\u771F\u5B9E\u4E8B\u6545\uFF1A\u5E26\u53C2\u52A8\u4F5C\u9875\u8BEF\u8D70\u5217\u8868/\u66F4\u65B0\u8BED\u4E49\uFF0C\u6570\u636E\u98CE\u9669\uFF09\u3002
|
|
1057
|
+
\u4FEE\u6CD5\uFF1A\u4E3A\u8BE5\u6761\u76EE\u663E\u5F0F\u58F0\u660E spec: '...'\uFF08\u6307\u5411\u5E26\u53C2\u9875\u9762\u81EA\u5DF1\u7684 expose\uFF09\uFF0C\u6216\u8C03\u6574\u8DEF\u7531\u62C6\u5206\u3002`
|
|
1058
|
+
});
|
|
1059
|
+
});
|
|
1060
|
+
});
|
|
1061
|
+
const seenSpec = /* @__PURE__ */ new Map();
|
|
1062
|
+
pages.forEach((p, i) => {
|
|
1063
|
+
const key = effective[i];
|
|
1064
|
+
if (seenSpec.has(key)) {
|
|
1065
|
+
const first = pages[seenSpec.get(key)];
|
|
1066
|
+
const bothExplicit = !!p.spec && !!first.spec;
|
|
1067
|
+
violations.push({
|
|
1068
|
+
rule: "R2",
|
|
1069
|
+
level: "warn",
|
|
1070
|
+
message: `[R2] \u8DEF\u7531 "${p.route}" \u4E0E "${first.route}" \u6709\u6548 spec \u76F8\u540C\uFF08"${key}"\uFF09\u3002${bothExplicit ? "\u4E24\u6761\u5747\u4E3A\u663E\u5F0F spec" : "\u5176\u4E2D\u542B\u63A8\u5BFC spec"}\u2014\u2014\u82E5\u4E3A\u523B\u610F\u7684\u83DC\u5355\u522B\u540D\u53EF\u5FFD\u7565\u672C\u8B66\u544A\uFF0C\u5426\u5219\u8BF7\u6838\u5BF9\u3002`
|
|
1071
|
+
});
|
|
1072
|
+
} else {
|
|
1073
|
+
seenSpec.set(key, i);
|
|
1074
|
+
}
|
|
1075
|
+
});
|
|
1076
|
+
if (options.schema && options.remotes) {
|
|
1077
|
+
pages.forEach((p, i) => {
|
|
1078
|
+
const remote = remoteOfRoute(p.route, options.remotes);
|
|
1079
|
+
if (!remote) return;
|
|
1080
|
+
const entry = options.schema[remote];
|
|
1081
|
+
if (!entry || entry.exists === false) return;
|
|
1082
|
+
const exposes = new Set((entry.exposes ?? []).map(normSpec));
|
|
1083
|
+
if (!exposes.has(effective[i])) {
|
|
1084
|
+
violations.push({
|
|
1085
|
+
rule: "R3",
|
|
1086
|
+
level: "error",
|
|
1087
|
+
message: `[R3] \u8DEF\u7531 "${p.route}" \u7684 spec "${effective[i]}" \u4E0D\u5728\u8FDC\u7A0B "${remote}" \u7684 exposes \u6E05\u5355\u4E2D\u3002
|
|
1088
|
+
\u6839\u56E0\uFF1Aspec \u62FC\u5199\u4E0E\u8FDC\u7A0B exposes \u952E\u4E0D\u4E00\u81F4\uFF08\u8FD0\u884C\u65F6\u5C06 MFU-006 \u52A0\u8F7D\u5931\u8D25\uFF09\u3002
|
|
1089
|
+
\u4FEE\u6CD5\uFF1A\u6838\u5BF9\u8FDC\u7A0B vite.config exposes \u952E\u540D\uFF0C\u6216\u4FEE\u6B63\u672C\u8868 spec\u3002
|
|
1090
|
+
\u8FDC\u7A0B\u5B9E\u9645 exposes\uFF08\u524D 10\uFF09\uFF1A${[...exposes].slice(0, 10).join("\u3001") || "\uFF08\u7A7A\uFF09"}`
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
pages.forEach((p, i) => {
|
|
1096
|
+
pages.forEach((q, j) => {
|
|
1097
|
+
if (j <= i) return;
|
|
1098
|
+
const kind = shadows(p.route, q.route);
|
|
1099
|
+
if (!kind) return;
|
|
1100
|
+
violations.push({
|
|
1101
|
+
rule: "R4",
|
|
1102
|
+
level: "error",
|
|
1103
|
+
message: kind === "full" ? `[R4] \u8DEF\u7531\u5B8C\u5168\u91CD\u590D\uFF1A"${p.route}"\uFF08\u7B2C ${i + 1} \u6761\uFF09\u4E0E "${q.route}"\uFF08\u7B2C ${j + 1} \u6761\uFF09\u3002` : `[R4] \u8DEF\u7531 "${q.route}"\uFF08\u7B2C ${j + 1} \u6761\uFF09\u4F1A\u88AB\u66F4\u9760\u524D\u7684 "${p.route}" \u906E\u853D\uFF08\u5339\u914D\u5148\u5230\u5148\u5F97\uFF0C\u540E\u8005\u6210\u6B7B\u8DEF\u7531\uFF09\u3002
|
|
1104
|
+
\u4FEE\u6CD5\uFF1A\u8C03\u6574\u6570\u7EC4\u987A\u5E8F\uFF08\u66F4\u5177\u4F53\u7684\u9759\u6001\u8DEF\u7531\u653E\u524D\u9762\uFF09\u6216\u5408\u5E76\u6761\u76EE\u3002`
|
|
1105
|
+
});
|
|
1106
|
+
});
|
|
1107
|
+
});
|
|
1108
|
+
const seenName = /* @__PURE__ */ new Map();
|
|
1109
|
+
pages.forEach((p, i) => {
|
|
1110
|
+
const name = typeof p.name === "string" ? p.name : "";
|
|
1111
|
+
if (!name) return;
|
|
1112
|
+
if (seenName.has(name)) {
|
|
1113
|
+
violations.push({
|
|
1114
|
+
rule: "R5",
|
|
1115
|
+
level: "warn",
|
|
1116
|
+
message: `[R5] name "${name}" \u91CD\u590D\uFF08"${pages[seenName.get(name)].route}" \u4E0E "${p.route}"\uFF09\u2014\u2014vue-router \u547D\u540D\u8DF3\u8F6C push({name}) \u5C06\u547D\u4E2D\u524D\u8005\uFF0C\u8BF7\u6838\u5BF9\u3002`
|
|
1117
|
+
});
|
|
1118
|
+
} else {
|
|
1119
|
+
seenName.set(name, i);
|
|
1120
|
+
}
|
|
1121
|
+
});
|
|
1122
|
+
return violations;
|
|
1123
|
+
}
|
|
1124
|
+
function formatViolations(violations) {
|
|
1125
|
+
const errors = violations.filter((v) => v.level === "error");
|
|
1126
|
+
const warns = violations.filter((v) => v.level === "warn");
|
|
1127
|
+
let out = "";
|
|
1128
|
+
if (errors.length) {
|
|
1129
|
+
out += `[fulgurjs] \u8DEF\u7531\u8868\u6821\u9A8C\u5931\u8D25\uFF08${errors.length} \u9879\uFF0CERROR\uFF09\uFF1A
|
|
1130
|
+
` + errors.map((v) => v.message).join("\n") + "\n";
|
|
1131
|
+
}
|
|
1132
|
+
if (warns.length) {
|
|
1133
|
+
out += `[fulgurjs] \u8DEF\u7531\u8868\u6821\u9A8C\u8B66\u544A\uFF08${warns.length} \u9879\uFF0CWARN\uFF09\uFF1A
|
|
1134
|
+
` + warns.map((v) => v.message).join("\n");
|
|
1135
|
+
}
|
|
1136
|
+
return out.trim();
|
|
1137
|
+
}
|
|
1138
|
+
function definePages(pages, options = {}) {
|
|
1139
|
+
const violations = validatePages(pages, options);
|
|
1140
|
+
const errors = violations.filter((v) => v.level === "error");
|
|
1141
|
+
const warns = violations.filter((v) => v.level === "warn");
|
|
1142
|
+
const text = formatViolations(violations);
|
|
1143
|
+
if (text) {
|
|
1144
|
+
if (errors.length && options.strict !== false) {
|
|
1145
|
+
throw new Error(text);
|
|
1146
|
+
}
|
|
1147
|
+
console[errors.length ? "error" : "warn"](text);
|
|
1148
|
+
}
|
|
1149
|
+
return pages;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
// src/vue-adapter.ts
|
|
855
1153
|
var ERROR_STYLE = {
|
|
856
1154
|
padding: "16px",
|
|
857
1155
|
border: "1px solid #fde2e2",
|
|
@@ -863,9 +1161,7 @@ var ERROR_STYLE = {
|
|
|
863
1161
|
};
|
|
864
1162
|
var RemoteErrorPlaceholder = (0, import_vue.defineComponent)({
|
|
865
1163
|
name: "FulgurjsRemoteError",
|
|
866
|
-
props: {
|
|
867
|
-
error: { type: Object, default: void 0 }
|
|
868
|
-
},
|
|
1164
|
+
props: { error: { type: Object, default: void 0 } },
|
|
869
1165
|
setup(props) {
|
|
870
1166
|
return () => {
|
|
871
1167
|
const err = props.error;
|
|
@@ -874,25 +1170,127 @@ var RemoteErrorPlaceholder = (0, import_vue.defineComponent)({
|
|
|
874
1170
|
return (0, import_vue.h)("div", { style: ERROR_STYLE }, [
|
|
875
1171
|
(0, import_vue.h)("p", { style: "margin:0 0 4px;font-weight:600" }, `\u8FDC\u7A0B\u7EC4\u4EF6\u52A0\u8F7D\u5931\u8D25\uFF08\u9519\u8BEF\u7801 ${code}\uFF09`),
|
|
876
1172
|
(0, import_vue.h)("p", { style: "margin:0 0 8px;word-break:break-all" }, message),
|
|
877
|
-
(0, import_vue.h)(
|
|
878
|
-
"p",
|
|
879
|
-
{ style: "margin:0" },
|
|
880
|
-
"\u4FEE\u6CD5\uFF1A\u2460 \u6838\u5BF9 spec \u7684\u300C\u8FDC\u7A0B\u540D/expose \u540D\u300D\u4E0E\u8FDC\u7A0B\u5E94\u7528 exposes \u662F\u5426\u4E00\u81F4\uFF08MFU-006/008\uFF09\uFF1B\u2461 \u6838\u5BF9 remotes \u5730\u5740\u7AEF\u53E3\u4E0E\u8FDC\u7A0B\u670D\u52A1\u53EF\u8FBE\u6027\u3001remoteEntry \u662F\u5426\u53EF\u8BBF\u95EE\uFF08MFU-001\uFF09\uFF1B\u2462 \u67E5\u770B window \u7684 fulgurjs:error \u4E8B\u4EF6\u4E0E console \u540C\u6E90\u9519\u8BEF\u5B9A\u4F4D\u6839\u56E0\u3002"
|
|
881
|
-
)
|
|
1173
|
+
(0, import_vue.h)("p", { style: "margin:0" }, "\u4FEE\u6CD5\uFF1A\u2460 \u6838\u5BF9 spec \u7684\u300C\u8FDC\u7A0B\u540D/expose \u540D\u300D\u4E0E\u8FDC\u7A0B\u5E94\u7528 exposes \u662F\u5426\u4E00\u81F4\uFF08MFU-006/008\uFF09\uFF1B\u2461 \u6838\u5BF9 remotes \u5730\u5740\u7AEF\u53E3\u4E0E\u8FDC\u7A0B\u670D\u52A1\u53EF\u8FBE\u6027\u3001remoteEntry \u662F\u5426\u53EF\u8BBF\u95EE\uFF08MFU-001\uFF09\uFF1B\u2462 \u67E5\u770B window \u7684 fulgurjs:error \u4E8B\u4EF6\u4E0E console \u540C\u6E90\u9519\u8BEF\u5B9A\u4F4D\u6839\u56E0\u3002")
|
|
882
1174
|
]);
|
|
883
1175
|
};
|
|
884
1176
|
}
|
|
885
1177
|
});
|
|
886
|
-
function
|
|
887
|
-
return (
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
1178
|
+
function createRemoteComponent(loadRemote2) {
|
|
1179
|
+
return function remoteComponent2(spec, opts = {}) {
|
|
1180
|
+
return (0, import_vue.defineAsyncComponent)({
|
|
1181
|
+
loader: () => loadRemote2(spec, { retries: opts.retries }).then((m) => m.default ?? m),
|
|
1182
|
+
loadingComponent: opts.loadingComponent,
|
|
1183
|
+
errorComponent: opts.errorComponent ?? RemoteErrorPlaceholder,
|
|
1184
|
+
delay: opts.delay,
|
|
1185
|
+
timeout: opts.timeout
|
|
1186
|
+
});
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
var cleanCompName = (spec) => "Fulgurjs_" + spec.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
1190
|
+
function defaultDeriveSpec2(route) {
|
|
1191
|
+
return route.split("/").filter(Boolean).slice(1).filter((seg) => !seg.startsWith(":")).join("/");
|
|
1192
|
+
}
|
|
1193
|
+
function createHostPages(options, load) {
|
|
1194
|
+
const { pages, remotePrefixes, deriveSpec, schema, strict, base, beforeLoad, loadingComponent, errorComponent, delay } = options;
|
|
1195
|
+
definePages(pages, { deriveSpec, remotes: remotePrefixes, schema, strict });
|
|
1196
|
+
const prefixes = Object.entries(remotePrefixes).map(([p, name]) => [p.endsWith("/") ? p : `${p}/`, name]).sort((a, b) => b[0].length - a[0].length);
|
|
1197
|
+
const remoteOf = (route) => {
|
|
1198
|
+
for (const [prefix, name] of prefixes) {
|
|
1199
|
+
if (route.startsWith(prefix) || `${route}/` === prefix) return name;
|
|
1200
|
+
}
|
|
1201
|
+
throw new Error(
|
|
1202
|
+
`[fulgurjs] page route "${route}" matches none of remotePrefixes { ${Object.entries(remotePrefixes).map(([k, v]) => `'${k}': '${v}'`).join(", ")} }
|
|
1203
|
+
\u6839\u56E0: \u9875\u9762\u8868\u6761\u76EE\u7684\u8DEF\u7531\u4E0D\u5728\u4EFB\u4F55\u5DF2\u58F0\u660E\u7684\u8FDC\u7A0B\u524D\u7F00\u4E0B\u3002
|
|
1204
|
+
\u4FEE\u6CD5: \u5728 remotePrefixes \u8865\u5145\u8BE5\u8DEF\u7531\u524D\u7F00 \u2192 \u8FDC\u7A0B\u540D\u7684\u6620\u5C04\uFF0C\u6216\u4FEE\u6B63\u9875\u9762\u8868 route\u3002`
|
|
1205
|
+
);
|
|
1206
|
+
};
|
|
1207
|
+
for (const page of pages) remoteOf(page.route);
|
|
1208
|
+
const specOf = (page) => {
|
|
1209
|
+
const s = page.spec ?? (deriveSpec ? deriveSpec(page.route) : defaultDeriveSpec2(page.route));
|
|
1210
|
+
return `${remoteOf(page.route)}/${String(s).replace(/^[./]+/, "")}`;
|
|
1211
|
+
};
|
|
1212
|
+
const baseNorm = base && base !== "/" ? base.endsWith("/") ? base : `${base}/` : "";
|
|
1213
|
+
const matchSegments = (pattern, path) => {
|
|
1214
|
+
const patSegs = pattern.split("/").filter(Boolean);
|
|
1215
|
+
const pathSegs = path.split("/").filter(Boolean);
|
|
1216
|
+
if (patSegs.length !== pathSegs.length) return null;
|
|
1217
|
+
const params = {};
|
|
1218
|
+
for (let i = 0; i < patSegs.length; i++) {
|
|
1219
|
+
const seg = patSegs[i];
|
|
1220
|
+
if (seg.startsWith(":")) {
|
|
1221
|
+
try {
|
|
1222
|
+
params[seg.slice(1)] = decodeURIComponent(pathSegs[i]);
|
|
1223
|
+
} catch {
|
|
1224
|
+
return null;
|
|
1225
|
+
}
|
|
1226
|
+
} else if (seg !== pathSegs[i]) {
|
|
1227
|
+
return null;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
return params;
|
|
1231
|
+
};
|
|
1232
|
+
let cacheSession;
|
|
1233
|
+
const compCache = /* @__PURE__ */ new Map();
|
|
1234
|
+
const resolve = (rawPath) => {
|
|
1235
|
+
let clean = String(rawPath ?? "");
|
|
1236
|
+
if (baseNorm) {
|
|
1237
|
+
if (clean === baseNorm.replace(/\/$/, "")) clean = "/";
|
|
1238
|
+
else if (clean.startsWith(baseNorm)) clean = `/${clean.slice(baseNorm.length)}`;
|
|
1239
|
+
}
|
|
1240
|
+
clean = (clean.replace(/\/+$/, "") || "/").split("?")[0].split("#")[0];
|
|
1241
|
+
for (const page of pages) {
|
|
1242
|
+
const params = matchSegments(page.route, clean);
|
|
1243
|
+
if (params) return { page, remote: remoteOf(page.route), spec: specOf(page), params };
|
|
1244
|
+
}
|
|
1245
|
+
return null;
|
|
1246
|
+
};
|
|
1247
|
+
const component = (spec) => {
|
|
1248
|
+
const sk = (globalThis.__FULGURJS_APP_CONFIG__ ?? {}).sessionKey;
|
|
1249
|
+
const sessionKey = typeof sk === "string" ? sk : void 0;
|
|
1250
|
+
if (sessionKey !== cacheSession) {
|
|
1251
|
+
compCache.clear();
|
|
1252
|
+
cacheSession = sessionKey;
|
|
1253
|
+
}
|
|
1254
|
+
let comp = compCache.get(spec);
|
|
1255
|
+
if (!comp) {
|
|
1256
|
+
const name = cleanCompName(spec);
|
|
1257
|
+
const asyncComp = (0, import_vue.defineAsyncComponent)({
|
|
1258
|
+
loader: async () => {
|
|
1259
|
+
await beforeLoad?.();
|
|
1260
|
+
const mod = await load(spec);
|
|
1261
|
+
const inner = mod?.default ?? mod;
|
|
1262
|
+
if (!inner) {
|
|
1263
|
+
throw new Error(
|
|
1264
|
+
`[fulgurjs] remote module "${spec}" did not export a component (got ${inner === null ? "null" : typeof inner})\u3002
|
|
1265
|
+
\u4FEE\u6CD5: \u6838\u5BF9\u8FDC\u7A0B exposes \u952E\u540D\u4E0E\u9875\u9762\u8868 spec \u662F\u5426\u4E00\u81F4\uFF08MFU-006 \u8BED\u4E49\uFF09`
|
|
1266
|
+
);
|
|
1267
|
+
}
|
|
1268
|
+
return inner;
|
|
1269
|
+
},
|
|
1270
|
+
loadingComponent,
|
|
1271
|
+
errorComponent: errorComponent ?? RemoteErrorPlaceholder,
|
|
1272
|
+
delay
|
|
1273
|
+
});
|
|
1274
|
+
comp = (0, import_vue.defineComponent)({
|
|
1275
|
+
name,
|
|
1276
|
+
inheritAttrs: false,
|
|
1277
|
+
setup: (_props, ctx) => () => (0, import_vue.h)(asyncComp, ctx.attrs, ctx.slots)
|
|
1278
|
+
});
|
|
1279
|
+
compCache.set(spec, comp);
|
|
1280
|
+
}
|
|
1281
|
+
return comp;
|
|
1282
|
+
};
|
|
1283
|
+
const keepAliveNames = pages.filter((p) => p.keepAlive).map((p) => cleanCompName(specOf(p)));
|
|
1284
|
+
return { pages, resolve, component, keepAliveNames };
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
// src/vue.ts
|
|
1288
|
+
var remoteComponent = createRemoteComponent(loadRemote);
|
|
1289
|
+
function createHostPages2(options) {
|
|
1290
|
+
return createHostPages(options, loadRemote);
|
|
894
1291
|
}
|
|
895
1292
|
// Annotate the CommonJS export names for ESM import in node:
|
|
896
1293
|
0 && (module.exports = {
|
|
1294
|
+
createHostPages,
|
|
897
1295
|
remoteComponent
|
|
898
1296
|
});
|
package/dist/vue.d.cts
CHANGED
|
@@ -1,35 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as vue from 'vue';
|
|
2
|
+
import { createHostPages as createHostPages$1, HostPages, RemoteComponentOptions } from './vue-adapter.cjs';
|
|
3
|
+
export { HostPagesOptions, ResolvedHostPage } from './vue-adapter.cjs';
|
|
4
|
+
import './pages.cjs';
|
|
2
5
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* 定位:runtime.js 保持框架无关(不 import vue,gzip 红线守卫),
|
|
7
|
-
* Vue 封装按需引入——独立文件、零 runtime 体积增量。
|
|
8
|
-
*
|
|
9
|
-
* 语义(用法与选项见 README §8):
|
|
10
|
-
* - 内部 = defineAsyncComponent({ loader: () => loadRemote(spec, opts).then(m => m.default ?? m) });
|
|
11
|
-
* - H3 零兜底:加载失败显式进错误态(fulgurjs:error 事件由 runtime 层照常发出),
|
|
12
|
-
* 不传 errorComponent 时渲染内置默认错误占位(文案含错误码+根因+修法,显式非静默);
|
|
13
|
-
* - 模块去重沿用 loadRemote 内部 Promise 缓存(同 spec 不重复加载);
|
|
14
|
-
* - 返回标准 Vue 异步组件,props(如 form-params)在使用处透传,不改分发机制。
|
|
15
|
-
*/
|
|
6
|
+
declare const remoteComponent: (spec: string, opts?: RemoteComponentOptions) => vue.Component;
|
|
7
|
+
/** 宿主页面适配器(绑定本包运行时的 loadRemote;选项与返回值类型见 vue-adapter) */
|
|
8
|
+
declare function createHostPages(options: Parameters<typeof createHostPages$1>[0]): HostPages;
|
|
16
9
|
|
|
17
|
-
|
|
18
|
-
/** 加载期间展示的组件 */
|
|
19
|
-
loadingComponent?: Component;
|
|
20
|
-
/** 加载失败展示的组件(收到 error prop);不传时用内置默认错误占位 */
|
|
21
|
-
errorComponent?: Component;
|
|
22
|
-
/** 单次调用级重试覆盖,透传 loadRemote(缺省用远程注册值,默认 2) */
|
|
23
|
-
retries?: number;
|
|
24
|
-
/** defineAsyncComponent delay(ms):切换到 loadingComponent 前的等待,默认 200 */
|
|
25
|
-
delay?: number;
|
|
26
|
-
/** defineAsyncComponent timeout(ms):超时进错误态;不设则由 runtime 容器超时兜底 */
|
|
27
|
-
timeout?: number;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* 创建远程组件:`remoteComponent('demo-host/FormRouterPage')`。
|
|
31
|
-
* 返回标准 Vue 异步组件,使用处直接透传 props(如 form-params)。
|
|
32
|
-
*/
|
|
33
|
-
declare function remoteComponent(spec: string, opts?: RemoteComponentOptions): Component;
|
|
34
|
-
|
|
35
|
-
export { type RemoteComponentOptions, remoteComponent };
|
|
10
|
+
export { HostPages, RemoteComponentOptions, createHostPages, remoteComponent };
|
package/dist/vue.d.ts
CHANGED
|
@@ -1,35 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as vue from 'vue';
|
|
2
|
+
import { createHostPages as createHostPages$1, HostPages, RemoteComponentOptions } from './vue-adapter.js';
|
|
3
|
+
export { HostPagesOptions, ResolvedHostPage } from './vue-adapter.js';
|
|
4
|
+
import './pages.js';
|
|
2
5
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* 定位:runtime.js 保持框架无关(不 import vue,gzip 红线守卫),
|
|
7
|
-
* Vue 封装按需引入——独立文件、零 runtime 体积增量。
|
|
8
|
-
*
|
|
9
|
-
* 语义(用法与选项见 README §8):
|
|
10
|
-
* - 内部 = defineAsyncComponent({ loader: () => loadRemote(spec, opts).then(m => m.default ?? m) });
|
|
11
|
-
* - H3 零兜底:加载失败显式进错误态(fulgurjs:error 事件由 runtime 层照常发出),
|
|
12
|
-
* 不传 errorComponent 时渲染内置默认错误占位(文案含错误码+根因+修法,显式非静默);
|
|
13
|
-
* - 模块去重沿用 loadRemote 内部 Promise 缓存(同 spec 不重复加载);
|
|
14
|
-
* - 返回标准 Vue 异步组件,props(如 form-params)在使用处透传,不改分发机制。
|
|
15
|
-
*/
|
|
6
|
+
declare const remoteComponent: (spec: string, opts?: RemoteComponentOptions) => vue.Component;
|
|
7
|
+
/** 宿主页面适配器(绑定本包运行时的 loadRemote;选项与返回值类型见 vue-adapter) */
|
|
8
|
+
declare function createHostPages(options: Parameters<typeof createHostPages$1>[0]): HostPages;
|
|
16
9
|
|
|
17
|
-
|
|
18
|
-
/** 加载期间展示的组件 */
|
|
19
|
-
loadingComponent?: Component;
|
|
20
|
-
/** 加载失败展示的组件(收到 error prop);不传时用内置默认错误占位 */
|
|
21
|
-
errorComponent?: Component;
|
|
22
|
-
/** 单次调用级重试覆盖,透传 loadRemote(缺省用远程注册值,默认 2) */
|
|
23
|
-
retries?: number;
|
|
24
|
-
/** defineAsyncComponent delay(ms):切换到 loadingComponent 前的等待,默认 200 */
|
|
25
|
-
delay?: number;
|
|
26
|
-
/** defineAsyncComponent timeout(ms):超时进错误态;不设则由 runtime 容器超时兜底 */
|
|
27
|
-
timeout?: number;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* 创建远程组件:`remoteComponent('demo-host/FormRouterPage')`。
|
|
31
|
-
* 返回标准 Vue 异步组件,使用处直接透传 props(如 form-params)。
|
|
32
|
-
*/
|
|
33
|
-
declare function remoteComponent(spec: string, opts?: RemoteComponentOptions): Component;
|
|
34
|
-
|
|
35
|
-
export { type RemoteComponentOptions, remoteComponent };
|
|
10
|
+
export { HostPages, RemoteComponentOptions, createHostPages, remoteComponent };
|