Skip to content

Commit 5c39bdc

Browse files
committed
test(build): guard gsap slot, move md-katex bridge
1 parent df72e8b commit 5c39bdc

2 files changed

Lines changed: 60 additions & 8 deletions

File tree

‎app/src/build/manualChunks.test.ts‎

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ describe("vendorGroupOf 钉值", () => {
4545
["/r/node_modules/hast-util-to-text/lib/index.js", "vendor-katex"],
4646
["/r/node_modules/hast-util-from-dom/lib/index.js", "vendor-katex"],
4747
["/r/node_modules/hast-util-to-jsx-runtime/lib/index.js", "vendor-md"],
48-
["/r/node_modules/property-information/lib/index.js", "vendor-katex"],
49-
["/r/node_modules/hastscript/index.js", "vendor-katex"],
48+
["/r/node_modules/property-information/lib/index.js", "vendor-md"],
49+
["/r/node_modules/hastscript/index.js", "vendor-md"],
5050
["/r/node_modules/@tauri-apps/api/core.js", "vendor-tauri"],
5151
["/r/node_modules/@tauri-apps/plugin-dialog/dist-js/index.js", "vendor-tauri"],
5252
["/r/node_modules/codemirror/dist/index.js", "vendor-editor"],
@@ -131,9 +131,9 @@ const EXACT_PINS: readonly (readonly [string, VendorGroup])[] = [
131131
["hast-util-from-html-isomorphic", "vendor-katex"],
132132
["hast-util-is-element", "vendor-katex"],
133133
["hast-util-parse-selector", "vendor-katex"],
134-
["hastscript", "vendor-katex"],
135-
["property-information", "vendor-katex"],
136-
["web-namespaces", "vendor-katex"],
134+
["property-information", "vendor-md"],
135+
["hastscript", "vendor-md"],
136+
["web-namespaces", "vendor-md"],
137137
["codemirror", "vendor-editor"],
138138
["crelt", "vendor-editor"],
139139
["style-mod", "vendor-editor"],
@@ -249,3 +249,44 @@ describe("接线面全量一致(manualChunks 不许有第二套逻辑)", ()
249249
expect(ids.map(manualChunks)).toEqual(ids.map((id) => vendorGroupOf(id)));
250250
});
251251
});
252+
253+
// Task 5(不装 GSAP):预留槽 + 循环 chunk 的**反例守卫**。槽位本身见 EXACT 的 gsap 两行。
254+
describe("批 6 预留:GSAP 必须落进独立 chunk(本批不安装 GSAP)", () => {
255+
it("GSAP 的两个包名都归 vendor-gsap,且与 react 不同组", () => {
256+
const gsap = vendorGroupOf("/r/node_modules/gsap/index.js");
257+
const gsapReact = vendorGroupOf("/r/node_modules/@gsap/react/dist/index.js");
258+
expect(gsap).toBe("vendor-gsap");
259+
expect(gsapReact).toBe("vendor-gsap");
260+
expect(gsap).not.toBe(vendorGroupOf("/r/node_modules/react/index.js"));
261+
});
262+
263+
it("槽位在表里,且 package.json 此刻没有 gsap(两半必须同时成立)", () => {
264+
// 只查 package.json ⇒ 槽位被删也通过;只查表 ⇒ 批 6 装了依赖却没人复核产物也通过。
265+
// devDependencies 也查:本段问的是「gsap 装了没有」,不是「它进不进浏览器产物」。
266+
const pkg = JSON.parse(readFileSync(join(HERE, "..", "..", "package.json"), "utf8")) as {
267+
dependencies?: Record<string, string>;
268+
devDependencies?: Record<string, string>;
269+
};
270+
const all = { ...(pkg.dependencies ?? {}), ...(pkg.devDependencies ?? {}) };
271+
// 批 6 装上后这条会红 ⇒ 去构建一次,核对 vendor-gsap chunk 独立生成且只经 import() 到达。
272+
expect(Object.keys(all).filter((d) => d === "gsap" || d === "@gsap/react")).toEqual([]);
273+
expect(vendorGroupOf("/r/node_modules/gsap/index.js")).toBe("vendor-gsap");
274+
});
275+
276+
it("反例:槽位不得被任何前缀规则吞掉(防将来有人加宽前缀)", () => {
277+
expect(vendorGroupOf("/r/node_modules/gsap-core/index.js")).toBeUndefined();
278+
expect(vendorGroupOf("/r/node_modules/not-gsap/index.js")).toBeUndefined();
279+
});
280+
});
281+
282+
// 实测(Task 4):vite 警告 `Circular chunk: vendor-katex -> vendor-md -> vendor-katex`,两 chunk
283+
// 首行互指 ⇒ 130.66 kB gzip 熔成不可分簇。断环只靠**方向**(不靠把 katex 并进 md —— 那会被
284+
// 上面的 EXACT 钉值表抓住):md→katex 的桥留在 md 侧,katex→md 的单向边保留。
285+
describe("循环 chunk 反例守卫(vendor-md ⇄ vendor-katex 不许成环)", () => {
286+
it("md→katex 的桥(property-information 族)必须留在 vendor-md", () => {
287+
for (const n of ["property-information", "hastscript", "web-namespaces"]) {
288+
expect(EXACT[n]).toBe("vendor-md");
289+
expect(vendorGroupOf(`/r/node_modules/${n}/index.js`)).toBe("vendor-md");
290+
}
291+
});
292+
});

