@fulgurjs/federation 4.0.0 → 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.
@@ -0,0 +1,144 @@
1
+ import {
2
+ definePages
3
+ } from "./chunk-TSXZ7TWA.js";
4
+
5
+ // src/vue-adapter.ts
6
+ import { defineAsyncComponent, defineComponent, h } from "vue";
7
+ var ERROR_STYLE = {
8
+ padding: "16px",
9
+ border: "1px solid #fde2e2",
10
+ borderRadius: "4px",
11
+ background: "#fef0f0",
12
+ color: "#c45656",
13
+ fontSize: "13px",
14
+ lineHeight: "1.6"
15
+ };
16
+ var RemoteErrorPlaceholder = defineComponent({
17
+ name: "FulgurjsRemoteError",
18
+ props: { error: { type: Object, default: void 0 } },
19
+ setup(props) {
20
+ return () => {
21
+ const err = props.error;
22
+ const code = err?.code ?? "UNKNOWN";
23
+ const message = err?.message ?? String(props.error ?? "unknown error");
24
+ return h("div", { style: ERROR_STYLE }, [
25
+ h("p", { style: "margin:0 0 4px;font-weight:600" }, `\u8FDC\u7A0B\u7EC4\u4EF6\u52A0\u8F7D\u5931\u8D25\uFF08\u9519\u8BEF\u7801 ${code}\uFF09`),
26
+ h("p", { style: "margin:0 0 8px;word-break:break-all" }, message),
27
+ 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")
28
+ ]);
29
+ };
30
+ }
31
+ });
32
+ function createRemoteComponent(loadRemote) {
33
+ return function remoteComponent(spec, opts = {}) {
34
+ return defineAsyncComponent({
35
+ loader: () => loadRemote(spec, { retries: opts.retries }).then((m) => m.default ?? m),
36
+ loadingComponent: opts.loadingComponent,
37
+ errorComponent: opts.errorComponent ?? RemoteErrorPlaceholder,
38
+ delay: opts.delay,
39
+ timeout: opts.timeout
40
+ });
41
+ };
42
+ }
43
+ var cleanCompName = (spec) => "Fulgurjs_" + spec.replace(/[^A-Za-z0-9_-]/g, "_");
44
+ function defaultDeriveSpec(route) {
45
+ return route.split("/").filter(Boolean).slice(1).filter((seg) => !seg.startsWith(":")).join("/");
46
+ }
47
+ function createHostPages(options, load) {
48
+ const { pages, remotePrefixes, deriveSpec, schema, strict, base, beforeLoad, loadingComponent, errorComponent, delay } = options;
49
+ definePages(pages, { deriveSpec, remotes: remotePrefixes, schema, strict });
50
+ const prefixes = Object.entries(remotePrefixes).map(([p, name]) => [p.endsWith("/") ? p : `${p}/`, name]).sort((a, b) => b[0].length - a[0].length);
51
+ const remoteOf = (route) => {
52
+ for (const [prefix, name] of prefixes) {
53
+ if (route.startsWith(prefix) || `${route}/` === prefix) return name;
54
+ }
55
+ throw new Error(
56
+ `[fulgurjs] page route "${route}" matches none of remotePrefixes { ${Object.entries(remotePrefixes).map(([k, v]) => `'${k}': '${v}'`).join(", ")} }
57
+ \u6839\u56E0: \u9875\u9762\u8868\u6761\u76EE\u7684\u8DEF\u7531\u4E0D\u5728\u4EFB\u4F55\u5DF2\u58F0\u660E\u7684\u8FDC\u7A0B\u524D\u7F00\u4E0B\u3002
58
+ \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`
59
+ );
60
+ };
61
+ for (const page of pages) remoteOf(page.route);
62
+ const specOf = (page) => {
63
+ const s = page.spec ?? (deriveSpec ? deriveSpec(page.route) : defaultDeriveSpec(page.route));
64
+ return `${remoteOf(page.route)}/${String(s).replace(/^[./]+/, "")}`;
65
+ };
66
+ const baseNorm = base && base !== "/" ? base.endsWith("/") ? base : `${base}/` : "";
67
+ const matchSegments = (pattern, path) => {
68
+ const patSegs = pattern.split("/").filter(Boolean);
69
+ const pathSegs = path.split("/").filter(Boolean);
70
+ if (patSegs.length !== pathSegs.length) return null;
71
+ const params = {};
72
+ for (let i = 0; i < patSegs.length; i++) {
73
+ const seg = patSegs[i];
74
+ if (seg.startsWith(":")) {
75
+ try {
76
+ params[seg.slice(1)] = decodeURIComponent(pathSegs[i]);
77
+ } catch {
78
+ return null;
79
+ }
80
+ } else if (seg !== pathSegs[i]) {
81
+ return null;
82
+ }
83
+ }
84
+ return params;
85
+ };
86
+ let cacheSession;
87
+ const compCache = /* @__PURE__ */ new Map();
88
+ const resolve = (rawPath) => {
89
+ let clean = String(rawPath ?? "");
90
+ if (baseNorm) {
91
+ if (clean === baseNorm.replace(/\/$/, "")) clean = "/";
92
+ else if (clean.startsWith(baseNorm)) clean = `/${clean.slice(baseNorm.length)}`;
93
+ }
94
+ clean = (clean.replace(/\/+$/, "") || "/").split("?")[0].split("#")[0];
95
+ for (const page of pages) {
96
+ const params = matchSegments(page.route, clean);
97
+ if (params) return { page, remote: remoteOf(page.route), spec: specOf(page), params };
98
+ }
99
+ return null;
100
+ };
101
+ const component = (spec) => {
102
+ const sk = (globalThis.__FULGURJS_APP_CONFIG__ ?? {}).sessionKey;
103
+ const sessionKey = typeof sk === "string" ? sk : void 0;
104
+ if (sessionKey !== cacheSession) {
105
+ compCache.clear();
106
+ cacheSession = sessionKey;
107
+ }
108
+ let comp = compCache.get(spec);
109
+ if (!comp) {
110
+ const name = cleanCompName(spec);
111
+ const asyncComp = defineAsyncComponent({
112
+ loader: async () => {
113
+ await beforeLoad?.();
114
+ const mod = await load(spec);
115
+ const inner = mod?.default ?? mod;
116
+ if (!inner) {
117
+ throw new Error(
118
+ `[fulgurjs] remote module "${spec}" did not export a component (got ${inner === null ? "null" : typeof inner})\u3002
119
+ \u4FEE\u6CD5: \u6838\u5BF9\u8FDC\u7A0B exposes \u952E\u540D\u4E0E\u9875\u9762\u8868 spec \u662F\u5426\u4E00\u81F4\uFF08MFU-006 \u8BED\u4E49\uFF09`
120
+ );
121
+ }
122
+ return inner;
123
+ },
124
+ loadingComponent,
125
+ errorComponent: errorComponent ?? RemoteErrorPlaceholder,
126
+ delay
127
+ });
128
+ comp = defineComponent({
129
+ name,
130
+ inheritAttrs: false,
131
+ setup: (_props, ctx) => () => h(asyncComp, ctx.attrs, ctx.slots)
132
+ });
133
+ compCache.set(spec, comp);
134
+ }
135
+ return comp;
136
+ };
137
+ const keepAliveNames = pages.filter((p) => p.keepAlive).map((p) => cleanCompName(specOf(p)));
138
+ return { pages, resolve, component, keepAliveNames };
139
+ }
140
+
141
+ export {
142
+ createRemoteComponent,
143
+ createHostPages
144
+ };
@@ -0,0 +1,144 @@
1
+ // src/pages.ts
2
+ var normSpec = (s) => s.replace(/^[./]+/, "").replace(/\/+$/, "");
3
+ var hasParam = (route) => /(^|\/):[\w]+/.test(route);
4
+ var segs = (route) => route.split("/").filter(Boolean);
5
+ function defaultDeriveSpec(route) {
6
+ return segs(route).slice(1).filter((seg) => !seg.startsWith(":")).join("/");
7
+ }
8
+ function shadows(earlier, later) {
9
+ const a = segs(earlier);
10
+ const b = segs(later);
11
+ if (a.length !== b.length) return null;
12
+ if (earlier === later) return "full";
13
+ let hasParam2 = false;
14
+ for (let i = 0; i < a.length; i++) {
15
+ if (a[i] === b[i]) continue;
16
+ if (a[i].startsWith(":")) {
17
+ hasParam2 = true;
18
+ continue;
19
+ }
20
+ return null;
21
+ }
22
+ return hasParam2 ? "param" : null;
23
+ }
24
+ function remoteOfRoute(route, remotes) {
25
+ if (!remotes) return null;
26
+ for (const [prefix, name] of Object.entries(remotes)) {
27
+ if (route === prefix.replace(/\/+$/, "") || route.startsWith(prefix)) return name;
28
+ }
29
+ return null;
30
+ }
31
+ function validatePages(pages, options = {}) {
32
+ const deriveSpec = options.deriveSpec ?? defaultDeriveSpec;
33
+ const violations = [];
34
+ const effective = pages.map((p) => normSpec(p.spec ?? deriveSpec(p.route)));
35
+ pages.forEach((p, i) => {
36
+ if (p.spec || !hasParam(p.route)) return;
37
+ pages.forEach((q, j) => {
38
+ if (i === j) return;
39
+ if (effective[j] !== effective[i]) return;
40
+ violations.push({
41
+ rule: "R1",
42
+ level: "error",
43
+ 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
44
+ \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
45
+ \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
46
+ \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`
47
+ });
48
+ });
49
+ });
50
+ const seenSpec = /* @__PURE__ */ new Map();
51
+ pages.forEach((p, i) => {
52
+ const key = effective[i];
53
+ if (seenSpec.has(key)) {
54
+ const first = pages[seenSpec.get(key)];
55
+ const bothExplicit = !!p.spec && !!first.spec;
56
+ violations.push({
57
+ rule: "R2",
58
+ level: "warn",
59
+ 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`
60
+ });
61
+ } else {
62
+ seenSpec.set(key, i);
63
+ }
64
+ });
65
+ if (options.schema && options.remotes) {
66
+ pages.forEach((p, i) => {
67
+ const remote = remoteOfRoute(p.route, options.remotes);
68
+ if (!remote) return;
69
+ const entry = options.schema[remote];
70
+ if (!entry || entry.exists === false) return;
71
+ const exposes = new Set((entry.exposes ?? []).map(normSpec));
72
+ if (!exposes.has(effective[i])) {
73
+ violations.push({
74
+ rule: "R3",
75
+ level: "error",
76
+ message: `[R3] \u8DEF\u7531 "${p.route}" \u7684 spec "${effective[i]}" \u4E0D\u5728\u8FDC\u7A0B "${remote}" \u7684 exposes \u6E05\u5355\u4E2D\u3002
77
+ \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
78
+ \u4FEE\u6CD5\uFF1A\u6838\u5BF9\u8FDC\u7A0B vite.config exposes \u952E\u540D\uFF0C\u6216\u4FEE\u6B63\u672C\u8868 spec\u3002
79
+ \u8FDC\u7A0B\u5B9E\u9645 exposes\uFF08\u524D 10\uFF09\uFF1A${[...exposes].slice(0, 10).join("\u3001") || "\uFF08\u7A7A\uFF09"}`
80
+ });
81
+ }
82
+ });
83
+ }
84
+ pages.forEach((p, i) => {
85
+ pages.forEach((q, j) => {
86
+ if (j <= i) return;
87
+ const kind = shadows(p.route, q.route);
88
+ if (!kind) return;
89
+ violations.push({
90
+ rule: "R4",
91
+ level: "error",
92
+ 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
93
+ \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`
94
+ });
95
+ });
96
+ });
97
+ const seenName = /* @__PURE__ */ new Map();
98
+ pages.forEach((p, i) => {
99
+ const name = typeof p.name === "string" ? p.name : "";
100
+ if (!name) return;
101
+ if (seenName.has(name)) {
102
+ violations.push({
103
+ rule: "R5",
104
+ level: "warn",
105
+ 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`
106
+ });
107
+ } else {
108
+ seenName.set(name, i);
109
+ }
110
+ });
111
+ return violations;
112
+ }
113
+ function formatViolations(violations) {
114
+ const errors = violations.filter((v) => v.level === "error");
115
+ const warns = violations.filter((v) => v.level === "warn");
116
+ let out = "";
117
+ if (errors.length) {
118
+ out += `[fulgurjs] \u8DEF\u7531\u8868\u6821\u9A8C\u5931\u8D25\uFF08${errors.length} \u9879\uFF0CERROR\uFF09\uFF1A
119
+ ` + errors.map((v) => v.message).join("\n") + "\n";
120
+ }
121
+ if (warns.length) {
122
+ out += `[fulgurjs] \u8DEF\u7531\u8868\u6821\u9A8C\u8B66\u544A\uFF08${warns.length} \u9879\uFF0CWARN\uFF09\uFF1A
123
+ ` + warns.map((v) => v.message).join("\n");
124
+ }
125
+ return out.trim();
126
+ }
127
+ function definePages(pages, options = {}) {
128
+ const violations = validatePages(pages, options);
129
+ const errors = violations.filter((v) => v.level === "error");
130
+ const warns = violations.filter((v) => v.level === "warn");
131
+ const text = formatViolations(violations);
132
+ if (text) {
133
+ if (errors.length && options.strict !== false) {
134
+ throw new Error(text);
135
+ }
136
+ console[errors.length ? "error" : "warn"](text);
137
+ }
138
+ return pages;
139
+ }
140
+
141
+ export {
142
+ validatePages,
143
+ definePages
144
+ };
@@ -29,7 +29,15 @@ var ErrorCodes = {
29
29
  /** 加载到的模块没有任何导出(常见:exposes 指向了不导出内容的文件) */
30
30
  EMPTY_EXPORTS: "MFU-009",
31
31
  /** singleton 共享协商版本与消费方 requiredVersion 不一致(使用作用域版本,仅告警) */
32
- SINGLETON_SKEW: "MFU-010"
32
+ SINGLETON_SKEW: "MFU-010",
33
+ /** setup 生命周期入口导出形态非法(默认导出不是函数,或具名 onSession 不是函数) */
34
+ SETUP_INVALID_EXPORT: "MFU-011",
35
+ /** setup/onSession 执行抛错(该次 loadRemote 拒绝;仅清失败阶段缓存,允许重试) */
36
+ SETUP_RUN_FAILED: "MFU-012",
37
+ /** 远程声明了 onSession 但宿主 AppContext 未提供 sessionKey(登录代次) */
38
+ SETUP_SESSION_KEY_MISSING: "MFU-013",
39
+ /** setup/onSession 执行的同步段内递归 loadRemote 同一远程 */
40
+ SETUP_RECURSION: "MFU-014"
33
41
  };
34
42
 
35
43
  export {