--- /dev/null
+import { chromium } from "playwright";
+
+const PUB = "https://aidemo.istation.cloud/t/stationbuilder/";
+const browser = await chromium.launch();
+const ctx = await browser.newContext();
+const page = await ctx.newPage();
+const cdp = await ctx.newCDPSession(page);
+await cdp.send("Network.enable");
+const wire = new Map(); // url -> {encoded, enc}
+const reqUrl = new Map(); // requestId -> url
+cdp.on("Network.responseReceived", (e) => {
+ reqUrl.set(e.requestId, e.response.url);
+ wire.set(e.response.url, {
+ encoded: 0,
+ enc: e.response.headers["content-encoding"] || "-",
+ });
+});
+cdp.on("Network.loadingFinished", (e) => {
+ const u = reqUrl.get(e.requestId);
+ if (u && wire.has(u)) wire.get(u).encoded = e.encodedDataLength || 0;
+});
+let reloads = 0;
+page.on("domcontentloaded", () => reloads++);
+const t0 = Date.now();
+await page.goto(PUB, { waitUntil: "commit", timeout: 60000 });
+await page.waitForSelector(".company-side-nav", { timeout: 60000 });
+await page.waitForFunction(() => {
+ const els = Array.from(document.querySelectorAll(".empty-state"));
+ return !els.some(e => /加载中/.test(e.textContent || ""));
+}, undefined, { timeout: 60000 });
+const loadMs = Date.now() - t0;
+// keep page open 20s to detect a forced full-reload
+await page.waitForTimeout(20000);
+let total = 0, top = [];
+for (const [u, v] of wire) {
+ total += v.encoded;
+ top.push([v.encoded, v.enc, u.replace(PUB, "")]);
+}
+top.sort((a, b) => b[0] - a[0]);
+console.log(`loaded in ${loadMs}ms; domcontentloaded count (reload detector): ${reloads}`);
+console.log(`TOTAL wire bytes (CDP): ${(total / 1048576).toFixed(2)}MB across ${wire.size} responses`);
+console.log("top 8 by wire:");
+for (const [b, enc, u] of top.slice(0, 8))
+ console.log(` ${(b / 1024).toFixed(0)}KB [${enc}] ${u.slice(0, 80)}`);
+await browser.close();
+process.exit(0);
import vinext from "vinext";
-import { defineConfig } from "vite";
+import { defineConfig, type Plugin } from "vite";
+
+/**
+ * Strip inline base64 sourcemaps from dev module responses.
+ * vinext's RSC pipeline inlines sourcemaps into "use client" deps (e.g. antd.js
+ * balloons from 3MB to ~10MB), dominating first-load transfer. Replace the data
+ * URI with an external .map reference. Only buffers module-like URLs; never
+ * touches /api/ (streaming) or document/RSC responses.
+ */
+function stripInlineSourcemap(): Plugin {
+ return {
+ name: "strip-inline-sourcemap",
+ configureServer(server) {
+ server.middlewares.use((req, res, next) => {
+ const url = String(req.url || "");
+ const moduleLike =
+ !url.includes("/api/") &&
+ (url.includes("/node_modules/") || url.includes("/@id") ||
+ url.includes("/@vite") || url.includes("/@react-refresh") ||
+ /\.(tsx?|jsx?|css)(\?|$)/.test(url));
+ if (!moduleLike) return next();
+ const origWrite = res.write.bind(res);
+ const origEnd = res.end.bind(res);
+ const chunks: Buffer[] = [];
+ (res as any).write = (chunk: any) => {
+ if (chunk) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
+ return true;
+ };
+ (res as any).end = (chunk?: any) => {
+ try {
+ if (chunk) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
+ let body = Buffer.concat(chunks);
+ const ct = String(res.getHeader("content-type") || "");
+ if (ct.includes("javascript") && body.includes("sourceMappingURL=data:")) {
+ const mapName = url.split("?")[0].split("/").pop() + ".map";
+ body = Buffer.from(
+ body.toString("utf8").replace(
+ /\n?\/\/# sourceMappingURL=data:application\/json[^;\n]*;base64,[A-Za-z0-9+/=]+/,
+ "\n//# sourceMappingURL=" + mapName
+ )
+ );
+ res.removeHeader("transfer-encoding");
+ res.setHeader("content-length", body.length);
+ }
+ (res as any).write = origWrite;
+ (res as any).end = origEnd;
+ if (body.length) origWrite(body);
+ origEnd();
+ } catch {
+ origEnd();
+ }
+ };
+ next();
+ });
+ },
+ };
+}
/**
* Self-contained local UI configuration.
},
},
},
- plugins: [vinext()],
+ plugins: [vinext(), stripInlineSourcemap()],
});