@fulgurjs/federation 0.4.2

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/pages.cjs ADDED
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/pages.ts
21
+ var pages_exports = {};
22
+ __export(pages_exports, {
23
+ defineFulgurPages: () => defineFulgurPages,
24
+ validateFulgurPages: () => validateFulgurPages
25
+ });
26
+ module.exports = __toCommonJS(pages_exports);
27
+ var normSpec = (s) => s.replace(/^[./]+/, "").replace(/\/+$/, "");
28
+ var hasParam = (route) => /(^|\/):[\w]+/.test(route);
29
+ var segs = (route) => route.split("/").filter(Boolean);
30
+ function defaultDeriveSpec(route) {
31
+ return segs(route).slice(1).filter((seg) => !seg.startsWith(":")).join("/");
32
+ }
33
+ function shadows(earlier, later) {
34
+ const a = segs(earlier);
35
+ const b = segs(later);
36
+ if (a.length !== b.length) return null;
37
+ if (earlier === later) return "full";
38
+ let hasParam2 = false;
39
+ for (let i = 0; i < a.length; i++) {
40
+ if (a[i] === b[i]) continue;
41
+ if (a[i].startsWith(":")) {
42
+ hasParam2 = true;
43
+ continue;
44
+ }
45
+ return null;
46
+ }
47
+ return hasParam2 ? "param" : null;
48
+ }
49
+ function remoteOfRoute(route, remotes) {
50
+ if (!remotes) return null;
51
+ for (const [prefix, name] of Object.entries(remotes)) {
52
+ if (route === prefix.replace(/\/+$/, "") || route.startsWith(prefix)) return name;
53
+ }
54
+ return null;
55
+ }
56
+ function validateFulgurPages(pages, options = {}) {
57
+ const deriveSpec = options.deriveSpec ?? defaultDeriveSpec;
58
+ const violations = [];
59
+ const effective = pages.map((p) => normSpec(p.spec ?? deriveSpec(p.route)));
60
+ pages.forEach((p, i) => {
61
+ if (p.spec || !hasParam(p.route)) return;
62
+ pages.forEach((q, j) => {
63
+ if (i === j) return;
64
+ if (effective[j] !== effective[i]) return;
65
+ violations.push({
66
+ rule: "R1",
67
+ level: "error",
68
+ 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
69
+ \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
70
+ \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
71
+ \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`
72
+ });
73
+ });
74
+ });
75
+ const seenSpec = /* @__PURE__ */ new Map();
76
+ pages.forEach((p, i) => {
77
+ const key = effective[i];
78
+ if (seenSpec.has(key)) {
79
+ const first = pages[seenSpec.get(key)];
80
+ const bothExplicit = !!p.spec && !!first.spec;
81
+ violations.push({
82
+ rule: "R2",
83
+ level: "warn",
84
+ 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`
85
+ });
86
+ } else {
87
+ seenSpec.set(key, i);
88
+ }
89
+ });
90
+ if (options.schema && options.remotes) {
91
+ pages.forEach((p, i) => {
92
+ const remote = remoteOfRoute(p.route, options.remotes);
93
+ if (!remote) return;
94
+ const entry = options.schema[remote];
95
+ if (!entry || entry.exists === false) return;
96
+ const exposes = new Set((entry.exposes ?? []).map(normSpec));
97
+ if (!exposes.has(effective[i])) {
98
+ violations.push({
99
+ rule: "R3",
100
+ level: "error",
101
+ message: `[R3] \u8DEF\u7531 "${p.route}" \u7684 spec "${effective[i]}" \u4E0D\u5728\u8FDC\u7A0B "${remote}" \u7684 exposes \u6E05\u5355\u4E2D\u3002
102
+ \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
103
+ \u4FEE\u6CD5\uFF1A\u6838\u5BF9\u8FDC\u7A0B vite.config exposes \u952E\u540D\uFF0C\u6216\u4FEE\u6B63\u672C\u8868 spec\u3002
104
+ \u8FDC\u7A0B\u5B9E\u9645 exposes\uFF08\u524D 10\uFF09\uFF1A${[...exposes].slice(0, 10).join("\u3001") || "\uFF08\u7A7A\uFF09"}`
105
+ });
106
+ }
107
+ });
108
+ }
109
+ pages.forEach((p, i) => {
110
+ pages.forEach((q, j) => {
111
+ if (j <= i) return;
112
+ const kind = shadows(p.route, q.route);
113
+ if (!kind) return;
114
+ violations.push({
115
+ rule: "R4",
116
+ level: "error",
117
+ 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
118
+ \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`
119
+ });
120
+ });
121
+ });
122
+ const seenName = /* @__PURE__ */ new Map();
123
+ pages.forEach((p, i) => {
124
+ const name = typeof p.name === "string" ? p.name : "";
125
+ if (!name) return;
126
+ if (seenName.has(name)) {
127
+ violations.push({
128
+ rule: "R5",
129
+ level: "warn",
130
+ 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`
131
+ });
132
+ } else {
133
+ seenName.set(name, i);
134
+ }
135
+ });
136
+ return violations;
137
+ }
138
+ function formatViolations(violations) {
139
+ const errors = violations.filter((v) => v.level === "error");
140
+ const warns = violations.filter((v) => v.level === "warn");
141
+ let out = "";
142
+ if (errors.length) {
143
+ out += `[fulgur] \u8DEF\u7531\u8868\u6821\u9A8C\u5931\u8D25\uFF08${errors.length} \u9879\uFF0CERROR\uFF09\uFF1A
144
+ ` + errors.map((v) => v.message).join("\n") + "\n";
145
+ }
146
+ if (warns.length) {
147
+ out += `[fulgur] \u8DEF\u7531\u8868\u6821\u9A8C\u8B66\u544A\uFF08${warns.length} \u9879\uFF0CWARN\uFF09\uFF1A
148
+ ` + warns.map((v) => v.message).join("\n");
149
+ }
150
+ return out.trim();
151
+ }
152
+ function defineFulgurPages(pages, options = {}) {
153
+ const violations = validateFulgurPages(pages, options);
154
+ const errors = violations.filter((v) => v.level === "error");
155
+ const warns = violations.filter((v) => v.level === "warn");
156
+ const text = formatViolations(violations);
157
+ if (text) {
158
+ if (errors.length && options.strict !== false) {
159
+ throw new Error(text);
160
+ }
161
+ console[errors.length ? "error" : "warn"](text);
162
+ }
163
+ return pages;
164
+ }
165
+ // Annotate the CommonJS export names for ESM import in node:
166
+ 0 && (module.exports = {
167
+ defineFulgurPages,
168
+ validateFulgurPages
169
+ });
@@ -0,0 +1,66 @@
1
+ /**
2
+ * D.2 宿主路由表校验器(defineFulgurPages)
3
+ *
4
+ * 背景:带参路由缺省推导 spec 时会剥离 :参数 段,可能与其它条目(如列表页)的
5
+ * expose 键收敛相同 → 静默加载错误组件(真实事故:复制流程误走更新语义)。
6
+ *
7
+ * 定位:纯同步纯函数,dev(宿主浏览器模块求值)与 build(node)行为一致;
8
+ * 只校验不改写入参;ERROR 级聚合一次 throw(strict:false 降级 console.error),
9
+ * WARN 级聚合 console.warn。
10
+ *
11
+ * 规则:
12
+ * - R1 [ERROR] 带参路由(无显式 spec)的推导 spec 与任一其它条目的有效 spec 相同
13
+ * - R2 [WARN] 多条目的有效 spec 完全相同(可能是刻意的菜单别名,要求有意识)
14
+ * - R3 [ERROR] schema 提供时:有效 spec 的 exposes 键不在对应 remote 清单中
15
+ * (把运行时 MFU-006 提前到启动期;schema 缺失/remote 不可达则跳过)
16
+ * - R4 [ERROR] 静态路由被数组中更靠前的带参路由遮蔽(先到先得 → 静态路由成死路由);
17
+ * 含路由完全重复
18
+ * - R5 [WARN] name 字段重复(vue-router 命名跳转歧义)
19
+ */
20
+ interface FulgurPageRouteLike {
21
+ /** 宿主路由路径(:param 段) */
22
+ route: string;
23
+ /** 显式 spec(缺省走推导) */
24
+ spec?: string;
25
+ /** 其余字段(name/title/...)由宿主自由扩展,校验器按需读取 */
26
+ name?: string;
27
+ [key: string]: unknown;
28
+ }
29
+ interface RemoteSchemaEntry {
30
+ /** 该 remote 的 exposes 键清单(与 spec 同口径,剥 ./ 前缀比较) */
31
+ exposes: string[];
32
+ /** manifest 是否成功拉取(false 时跳过该 remote 的 R3,诚实降级) */
33
+ exists?: boolean;
34
+ }
35
+ interface FulgurPagesOptions {
36
+ /**
37
+ * 带参路由的缺省 spec 推导规则(宿主私有约定,如
38
+ * (route) => `pages/${route 去前缀去 :参}`)。
39
+ * 缺省 = 去掉首段(远程前缀)+ 剥 :参 段(无 pages/ 前缀)——
40
+ * 推导只需在全部条目间自洽即可保持冲突判定成立。
41
+ */
42
+ deriveSpec?: (route: string) => string;
43
+ /** 路由前缀 → remote 名(R3 用:{'/remote-a/': 'remote-a'});缺省跳过 R3 */
44
+ remotes?: Record<string, string>;
45
+ /** remote exposes 清单(dev 由 virtual:fulgur-remote-schema 提供);缺省跳过 R3 */
46
+ schema?: Record<string, RemoteSchemaEntry>;
47
+ /** false = ERROR 降级 console.error(默认 true = throw) */
48
+ strict?: boolean;
49
+ }
50
+ interface PageViolation {
51
+ rule: 'R1' | 'R2' | 'R3' | 'R4' | 'R5';
52
+ level: 'error' | 'warn';
53
+ message: string;
54
+ }
55
+ /**
56
+ * 校验路由表(独立导出便于单测):返回违例清单(不抛错)。
57
+ */
58
+ declare function validateFulgurPages(pages: FulgurPageRouteLike[], options?: FulgurPagesOptions): PageViolation[];
59
+ /**
60
+ * 宿主路由表定义入口:校验(聚合报错)后原样返回入参,不改写任何字段。
61
+ * 违反 ERROR 级规则时默认 throw(dev overlay / build 失败直接可见);
62
+ * strict: false 时降级 console.error。WARN 级始终 console.warn。
63
+ */
64
+ declare function defineFulgurPages<P extends FulgurPageRouteLike[]>(pages: P, options?: FulgurPagesOptions): P;
65
+
66
+ export { type FulgurPageRouteLike, type FulgurPagesOptions, type PageViolation, type RemoteSchemaEntry, defineFulgurPages, validateFulgurPages };
@@ -0,0 +1,66 @@
1
+ /**
2
+ * D.2 宿主路由表校验器(defineFulgurPages)
3
+ *
4
+ * 背景:带参路由缺省推导 spec 时会剥离 :参数 段,可能与其它条目(如列表页)的
5
+ * expose 键收敛相同 → 静默加载错误组件(真实事故:复制流程误走更新语义)。
6
+ *
7
+ * 定位:纯同步纯函数,dev(宿主浏览器模块求值)与 build(node)行为一致;
8
+ * 只校验不改写入参;ERROR 级聚合一次 throw(strict:false 降级 console.error),
9
+ * WARN 级聚合 console.warn。
10
+ *
11
+ * 规则:
12
+ * - R1 [ERROR] 带参路由(无显式 spec)的推导 spec 与任一其它条目的有效 spec 相同
13
+ * - R2 [WARN] 多条目的有效 spec 完全相同(可能是刻意的菜单别名,要求有意识)
14
+ * - R3 [ERROR] schema 提供时:有效 spec 的 exposes 键不在对应 remote 清单中
15
+ * (把运行时 MFU-006 提前到启动期;schema 缺失/remote 不可达则跳过)
16
+ * - R4 [ERROR] 静态路由被数组中更靠前的带参路由遮蔽(先到先得 → 静态路由成死路由);
17
+ * 含路由完全重复
18
+ * - R5 [WARN] name 字段重复(vue-router 命名跳转歧义)
19
+ */
20
+ interface FulgurPageRouteLike {
21
+ /** 宿主路由路径(:param 段) */
22
+ route: string;
23
+ /** 显式 spec(缺省走推导) */
24
+ spec?: string;
25
+ /** 其余字段(name/title/...)由宿主自由扩展,校验器按需读取 */
26
+ name?: string;
27
+ [key: string]: unknown;
28
+ }
29
+ interface RemoteSchemaEntry {
30
+ /** 该 remote 的 exposes 键清单(与 spec 同口径,剥 ./ 前缀比较) */
31
+ exposes: string[];
32
+ /** manifest 是否成功拉取(false 时跳过该 remote 的 R3,诚实降级) */
33
+ exists?: boolean;
34
+ }
35
+ interface FulgurPagesOptions {
36
+ /**
37
+ * 带参路由的缺省 spec 推导规则(宿主私有约定,如
38
+ * (route) => `pages/${route 去前缀去 :参}`)。
39
+ * 缺省 = 去掉首段(远程前缀)+ 剥 :参 段(无 pages/ 前缀)——
40
+ * 推导只需在全部条目间自洽即可保持冲突判定成立。
41
+ */
42
+ deriveSpec?: (route: string) => string;
43
+ /** 路由前缀 → remote 名(R3 用:{'/remote-a/': 'remote-a'});缺省跳过 R3 */
44
+ remotes?: Record<string, string>;
45
+ /** remote exposes 清单(dev 由 virtual:fulgur-remote-schema 提供);缺省跳过 R3 */
46
+ schema?: Record<string, RemoteSchemaEntry>;
47
+ /** false = ERROR 降级 console.error(默认 true = throw) */
48
+ strict?: boolean;
49
+ }
50
+ interface PageViolation {
51
+ rule: 'R1' | 'R2' | 'R3' | 'R4' | 'R5';
52
+ level: 'error' | 'warn';
53
+ message: string;
54
+ }
55
+ /**
56
+ * 校验路由表(独立导出便于单测):返回违例清单(不抛错)。
57
+ */
58
+ declare function validateFulgurPages(pages: FulgurPageRouteLike[], options?: FulgurPagesOptions): PageViolation[];
59
+ /**
60
+ * 宿主路由表定义入口:校验(聚合报错)后原样返回入参,不改写任何字段。
61
+ * 违反 ERROR 级规则时默认 throw(dev overlay / build 失败直接可见);
62
+ * strict: false 时降级 console.error。WARN 级始终 console.warn。
63
+ */
64
+ declare function defineFulgurPages<P extends FulgurPageRouteLike[]>(pages: P, options?: FulgurPagesOptions): P;
65
+
66
+ export { type FulgurPageRouteLike, type FulgurPagesOptions, type PageViolation, type RemoteSchemaEntry, defineFulgurPages, validateFulgurPages };
package/dist/pages.js ADDED
@@ -0,0 +1,143 @@
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 validateFulgurPages(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 += `[fulgur] \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 += `[fulgur] \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 defineFulgurPages(pages, options = {}) {
128
+ const violations = validateFulgurPages(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
+ export {
141
+ defineFulgurPages,
142
+ validateFulgurPages
143
+ };
@@ -0,0 +1,3 @@
1
+ var Q=/^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-((?:[0-9A-Za-z-]+)(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z.-]+)?$/;function k(t){let n=Q.exec(String(t).trim());return n?{major:Number(n[1]),minor:Number(n[2]??0),patch:Number(n[3]??0),pre:n[4]?n[4].split("."):[]}:null}function ee(t,n){if(t.length===0&&n.length===0)return 0;if(t.length===0)return 1;if(n.length===0)return-1;let o=Math.max(t.length,n.length);for(let r=0;r<o;r++){let l=t[r],m=n[r];if(l===void 0)return-1;if(m===void 0)return 1;let y=/^\d+$/.test(l),w=/^\d+$/.test(m);if(y&&w){let M=Number(l)-Number(m);if(M!==0)return M}else{if(y!==w)return y?-1:1;if(l!==m)return l<m?-1:1}}return 0}function T(t,n){let o=k(t),r=k(n);return!o||!r?t<n?-1:t>n?1:0:o.major!==r.major?o.major-r.major:o.minor!==r.minor?o.minor-r.minor:o.patch!==r.patch?o.patch-r.patch:ee(o.pre,r.pre)}var re=/^[a-z]+[+:-]/i;function N(t,n){if(n===!1||n==null||n==="")return!0;let o=String(n).trim();return!o||o==="*"||o==="latest"||re.test(o)?!0:o.split("||").some(r=>te(t,r.trim()))}function te(t,n){if(!n||n==="*")return!k(t)?.pre.length;let o=n.split(/\s+-\s+/);if(o.length===2){let m=o[0].trim(),y=o[1].trim();return!(!N(t,`>=${m}`)||!N(t,`<=${y}`))}let r=k(t);if(!r)return!1;let l=ne(n);if(l===null)return!0;for(let m of l)if(!ue(r,t,m))return!1;return!(r.pre.length>0&&!l.some(m=>m.ver.pre.length>0)&&!l.some(y=>y.ver.major===r.major&&y.ver.minor===r.minor&&y.ver.patch===r.patch))}function ne(t){let n=[],o=t.trim().split(/\s+/).filter(Boolean);if(o.length===0)return null;for(let r of o){let l=/^(>=|<=|>|<|=|\^|~)?(.*)$/.exec(r);if(!l)return null;let m=l[1],y=l[2],w=oe(m,y);if(w===null)return null;n.push(...w)}return n}function oe(t,n){let o=ae(n);if(!o)return null;let{ver:r,partial:l}=o;switch(t){case"^":{let m=ie(r);return m?[{op:">=",ver:r},{op:"<",ver:m}]:null}case"~":{let m=l>=2?{major:r.major,minor:r.minor+1,patch:0,pre:[]}:{major:r.major+1,minor:0,patch:0,pre:[]};return[{op:">=",ver:r},{op:"<",ver:m}]}case">=":return[{op:">=",ver:r}];case">":return l===1?[{op:">=",ver:{major:r.major+1,minor:0,patch:0,pre:[]}}]:l===2?[{op:">=",ver:{major:r.major,minor:r.minor+1,patch:0,pre:[]}}]:[{op:">",ver:r}];case"<":return l===1?[{op:"<",ver:{major:r.major,minor:0,patch:0,pre:[]}}]:l===2?[{op:"<",ver:{major:r.major,minor:r.minor,patch:0,pre:[]}}]:[{op:"<",ver:r}];case"<=":return[{op:"<=",ver:r}];case"=":case void 0:return l===1?[{op:">=",ver:r},{op:"<",ver:{major:r.major+1,minor:0,patch:0,pre:[]}}]:l===2?[{op:">=",ver:r},{op:"<",ver:{major:r.major,minor:r.minor+1,patch:0,pre:[]}}]:[{op:"=",ver:r}];default:return null}}function ie(t){return t.major>0?{major:t.major+1,minor:0,patch:0,pre:[]}:t.minor>0?{major:0,minor:t.minor+1,patch:0,pre:[]}:{major:0,minor:0,patch:t.patch+1,pre:[]}}function ae(t){let n=t.replace(/^v/,"").trim();if(!n||n==="*"||n==="x"||n==="X")return{ver:{major:0,minor:0,patch:0,pre:[]},partial:0};let o=n.split("."),r=[],l=0;for(let w of o.slice(0,3))if(/^\d+$/.test(w))r.push(Number(w)),l++;else{if(w==="x"||w==="X"||w==="*")break;break}let m=n.split("-"),y=m.length>1?m.slice(1).join("-").split("+")[0].split("."):[];return l===0&&y.length===0?{ver:{major:0,minor:0,patch:0,pre:[]},partial:0}:{ver:{major:r[0]??0,minor:r[1]??0,patch:r[2]??0,pre:y},partial:l}}function se(t,n){switch(n){case">=":return t>=0;case">":return t>0;case"<":return t<0;case"<=":return t<=0;case"=":return t===0}}function ue(t,n,o){let r=`${t.major}.${t.minor}.${t.patch}`+(t.pre.length?`-${t.pre.join(".")}`:"");if(o.upper)return!(!(T(r,`${o.ver.major}.${o.ver.minor}.${o.ver.patch}`+(o.ver.pre.length?`-${o.ver.pre.join(".")}`:""))>=0)||!(T(r,`${o.upper.major}.${o.upper.minor}.${o.upper.patch}`)<0));let l=T(r,`${o.ver.major}.${o.ver.minor}.${o.ver.patch}`+(o.ver.pre.length?`-${o.ver.pre.join(".")}`:"")),m=se(l,o.op);return o.ver.pre.length>0&&t.pre.length===0&&(o.op===">"||o.op==="<")?!1:m}var g=class extends Error{code;details;constructor(n,o,r){super(`[fulgur:${n}] ${o}`),this.name="FulgurError",this.code=n,this.details=r}},R={REMOTE_LOAD_FAILED:"MFU-001",REMOTE_NAME_MISMATCH:"MFU-002",SHARE_STRICT_VERSION:"MFU-003",SHARE_NOT_AVAILABLE:"MFU-004",CONTAINER_REINIT_CONFLICT:"MFU-005",MODULE_NOT_EXPOSED:"MFU-006",PRELOAD_FAILED:"MFU-007",REMOTE_UNKNOWN:"MFU-008",EMPTY_EXPORTS:"MFU-009",SINGLETON_SKEW:"MFU-010"};var F="0.4.2";var U=15e3,P=2,ce=5,le=3e4;function fe(){let t={},n=new Map,o=[],r=new Map,l=new WeakMap,m={};function y(e){m={...m,...e},typeof globalThis<"u"&&(globalThis.__FULGUR_APP_CONFIG__=m)}function w(){return m}let M={},D=()=>{for(let e of o)try{e.init?.(M)}catch(i){console.warn("[fulgur] runtimePlugin init failed:",e.name,i)}},I=e=>t[e]??={};function $(e){try{M.onRemoteError?.(e)}catch{}typeof window<"u"&&window.dispatchEvent(new CustomEvent("fulgur:error",{detail:e}))}function H(e="default"){return I(e),t}function K(e,i,a,u,E={}){let s=I(e||"default"),c=s[i]??={};c[a]||(c[a]={version:a,get:u,from:E.from??"unknown",eager:!!E.eager,loaded:!!E.loaded})}function x(e){for(let i of e){let a=n.get(i.name);n.set(i.name,{timeout:U,retries:P,...a,...i,state:a?.state??"idle",breakerState:a?.breakerState??{fails:0,openUntil:0},debug:a?.debug??{entry:i.entry,status:"idle"}})}}function q(e){x([e])}function G(e){o.push(...e),D()}async function z(e,i={}){let a=i.shareScope||"default",u=i.shareKey||e,s=I(a)[u]||{},c=Object.keys(s),d=i.requiredVersion,f=c.filter(p=>d===void 0||d===!1||N(p,d)),b;if(i.singleton&&c.length>0){let p=c.filter(h=>s[h].loaded);if(b=[...p.length?p:c].sort(T).pop(),c.length>1||!f.includes(b)){let h=s[b],O=`singleton skew "${u}": req "${d??"any"}" use ${b} from ${h.from}`;if(i.strictVersion){let v=new g(R.SHARE_STRICT_VERSION,O,{shareKey:u,requiredVersion:d});throw $({remote:h.from,error:v}),v}console.warn(`[fulgur:MFU-010] ${O}`)}}else if(b=[...f].sort(T).pop(),!b){if(i.strictVersion){let h=new g(R.SHARE_STRICT_VERSION,`no satisfying version for "${u}" (required "${d}") in share scope "${a}"`);throw $({remote:u,error:h}),h}if(i.fallback)return i.fallback();let p=new g(R.SHARE_NOT_AVAILABLE,`shared "${u}" (${d??"any"}) unavailable in scope "${a}" (no local fallback)`,{shareKey:u,requiredVersion:d});throw $({remote:u,error:p}),p}let _=s[b];if(M.resolveShare){let p=await M.resolveShare({shareKey:u,shareScope:a,requiredVersion:d,picked:_,available:Object.values(s)});p&&(_=p)}return _.loaded=!0,_.get()}function W(e,i,a){return new Promise((u,E)=>{let s=setTimeout(()=>E(new Error(`timeout after ${i}ms: ${a}`)),i);e.then(c=>{clearTimeout(s),u(c)},c=>{clearTimeout(s),E(c)})})}let B=e=>new Promise(i=>setTimeout(i,e));function j(e,i){if(!e||typeof e.get!="function")throw new Error(`remoteEntry of "${i.name}" does not export a container interface (get/init)`);if(e.name&&i.name&&e.name!==i.name){let a=`remoteEntry self-reported name "${e.name}" mismatches configured name "${i.name}"`;if(i.promise)console.warn(`[fulgur] ${a} (promise-based remote, continuing)`);else throw Object.assign(new Error(a),{code:R.REMOTE_NAME_MISMATCH})}}async function L(e,i){return W(import(e),i.timeout??U,`load remoteEntry ${e}`)}async function C(e,i){let a=e.shareScope||"default";if(e.container){let s=l.get(e.container);if(s&&s!==a)throw new g(R.CONTAINER_REINIT_CONFLICT,`container "${e.name}" already initialized with share scope "${s}", cannot re-init with "${a}"`);return e.container}if(e.containerPromise)return e.containerPromise;let u=e.breakerState;if(u.openUntil>Date.now()){let s=new g(R.REMOTE_LOAD_FAILED,`circuit breaker open for remote "${e.name}" until ${new Date(u.openUntil).toISOString()}`,{remote:e.name});throw $({remote:e.name,error:s}),s}e.state="loading",e.debug.status="loading";let E=(async()=>{let s=[];if(e.promise)s.push(async()=>{let p=await e.promise(),h=typeof p=="string"?await L(p,e):p;return j(h,e),h});else for(let p of[e.entry,...e.fallback??[]])s.push(async()=>{let h=await L(p,e);return j(h,e),h});let c,d,f=!1,b=i?.retries??e.retries??P;for(let p of s){for(let h=0;h<=b;h++){let O=performance.now();try{c=await p(),e.lastLoadMs=Math.round(performance.now()-O),e.debug.lastLoadMs=e.lastLoadMs,f=!0;break}catch(v){if(d=v,v?.code)throw v;h<b&&await B(200*2**h)}}if(f)break}if(!f||!c){let p=[];throw/^https?:\/\//.test(e.entry)&&typeof location<"u"?(p.push(`1. is the remote dev server running? try opening ${e.entry} in the browser \u2014 it must return JS, not HTML/an error`),p.push("2. is the URL correct in federation({ remotes })? dev and prod entries can differ"),p.push("3. CORS: the remote dev server must allow cross-origin requests (fulgur-federation enables server.cors automatically; a custom server config may have disabled it)")):(p.push(`1. is the remote deployed and reachable? try opening ${e.entry} in the browser \u2014 it must return JS`),p.push("2. NGINX/CDN routing: the entry path must serve the remoteEntry JS file (check try_files / fallback rules)")),new g(R.REMOTE_LOAD_FAILED,`failed to load remote "${e.name}" from ${e.entry}: ${String(d?.message??d)}
2
+ `+p.join(`
3
+ `),{remote:e.name})}j(c,e);let _=l.get(c);if(_&&_!==a)throw new g(R.CONTAINER_REINIT_CONFLICT,`container "${e.name}" already initialized with share scope "${_}", cannot re-init with "${a}"`);return typeof c.init=="function"&&await c.init(t),l.set(c,a),e.container=c,e.state="loaded",e.debug.status="loaded",u.fails=0,c})();e.containerPromise=E;try{return await E}catch(s){if(e.containerPromise=void 0,e.state="failed",e.debug.status="failed",e.debug.error=String(s?.message??s),u.fails+=1,u.fails>=ce&&(u.openUntil=Date.now()+le,u.fails=0),s instanceof g)throw $({remote:e.name,error:s}),s;if(s?.code)throw $({remote:e.name,error:s}),s;let c=new g(R.REMOTE_LOAD_FAILED,String(s?.message??s),{remote:e.name});throw $({remote:e.name,error:c}),c}}function A(e){let i=e.indexOf("/");return i===-1?{remote:e,module:""}:{remote:e.slice(0,i),module:X(e.slice(i+1))}}function X(e){return e?e.startsWith(".")?e:`./${e}`:""}async function J(e,i){let{remote:a,module:u}=A(e);M.beforeLoadRemote?.({remote:a,module:u});let E=n.get(a);if(!E)throw new g(R.REMOTE_UNKNOWN,`unknown remote "${a}". Register it via federation({ remotes }) or registerRemote().`,{remote:a});let s;try{s=await C(E,{retries:i?.retries})}catch(f){if(i?.fallbackModule)return console.error(`[fulgur] loadRemote("${e}") failed; returning fallbackModule (\u663E\u5F0F\u964D\u7EA7\uFF0C\u9519\u8BEF\u5DF2\u900F\u51FA)`,f),$({remote:a,error:f}),await i.fallbackModule();throw f}if(!u)return s;let c=`${a}@${E.shareScope||"default"}#${u}`;r.has(c)||r.set(c,s.get(u).catch(f=>{throw r.delete(c),f instanceof g||f?.code?f:new g(R.REMOTE_LOAD_FAILED,`failed to load module "${u}" from remote "${a}": ${String(f?.message??f)}`,{remote:a,module:u})}));let d;try{d=await r.get(c)}catch(f){if(i?.fallbackModule)return console.error(`[fulgur] loadRemote("${e}") failed; returning fallbackModule (\u663E\u5F0F\u964D\u7EA7\uFF0C\u9519\u8BEF\u5DF2\u900F\u51FA)`,f),$({remote:a,error:f}),await i.fallbackModule();throw f}if(M.afterLoadRemote?.({remote:a,module:u,module_ns:d}),d&&typeof d=="object"&&Object.keys(d).length===0){let f=new g(R.EMPTY_EXPORTS,`no exports: ${u} @ ${a}`,{remote:a,module:u});$({remote:a,error:f}),console.error(`[fulgur:MFU-009] ${f.message}`)}return d}function Y(e){let i=n.get(e);if(!i)throw new g(R.REMOTE_UNKNOWN,`unknown remote "${e}"`,{remote:e});return C(i)}async function Z(e,i={}){let{remote:a}=A(e),u=n.get(a);if(!u)throw new g(R.REMOTE_UNKNOWN,`unknown remote "${a}"`,{remote:a});let E=i.mode??"preload",s=(c,d)=>{if(typeof document>"u"||document.querySelector(`link[href="${c}"]`))return;let f=document.createElement("link");f.rel=d==="style"?"stylesheet":"modulepreload",f.href=c,E==="prefetch"&&(f.fetchPriority="low"),document.head.appendChild(f)};try{if(u.manifestUrl){let c=await fetch(u.manifestUrl,{cache:"no-cache"});if(c.ok){let d=await c.json();s(u.entry,"modulepreload");let f=d.exposes??{};for(let b of Object.keys(f)){let _=f[b];_?.file&&s(new URL(_.file,u.entry).href,"modulepreload");for(let p of _?.css??[])s(new URL(p,u.entry).href,"style")}return}}s(u.entry,"modulepreload")}catch(c){$({remote:a,error:new g(R.PRELOAD_FAILED,String(c?.message??c),{remote:a})})}}return typeof window>"u"||(window.__FULGUR_SCOPE__=t,window.__FULGUR_INFO__={get remotes(){let e={};for(let[i,a]of n)e[i]={...a.debug,entry:a.entry};return e},errors:[]}),{shareScopeMap:t,initSharing:H,registerShare:K,registerRemotes:x,registerRemote:q,registerPlugins:G,loadShare:z,loadRemote:J,getContainer:Y,preloadRemote:Z,parseSpec:A,provideFulgurAppConfig:y,getFulgurAppConfig:w}}var V=globalThis,S=V.__FULGUR_RUNTIME__??fe();V.__FULGUR_RUNTIME__=S;try{Object.freeze(S)}catch{}var Re=F;function Se(){return S}var we=S.initSharing,be=S.registerShare,_e=S.registerRemotes,$e=S.registerRemote,Me=S.registerPlugins,ve=S.loadShare,Te=S.loadRemote,Oe=S.getContainer,ke=S.preloadRemote,Ne=S.shareScopeMap,Ie=S.provideFulgurAppConfig,je=S.getFulgurAppConfig;function Ae(t){if(t&&typeof t=="object"&&"default"in t){let n=t.default;return n!==void 0?n:t}return t}var xe=S;export{xe as default,Oe as getContainer,je as getFulgurAppConfig,Se as getRuntime,we as initSharing,Te as loadRemote,ve as loadShare,ke as preloadRemote,Ie as provideFulgurAppConfig,Me as registerPlugins,$e as registerRemote,_e as registerRemotes,be as registerShare,S as runtime,Ne as shareScopeMap,Ae as unwrapDefault,Re as version};
@@ -0,0 +1,36 @@
1
+ # P5:fixtures Vite 7/8 兼容矩阵(2026-09-14 完成)
2
+
3
+ > 结论先行:**Vite 7.3.6 与 8.3.0 下 fixtures 三件套 dev+prod e2e 全部通过**,无需代码改动。
4
+ > 兼容矩阵至此覆盖 Vite 5.1.4 / 5.2.12 / 6.4.3 / 7.3.6 / 8.3.0。
5
+
6
+ ## 测试方法
7
+
8
+ - fixtures:`fixtures/{host-vue,remote-a,remote-b}`(端口 5100/5101/5102,prod 经 NGINX 8999)。
9
+ - 升级方式:三套 fixture 内 `pnpm add -D vite@^7 @vitejs/plugin-vue@^6`(7 轮);`vite@^8`(8 轮)。
10
+ e2e 全量跑 `e2e/`(playwright `test:dev` + `test:prod`,含 HMR / shared singleton / 键重命名 /
11
+ promise remote / MFU 错误码 / 容错恢复全功能用例)。
12
+ - 测毕 `git checkout -- fixtures/ && pnpm install` 恢复 6.4.3 基线(fixtures 不留升级残留)。
13
+
14
+ ## 结果矩阵
15
+
16
+ | Vite 版本 | @vitejs/plugin-vue | dev e2e | prod e2e | 备注 |
17
+ |-----------|--------------------|---------|----------|------|
18
+ | 5.1.4 | 4.x | ✅(历史轮) | ✅(历史轮) | testbed bpm 前基线 |
19
+ | 5.2.12 | 4.x | ✅(历史轮) | ✅(历史轮) | testbed lowcode 前基线 |
20
+ | 6.4.3 | ^5.2.0 | ✅ 10/10 | ✅ 8/8 | fixtures 默认版本 |
21
+ | **7.3.6** | ^6.x | ✅ 10/10 | ✅ 8/8 | 本轮实测,零改动通过 |
22
+ | **8.3.0** | ^6.x | ✅ 10/10 | ✅ 8/8 | 本轮实测,零改动通过 |
23
+
24
+ (testbed 三应用分别使用 Vite 6.4.3/5.1.4/5.2.12,见 `docs/mes-zc 环境事实`——真实工程同样覆盖。)
25
+
26
+ ## 插件侧依赖 Vite 的注意点(7/8 下均验证无碍)
27
+
28
+ - `configResolved` / `resolveId` / `transform` / `transformIndexHtml(pre)` / `configureServer`
29
+ 等钩子签名在 7/8 无破坏性变化;
30
+ - dev 协作引擎依赖的 `server.middlewares`、`pluginContainer.resolveId` 行为一致;
31
+ - build 引擎(rollup 原生输出 + `emitFile chunk`)在 7/8 下产物结构不变。
32
+
33
+ ## 遗留
34
+
35
+ - Vite 8 原生 rolldown 分支(`rolldown-vite`)不在本矩阵范围,待上游合并主线后再跑一轮。
36
+ - fixtures 只覆盖 vue 生态;react fixture 若将来补齐需同步扩 7/8 矩阵。