Repository navigation
Expand file tree
/
Copy pathwarmBundledDev.ts
More file actions
122 lines (114 loc) · 4.48 KB
/
Copy pathwarmBundledDev.ts
File metadata and controls
122 lines (114 loc) · 4.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import type { Plugin, ViteDevServer } from "vite-plus";
// What the first page load pulls in: the app, the managed-auth shell, the chat
// layout and its landing routes, and the sidebar's project icons.
const STARTUP_LAZY_ENTRIES = [
"src/main.tsx",
"src/components/clerk/BrowserManagedAuthShell.tsx",
"src/routes/_chat.tsx",
"src/routes/_chat.index.tsx",
"src/routes/_chat.$environmentId.$threadId.tsx",
"src/lib/lucideIconByName.ts",
];
const WARMUP_CLIENT_ID = "t3code-startup-warmup";
// Bundled dev keeps these private and untyped. A Vite upgrade that moves them
// skips the warmup or logs a warning, never breaks dev; bundledDev.test.ts
// fails so the upgrade cannot drop the warmup silently.
interface BundledDevInternals {
waitForInitialBuildFinish(): Promise<void>;
triggerLazyBundling(
moduleId: string,
clientId: string,
): Promise<{ readonly filename: string } | undefined>;
markPayloadDelivered(filename: string): void;
readonly devEngine: {
readonly moduleGraph: {
getModuleIds(): readonly string[];
getModuleInfo(id: string): { readonly dynamicallyImportedIds: readonly string[] } | null;
};
removeClient(clientId: string): Promise<void>;
};
}
function hasBundledDevInternals(value: unknown): value is BundledDevInternals {
return (
typeof value === "object" &&
value !== null &&
"waitForInitialBuildFinish" in value &&
typeof value.waitForInitialBuildFinish === "function" &&
"triggerLazyBundling" in value &&
typeof value.triggerLazyBundling === "function" &&
"markPayloadDelivered" in value &&
typeof value.markPayloadDelivered === "function"
);
}
/**
* Compiles `entries` as lazy chunks once, for a throwaway client, and returns
* the lazy module ids it compiled. The browser's own requests then reuse
* Rolldown's transform cache. Entries are paths relative to the Vite root.
*/
async function warmLazyEntries(
server: ViteDevServer,
entries: readonly string[],
): Promise<string[]> {
// Widened so the guard narrows to the internals, not to Vite's public class.
const bundledDev: unknown = server.environments.client.bundledDev;
if (!hasBundledDevInternals(bundledDev)) return [];
await bundledDev.waitForInitialBuildFinish();
const { moduleGraph } = bundledDev.devEngine;
// Vite's root and Rolldown's module ids are both absolute, slash-separated paths.
const wanted = new Set(entries.map((entry) => `${server.config.root}/${entry}`));
const warmed = new Set<string>();
// A lazy entry is only in the graph after its parent compiled, so walk in
// passes. Each pass compiles at least one new wanted id, so it ends.
const nextLazyIds = () =>
new Set(
moduleGraph
.getModuleIds()
.flatMap((id) => moduleGraph.getModuleInfo(id)?.dynamicallyImportedIds ?? [])
.filter(
(id) =>
id.includes("rolldown-lazy") && !warmed.has(id) && wanted.has(id.split("?")[0] ?? id),
),
);
try {
for (let pending = nextLazyIds(); pending.size > 0; pending = nextLazyIds()) {
for (const id of pending) {
warmed.add(id);
const result = await bundledDev.triggerLazyBundling(id, WARMUP_CLIENT_ID);
// Acked so the engine drops the pending payload instead of holding the chunk.
if (result) bundledDev.markPayloadDelivered(result.filename);
}
}
} finally {
await bundledDev.devEngine.removeClient(WARMUP_CLIENT_ID);
}
return [...warmed];
}
// Bundled dev compiles lazy chunks on first request and ignores `server.warmup`.
// Warm them on listening to avoid serial compilation before first paint.
export function warmBundledDevPlugin(): Plugin {
return {
name: "t3code:warm-bundled-dev",
apply: "serve",
configureServer(server) {
if (!server.config.experimental.bundledDev) return;
server.httpServer?.once("listening", () => {
const startedAt = performance.now();
warmLazyEntries(server, STARTUP_LAZY_ENTRIES).then(
(warmed) => {
if (warmed.length === 0) return;
const seconds = ((performance.now() - startedAt) / 1000).toFixed(1);
server.config.logger.info(
`[t3code] pre-bundled ${String(warmed.length)} startup chunks in ${seconds}s`,
{ timestamp: true },
);
},
(error: unknown) => {
server.config.logger.warn(`[t3code] startup chunk warmup failed: ${String(error)}`, {
timestamp: true,
});
},
);
});
},
};
}