‎app/src/build/manualChunks.ts‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
* 因此 gsap 未安装时规则**天然惰性**(没有任何模块 id 会解析出包名 `gsap`),
1818
* 装了之后**零改动自动生效**。切不可改写成对象形式
1919
* `{ "vendor-gsap": ["gsap"] }` —— 那会在 gsap 未安装时**直接让构建失败**。
20+
* @ai-context: 分组还可能**成环**(不只是「搬字节」):`vendor-md` 与 `vendor-katex` 互相静态
21+
* import 时被 vite 判为循环 chunk 并熔成不可分簇 ⇒ 懒加载边界静默失效。断环只能靠
22+
* **方向**;⚠️ 截至 Task 5 该环**尚未断净**(残留桥与已实测的最小解见下方管线段)。
2023
*
2124
* 副作用:无(纯函数)。
2225
* 边界:入参是 rollup 的模块 id(Windows 上可能含 `\`);不解析 tsconfig paths;不处理虚拟模块(`\0` 前缀)。
@@ -56,9 +59,6 @@ export const EXACT: Readonly<Record<string, VendorGroup>> = {
5659
"hast-util-from-html-isomorphic": "vendor-katex",
5760
"hast-util-is-element": "vendor-katex",
5861
"hast-util-parse-selector": "vendor-katex",
59-
hastscript: "vendor-katex",
60-
"property-information": "vendor-katex",
61-
"web-namespaces": "vendor-katex",
6262
// 编辑器(CodeMirror 6 无 scope 的几件)
6363
codemirror: "vendor-editor",
6464
crelt: "vendor-editor",
@@ -69,6 +69,17 @@ export const EXACT: Readonly<Record<string, VendorGroup>> = {
6969
zustand: "vendor-canvas",
7070
"use-sync-external-store": "vendor-canvas",
7171
// Markdown 管线
72+
// 🔴 以下三件是 **md → katex** 反向边的一条桥(`hast-util-to-jsx-runtime` → `property-information`),
73+
// 必须留在 vendor-md:归回 vendor-katex 会让该方向复活。⚠️ **Task 5 实测的诚实边界**:
74+
// 只挪这三件**不足以断环** —— 本组的 `hastscript` 仍 import `hast-util-parse-selector`
75+
// (仍登记在 vendor-katex)⇒ 构建**依旧**打印 `Circular chunk: vendor-katex -> vendor-md ->
76+
// vendor-katex`,两 chunk 依旧互相静态 import(130.58 kB gzip 熔成不可分簇)。
77+
// 已实测的**断环最小解** = 把 `hast-util-parse-selector` 一并挪来本组(我实测:vite 不再打印
78+
// 该警告、md→katex 边归零只剩 katex→md 单向);该行**超出控制方授权范围 ⇒ 本批未落地**,
79+
// 证据与复现命令见 `.superpowers/sdd/2026-09-11-frontend-redesign-batch2-bundle/task-5-report.md` §C。
80+
"property-information": "vendor-md",
81+
hastscript: "vendor-md",
82+
"web-namespaces": "vendor-md",
7283
"react-markdown": "vendor-md",
7384
unified: "vendor-md",
7485
bail: "vendor-md",

0 commit comments

Comments
 (0)