1 |
|
2 |
|
3 |
|
4 |
|
5 | "use strict";
|
6 |
|
7 | const RuntimeGlobals = require("../RuntimeGlobals");
|
8 | const RuntimeModule = require("../RuntimeModule");
|
9 | const Template = require("../Template");
|
10 | const { first } = require("../util/SetHelpers");
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 | class GetChunkFilenameRuntimeModule extends RuntimeModule {
|
20 | |
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | constructor(contentType, name, global, getFilenameForChunk, allChunks) {
|
28 | super(`get ${name} chunk filename`);
|
29 | this.contentType = contentType;
|
30 | this.global = global;
|
31 | this.getFilenameForChunk = getFilenameForChunk;
|
32 | this.allChunks = allChunks;
|
33 | this.dependentHash = true;
|
34 | }
|
35 |
|
36 | |
37 |
|
38 |
|
39 | generate() {
|
40 | const {
|
41 | global,
|
42 | chunk,
|
43 | chunkGraph,
|
44 | contentType,
|
45 | getFilenameForChunk,
|
46 | allChunks,
|
47 | compilation
|
48 | } = this;
|
49 | const { runtimeTemplate } = compilation;
|
50 |
|
51 |
|
52 | const chunkFilenames = new Map();
|
53 | let maxChunks = 0;
|
54 |
|
55 | let dynamicFilename;
|
56 |
|
57 | |
58 |
|
59 |
|
60 |
|
61 | const addChunk = c => {
|
62 | const chunkFilename = getFilenameForChunk(c);
|
63 | if (chunkFilename) {
|
64 | let set = chunkFilenames.get(chunkFilename);
|
65 | if (set === undefined) {
|
66 | chunkFilenames.set(chunkFilename, (set = new Set()));
|
67 | }
|
68 | set.add(c);
|
69 | if (typeof chunkFilename === "string") {
|
70 | if (set.size < maxChunks) return;
|
71 | if (set.size === maxChunks) {
|
72 | if (chunkFilename.length < dynamicFilename.length) return;
|
73 | if (chunkFilename.length === dynamicFilename.length) {
|
74 | if (chunkFilename < dynamicFilename) return;
|
75 | }
|
76 | }
|
77 | maxChunks = set.size;
|
78 | dynamicFilename = chunkFilename;
|
79 | }
|
80 | }
|
81 | };
|
82 |
|
83 | /** @type {string[]} */
|
84 | const includedChunksMessages = [];
|
85 | if (allChunks) {
|
86 | includedChunksMessages.push("all chunks");
|
87 | for (const c of chunk.getAllReferencedChunks()) {
|
88 | addChunk(c);
|
89 | }
|
90 | } else {
|
91 | includedChunksMessages.push("async chunks");
|
92 | for (const c of chunk.getAllAsyncChunks()) {
|
93 | addChunk(c);
|
94 | }
|
95 | const includeEntries = chunkGraph
|
96 | .getTreeRuntimeRequirements(chunk)
|
97 | .has(RuntimeGlobals.ensureChunkIncludeEntries);
|
98 | if (includeEntries) {
|
99 | includedChunksMessages.push("sibling chunks for the entrypoint");
|
100 | for (const c of chunkGraph.getChunkEntryDependentChunksIterable(
|
101 | chunk
|
102 | )) {
|
103 | addChunk(c);
|
104 | }
|
105 | }
|
106 | }
|
107 | for (const entrypoint of chunk.getAllReferencedAsyncEntrypoints()) {
|
108 | addChunk(entrypoint.chunks[entrypoint.chunks.length - 1]);
|
109 | }
|
110 |
|
111 |
|
112 | const staticUrls = new Map();
|
113 |
|
114 | const dynamicUrlChunks = new Set();
|
115 |
|
116 | |
117 |
|
118 |
|
119 |
|
120 |
|
121 | const addStaticUrl = (c, chunkFilename) => {
|
122 | |
123 |
|
124 |
|
125 |
|
126 | const unquotedStringify = value => {
|
127 | const str = `${value}`;
|
128 | if (str.length >= 5 && str === `${c.id}`) {
|
129 |
|
130 | return '" + chunkId + "';
|
131 | }
|
132 | const s = JSON.stringify(str);
|
133 | return s.slice(1, s.length - 1);
|
134 | };
|
135 | const unquotedStringifyWithLength = value => length =>
|
136 | unquotedStringify(`${value}`.slice(0, length));
|
137 | const chunkFilenameValue =
|
138 | typeof chunkFilename === "function"
|
139 | ? JSON.stringify(
|
140 | chunkFilename({
|
141 | chunk: c,
|
142 | contentHashType: contentType
|
143 | })
|
144 | )
|
145 | : JSON.stringify(chunkFilename);
|
146 | const staticChunkFilename = compilation.getPath(chunkFilenameValue, {
|
147 | hash: `" + ${RuntimeGlobals.getFullHash}() + "`,
|
148 | hashWithLength: length =>
|
149 | `" + ${RuntimeGlobals.getFullHash}().slice(0, ${length}) + "`,
|
150 | chunk: {
|
151 | id: unquotedStringify(c.id),
|
152 | hash: unquotedStringify(c.renderedHash),
|
153 | hashWithLength: unquotedStringifyWithLength(c.renderedHash),
|
154 | name: unquotedStringify(c.name || c.id),
|
155 | contentHash: {
|
156 | [contentType]: unquotedStringify(c.contentHash[contentType])
|
157 | },
|
158 | contentHashWithLength: {
|
159 | [contentType]: unquotedStringifyWithLength(
|
160 | c.contentHash[contentType]
|
161 | )
|
162 | }
|
163 | },
|
164 | contentHashType: contentType
|
165 | });
|
166 | let set = staticUrls.get(staticChunkFilename);
|
167 | if (set === undefined) {
|
168 | staticUrls.set(staticChunkFilename, (set = new Set()));
|
169 | }
|
170 | set.add(c.id);
|
171 | };
|
172 |
|
173 | for (const [filename, chunks] of chunkFilenames) {
|
174 | if (filename !== dynamicFilename) {
|
175 | for (const c of chunks) addStaticUrl(c, filename);
|
176 | } else {
|
177 | for (const c of chunks) dynamicUrlChunks.add(c);
|
178 | }
|
179 | }
|
180 |
|
181 | |
182 |
|
183 |
|
184 |
|
185 | const createMap = fn => {
|
186 | const obj = {};
|
187 | let useId = false;
|
188 | let lastKey;
|
189 | let entries = 0;
|
190 | for (const c of dynamicUrlChunks) {
|
191 | const value = fn(c);
|
192 | if (value === c.id) {
|
193 | useId = true;
|
194 | } else {
|
195 | obj[c.id] = value;
|
196 | lastKey = c.id;
|
197 | entries++;
|
198 | }
|
199 | }
|
200 | if (entries === 0) return "chunkId";
|
201 | if (entries === 1) {
|
202 | return useId
|
203 | ? `(chunkId === ${JSON.stringify(lastKey)} ? ${JSON.stringify(
|
204 | obj[lastKey]
|
205 | )} : chunkId)`
|
206 | : JSON.stringify(obj[lastKey]);
|
207 | }
|
208 | return useId
|
209 | ? `(${JSON.stringify(obj)}[chunkId] || chunkId)`
|
210 | : `${JSON.stringify(obj)}[chunkId]`;
|
211 | };
|
212 |
|
213 | |
214 |
|
215 |
|
216 |
|
217 | const mapExpr = fn => {
|
218 | return `" + ${createMap(fn)} + "`;
|
219 | };
|
220 |
|
221 | |
222 |
|
223 |
|
224 |
|
225 | const mapExprWithLength = fn => length => {
|
226 | return `" + ${createMap(c => `${fn(c)}`.slice(0, length))} + "`;
|
227 | };
|
228 |
|
229 | const url =
|
230 | dynamicFilename &&
|
231 | compilation.getPath(JSON.stringify(dynamicFilename), {
|
232 | hash: `" + ${RuntimeGlobals.getFullHash}() + "`,
|
233 | hashWithLength: length =>
|
234 | `" + ${RuntimeGlobals.getFullHash}().slice(0, ${length}) + "`,
|
235 | chunk: {
|
236 | id: `" + chunkId + "`,
|
237 | hash: mapExpr(c => c.renderedHash),
|
238 | hashWithLength: mapExprWithLength(c => c.renderedHash),
|
239 | name: mapExpr(c => c.name || c.id),
|
240 | contentHash: {
|
241 | [contentType]: mapExpr(c => c.contentHash[contentType])
|
242 | },
|
243 | contentHashWithLength: {
|
244 | [contentType]: mapExprWithLength(c => c.contentHash[contentType])
|
245 | }
|
246 | },
|
247 | contentHashType: contentType
|
248 | });
|
249 |
|
250 | return Template.asString([
|
251 | `// This function allow to reference ${includedChunksMessages.join(
|
252 | " and "
|
253 | )}`,
|
254 | `${global} = ${runtimeTemplate.basicFunction(
|
255 | "chunkId",
|
256 |
|
257 | staticUrls.size > 0
|
258 | ? [
|
259 | "// return url for filenames not based on template",
|
260 | // it minimizes to `x===1?"...":x===2?"...":"..."`
|
261 | Template.asString(
|
262 | Array.from(staticUrls, ([url, ids]) => {
|
263 | const condition =
|
264 | ids.size === 1
|
265 | ? `chunkId === ${JSON.stringify(first(ids))}`
|
266 | : `{${Array.from(
|
267 | ids,
|
268 | id => `${JSON.stringify(id)}:1`
|
269 | ).join(",")}}[chunkId]`;
|
270 | return `if (${condition}) return ${url};`;
|
271 | })
|
272 | ),
|
273 | "// return url for filenames based on template",
|
274 | `return ${url};`
|
275 | ]
|
276 | : ["// return url for filenames based on template", `return ${url};`]
|
277 | )};`
|
278 | ]);
|
279 | }
|
280 | }
|
281 |
|
282 | module.exports = GetChunkFilenameRuntimeModule;
|