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
|
import type { OutputBundle } from 'rollup';
import type { PluginOption } from 'vite';
/**
* A Vite bundle analyzer that identifies chunks that are not used for server rendering.
*
* The chunks injected by Astro for prerendering are flagged as non-server chunks.
* Any chunks that is only used by a non-server chunk are also flagged as non-server chunks.
* This continues transitively until all non-server chunks are found.
*/
export class NonServerChunkDetector {
private nonServerChunks?: string[];
public getPlugin(): PluginOption {
return {
name: 'non-server-chunk-detector',
generateBundle: (_, bundle) => {
// Skip if we bundle for client
if (!bundle['index.js']) return;
this.processBundle(bundle);
},
};
}
private processBundle(bundle: OutputBundle) {
const chunkNamesToFiles = new Map<string, string>();
const entryChunks: string[] = [];
const chunkToDependencies = new Map<string, string[]>();
for (const chunk of Object.values(bundle)) {
if (chunk.type !== 'chunk') continue;
// Construct a mapping from a chunk name to its file name
chunkNamesToFiles.set(chunk.name, chunk.fileName);
// Construct a mapping from a chunk file to all the modules it imports
chunkToDependencies.set(chunk.fileName, [...chunk.imports, ...chunk.dynamicImports]);
if (chunk.isEntry) {
// Entry chunks should always be kept around since they are to be imported by the runtime
entryChunks.push(chunk.fileName);
}
}
const chunkDecisions = new Map<string, boolean>();
for (const entry of entryChunks) {
// Entry chunks are used on the server
chunkDecisions.set(entry, true);
}
for (const chunk of ['prerender', 'prerender@_@astro']) {
// Prerender chunks are not used on the server
const fileName = chunkNamesToFiles.get(chunk);
if (fileName) {
chunkDecisions.set(fileName, false);
}
}
// Start a stack of chunks that are used on the server
const chunksToWalk = [...entryChunks];
// Iterate over the chunks, traversing the transitive dependencies of the chunks used on the server
for (let chunk = chunksToWalk.pop(); chunk; chunk = chunksToWalk.pop()) {
for (const dep of chunkToDependencies.get(chunk) ?? []) {
// Skip dependencies already flagged, dependencies may be repeated and/or circular
if (chunkDecisions.has(dep)) continue;
// A dependency of a module used on the server is also used on the server
chunkDecisions.set(dep, true);
// Add the dependency to the stack so its own dependencies are also flagged
chunksToWalk.push(dep);
}
}
// Any chunk not flagged as used on the server is a non-server chunk
this.nonServerChunks = Array.from(chunkToDependencies.keys()).filter(
(chunk) => !chunkDecisions.get(chunk),
);
}
public getNonServerChunks(): string[] {
return this.nonServerChunks ?? [];
}
}
|