UNPKG

8.46 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3*/
4
5"use strict";
6
7const RuntimeGlobals = require("../RuntimeGlobals");
8const RuntimeModule = require("../RuntimeModule");
9const Template = require("../Template");
10const { first } = require("../util/SetHelpers");
11
12/** @typedef {import("../Chunk")} Chunk */
13/** @typedef {import("../Compilation")} Compilation */
14/** @typedef {import("../Compilation").AssetInfo} AssetInfo */
15/** @typedef {import("../Compilation").PathData} PathData */
16
17/** @typedef {function(PathData, AssetInfo=): string} FilenameFunction */
18
19class GetChunkFilenameRuntimeModule extends RuntimeModule {
20 /**
21 * @param {string} contentType the contentType to use the content hash for
22 * @param {string} name kind of filename
23 * @param {string} global function name to be assigned
24 * @param {function(Chunk): string | FilenameFunction} getFilenameForChunk functor to get the filename or function
25 * @param {boolean} allChunks when false, only async chunks are included
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 * @returns {string} runtime code
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 /** @type {Map<string | FilenameFunction, Set<Chunk>>} */
52 const chunkFilenames = new Map();
53 let maxChunks = 0;
54 /** @type {string} */
55 let dynamicFilename;
56
57 /**
58 * @param {Chunk} c the chunk
59 * @returns {void}
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 /** @type {Map<string, Set<string | number>>} */
112 const staticUrls = new Map();
113 /** @type {Set<Chunk>} */
114 const dynamicUrlChunks = new Set();
115
116 /**
117 * @param {Chunk} c the chunk
118 * @param {string | FilenameFunction} chunkFilename the filename template for the chunk
119 * @returns {void}
120 */
121 const addStaticUrl = (c, chunkFilename) => {
122 /**
123 * @param {string | number} value a value
124 * @returns {string} string to put in quotes
125 */
126 const unquotedStringify = value => {
127 const str = `${value}`;
128 if (str.length >= 5 && str === `${c.id}`) {
129 // This is shorter and generates the same result
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 * @param {function(Chunk): string | number} fn function from chunk to value
183 * @returns {string} code with static mapping of results of fn
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 * @param {function(Chunk): string | number} fn function from chunk to value
215 * @returns {string} code with static mapping of results of fn for including in quoted string
216 */
217 const mapExpr = fn => {
218 return `" + ${createMap(fn)} + "`;
219 };
220
221 /**
222 * @param {function(Chunk): string | number} fn function from chunk to value
223 * @returns {function(number): string} function which generates code with static mapping of results of fn for including in quoted string for specific length
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
282module.exports = GetChunkFilenameRuntimeModule;