-
Notifications
You must be signed in to change notification settings - Fork 42
Expand file tree
/
Copy pathoptimizeChunk.js
More file actions
152 lines (142 loc) · 5.97 KB
/
optimizeChunk.js
File metadata and controls
152 lines (142 loc) · 5.97 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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
const ModuleFilenameHelpers = require("webpack/lib/ModuleFilenameHelpers");
const { ConcatSource } = require("webpack-sources");
const mem = require("mem");
function wrapFile(compilation, fileName, allModulesNeeded, chunkKeys) {
// create a stringified array
const outputOptions = compilation.output;
const pushArguments = JSON.stringify([
// pass the source compilation hash to figure out if a chunk is being required by its own build - if so, dont register anything
{ hash: compilation.hash, publicPath: outputOptions?.publicPath || "/" },
// array of keys to look up values in the allModulesNeeded hashmap
chunkKeys,
allModulesNeeded
]);
// add chunk registration code that will push all chunk requirements into webpack
// eslint-disable-next-line no-param-reassign
compilation.assets[fileName] = new ConcatSource(
String(
`(window["webpackRegister"] = window["webpackRegister"] || []).push(${pushArguments});\n`
),
compilation.assets[fileName]
);
}
const loopFiles = mem(files => {
return files.filter(file => {
return file.includes(".css");
});
});
// eslint-disable-next-line import/prefer-default-export
export function wrapChunks(compilation, chunks) {
// create a maps
const map = { ignoredChunk: new Set() };
const orgs = {};
// loop over each chunk
console.time("loop");
chunks.forEach(chunk => {
console.log("chunk", chunk);
// map weak maps and weak sets for better organization & perf
// console.group(group)
console.log(
"chunk",
chunk.id,
chunk.canBeInitial(),
chunk.isOnlyInitial(),
chunk.hasEntryModule(),
chunk.hasRuntime()
);
// check if this chunk is an entrypoint or has the webpack runtime
// if it does, dont bother mapping registration data or include them in any other chunks registration maps
if (chunk.hasEntryModule() || chunk.hasRuntime()) {
map.ignoredChunk.add(chunk.id);
} else {
console.log("ignored chunk", chunk);
}
// dont run if this has already been done on the chunk
if (!chunk.rendered) {
return;
}
// get all the modules in a chunk and loop over them
chunk.getModules().forEach(module => {
// add the chunk ID as a key and create an empty array if one isnt there already
if (!(map[chunk.id] instanceof Object)) {
map[chunk.id] = { js: [], css: [] };
}
// push each module in a chunk into its array within the map
if (module.id) map[chunk.id].js.push(`${module.id}`);
map[chunk.id].css = [...map[chunk.id].css, ...loopFiles(chunk.files)];
// check the reason a chunk exists, this is an array which returns any and all modules that depend on the current module
module.reasons.forEach(reason => {
if (reason.module) {
// if theres a module, loop over the chunks this module is in
reason.module.chunksIterable.forEach(reasonChunk => {
// add the chunkID of where this module exists
if (!orgs[reasonChunk.id])
orgs[reasonChunk.id] = { js: new Set(), css: new Set() };
orgs[reasonChunk.id].css = new Set([
...orgs[reasonChunk.id].css,
...loopFiles(reasonChunk.files)
]);
// console.log("reasonChunk", reasonChunk);
// orgs[chunk.id].add(`${module.id}-${module.rawRequest}`);
// add the chunkID that depends on this module
if (chunk.id) orgs[reasonChunk.id].js.add(chunk.id);
});
}
});
});
// loop over everything and add the all other chunks a chunk depends on.
// this creates a map telling us what a chunk needs and where to find it
// chunks usually wont contain ALL the dependencies they need, so i need to make sure that i record what files contain dependencies
// this chunk needs in order to be executed successfully
Object.keys(orgs).forEach(key => {
orgs[key].js.forEach(subSet => {
if (orgs[subSet]) {
orgs[subSet].js.delete(...map.ignoredChunk);
// dont walk entry or runtime chunks
if (!map.ignoredChunk.has(subSet)) {
if (orgs[subSet].js.size) orgs[key].js.add(...orgs[subSet].js);
if (orgs[subSet].css.size) orgs[key].css.add(...orgs[subSet].css);
}
}
});
});
});
console.timeEnd("loop");
console.log("internal map", map);
console.log("internal org", orgs);
// to ensure the chunk maps are complete, i run another loop over the chunks - the previous loop creates a complete map
// this loop uses the completed map to write the chunk registration data into each chunk file
chunks.forEach(chunk => {
if (!chunk.rendered || map.ignoredChunk.has(chunk.id)) {
return;
}
// loop over all files that make up this chunk
// eslint-disable-next-line no-restricted-syntax
for (const fileName of chunk.files) {
// check that its a javascript file (might be an image, html, css)
if (
ModuleFilenameHelpers.matchObject({}, fileName) &&
fileName.indexOf(".js") !== -1
) {
// get all the chunksID's the current chunk might need
const AllChunksNeeded = Array.from(orgs?.[chunk.id]?.js || new Set());
// create the final map which contains an array of chunkID as well as a object of chunk of what each chunk needs
const AllModulesNeeded = AllChunksNeeded.reduce(
(allDependencies, dependentChunk) => {
return {
...allDependencies,
[dependentChunk]: {
js: [...new Set(map[dependentChunk].js)],
css: [...new Set(map[dependentChunk].css)]
} // {"vendors-main":[modules], "somechunk": [modules]}
};
},
{}
);
console.log("AllModulesNeeded", AllModulesNeeded);
// now that we have maps of what the current file being iterated needs, write additional code to the file
wrapFile(compilation, fileName, AllModulesNeeded, AllChunksNeeded);
}
}
});
}