@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/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 remoteComponent(spec, opts = {}) {
887
- return (0, import_vue.defineAsyncComponent)({
888
- loader: () => loadRemote(spec, { retries: opts.retries }).then((m) => m.default ?? m),
889
- loadingComponent: opts.loadingComponent,
890
- errorComponent: opts.errorComponent ?? RemoteErrorPlaceholder,
891
- delay: opts.delay,
892
- timeout: opts.timeout
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 { Component } from 'vue';
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
- * Vue 生态层:remoteComponent() 远程组件直渲染。
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
- interface RemoteComponentOptions {
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 { Component } from 'vue';
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
- * Vue 生态层:remoteComponent() 远程组件直渲染。
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
- interface RemoteComponentOptions {
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 };