| 
									
										
										
										
											2018-11-21 18:32:10 +08:00
										 |  |  | /* | 
					
						
							|  |  |  | 	MIT License http://www.opensource.org/licenses/mit-license.php
 | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | "use strict"; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const RuntimeGlobals = require("../RuntimeGlobals"); | 
					
						
							|  |  |  | const RuntimeModule = require("../RuntimeModule"); | 
					
						
							|  |  |  | const Template = require("../Template"); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-19 04:09:10 +08:00
										 |  |  | /** @typedef {import("../Chunk")} Chunk */ | 
					
						
							|  |  |  | /** @typedef {import("../Compilation")} Compilation */ | 
					
						
							| 
									
										
										
										
											2019-09-13 17:12:26 +08:00
										 |  |  | /** @typedef {import("../Compilation").AssetInfo} AssetInfo */ | 
					
						
							| 
									
										
										
										
											2019-06-19 04:09:10 +08:00
										 |  |  | /** @typedef {import("../Compilation").PathData} PathData */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-09-13 17:12:26 +08:00
										 |  |  | /** @typedef {function(PathData, AssetInfo=): string} FilenameFunction */ | 
					
						
							| 
									
										
										
										
											2019-06-19 04:09:10 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-21 18:32:10 +08:00
										 |  |  | class GetChunkFilenameRuntimeModule extends RuntimeModule { | 
					
						
							| 
									
										
										
										
											2019-06-19 04:09:10 +08:00
										 |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {string} contentType the contentType to use the content hash for | 
					
						
							|  |  |  | 	 * @param {string} name kind of filename | 
					
						
							|  |  |  | 	 * @param {string} global function name to be assigned | 
					
						
							|  |  |  | 	 * @param {function(Chunk): string | FilenameFunction} getFilenameForChunk functor to get the filename or function | 
					
						
							|  |  |  | 	 * @param {boolean} allChunks when false, only async chunks are included | 
					
						
							|  |  |  | 	 */ | 
					
						
							| 
									
										
										
										
											2019-08-27 02:21:07 +08:00
										 |  |  | 	constructor(contentType, name, global, getFilenameForChunk, allChunks) { | 
					
						
							| 
									
										
										
										
											2018-11-28 20:07:40 +08:00
										 |  |  | 		super(`get ${name} chunk filename`); | 
					
						
							| 
									
										
										
										
											2018-11-21 18:32:10 +08:00
										 |  |  | 		this.contentType = contentType; | 
					
						
							|  |  |  | 		this.global = global; | 
					
						
							| 
									
										
										
										
											2018-12-29 19:48:59 +08:00
										 |  |  | 		this.getFilenameForChunk = getFilenameForChunk; | 
					
						
							| 
									
										
										
										
											2019-02-18 17:03:07 +08:00
										 |  |  | 		this.allChunks = allChunks; | 
					
						
							| 
									
										
										
										
											2018-11-21 18:32:10 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @returns {string} runtime code | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	generate() { | 
					
						
							| 
									
										
										
										
											2018-12-29 19:48:59 +08:00
										 |  |  | 		const { | 
					
						
							|  |  |  | 			global, | 
					
						
							|  |  |  | 			chunk, | 
					
						
							|  |  |  | 			contentType, | 
					
						
							|  |  |  | 			getFilenameForChunk, | 
					
						
							| 
									
										
										
										
											2019-02-18 17:03:07 +08:00
										 |  |  | 			allChunks, | 
					
						
							| 
									
										
										
										
											2018-12-29 19:48:59 +08:00
										 |  |  | 			compilation | 
					
						
							|  |  |  | 		} = this; | 
					
						
							| 
									
										
										
										
											2019-10-04 18:24:52 +08:00
										 |  |  | 		const { runtimeTemplate } = compilation; | 
					
						
							| 
									
										
										
										
											2019-02-18 17:03:07 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-19 04:09:10 +08:00
										 |  |  | 		/** @type {Map<string | FilenameFunction, Set<Chunk>>} */ | 
					
						
							| 
									
										
										
										
											2019-02-18 17:03:07 +08:00
										 |  |  | 		const chunkFilenames = new Map(); | 
					
						
							|  |  |  | 		let maxChunks = 0; | 
					
						
							| 
									
										
										
										
											2019-06-19 04:09:10 +08:00
										 |  |  | 		/** @type {string} */ | 
					
						
							| 
									
										
										
										
											2019-02-18 17:03:07 +08:00
										 |  |  | 		let dynamicFilename; | 
					
						
							| 
									
										
										
										
											2019-06-19 04:09:10 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		/** | 
					
						
							|  |  |  | 		 * @param {Chunk} c the chunk | 
					
						
							|  |  |  | 		 * @returns {void} | 
					
						
							|  |  |  | 		 */ | 
					
						
							| 
									
										
										
										
											2019-02-18 17:03:07 +08:00
										 |  |  | 		const addChunk = c => { | 
					
						
							|  |  |  | 			const chunkFilename = getFilenameForChunk(c); | 
					
						
							|  |  |  | 			if (chunkFilename) { | 
					
						
							|  |  |  | 				let set = chunkFilenames.get(chunkFilename); | 
					
						
							|  |  |  | 				if (set === undefined) { | 
					
						
							|  |  |  | 					chunkFilenames.set(chunkFilename, (set = new Set())); | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				set.add(c); | 
					
						
							| 
									
										
										
										
											2019-06-19 04:09:10 +08:00
										 |  |  | 				if (typeof chunkFilename === "string") { | 
					
						
							|  |  |  | 					if (set.size < maxChunks) return; | 
					
						
							|  |  |  | 					if (set.size === maxChunks) { | 
					
						
							|  |  |  | 						if (chunkFilename.length < dynamicFilename.length) return; | 
					
						
							|  |  |  | 						if (chunkFilename.length === dynamicFilename.length) { | 
					
						
							|  |  |  | 							if (chunkFilename < dynamicFilename) return; | 
					
						
							|  |  |  | 						} | 
					
						
							| 
									
										
										
										
											2018-12-29 19:48:59 +08:00
										 |  |  | 					} | 
					
						
							| 
									
										
										
										
											2019-06-19 04:09:10 +08:00
										 |  |  | 					maxChunks = set.size; | 
					
						
							|  |  |  | 					dynamicFilename = chunkFilename; | 
					
						
							| 
									
										
										
										
											2019-02-18 17:03:07 +08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-19 04:09:10 +08:00
										 |  |  | 		/** @type {string[]} */ | 
					
						
							| 
									
										
										
										
											2019-02-18 17:03:07 +08:00
										 |  |  | 		const includedChunksMessages = []; | 
					
						
							|  |  |  | 		if (allChunks) { | 
					
						
							|  |  |  | 			includedChunksMessages.push("all chunks"); | 
					
						
							|  |  |  | 			for (const c of chunk.getAllReferencedChunks()) { | 
					
						
							|  |  |  | 				addChunk(c); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			includedChunksMessages.push("async chunks"); | 
					
						
							|  |  |  | 			for (const c of chunk.getAllAsyncChunks()) { | 
					
						
							|  |  |  | 				addChunk(c); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			const includeEntries = compilation.chunkGraph | 
					
						
							|  |  |  | 				.getTreeRuntimeRequirements(chunk) | 
					
						
							|  |  |  | 				.has(RuntimeGlobals.ensureChunkIncludeEntries); | 
					
						
							|  |  |  | 			if (includeEntries) { | 
					
						
							|  |  |  | 				includedChunksMessages.push("sibling chunks for the entrypoint"); | 
					
						
							|  |  |  | 				for (const c of compilation.chunkGraph.getChunkEntryDependentChunksIterable( | 
					
						
							|  |  |  | 					chunk | 
					
						
							|  |  |  | 				)) { | 
					
						
							|  |  |  | 					addChunk(c); | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2018-12-29 19:48:59 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-09-08 00:02:14 +08:00
										 |  |  | 		for (const entrypoint of chunk.getAllReferencedAsyncEntrypoints()) { | 
					
						
							|  |  |  | 			addChunk(entrypoint.chunks[entrypoint.chunks.length - 1]); | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-02-18 17:03:07 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-19 04:09:10 +08:00
										 |  |  | 		/** @type {Map<string, Set<string | number>>} */ | 
					
						
							| 
									
										
										
										
											2019-02-18 17:03:07 +08:00
										 |  |  | 		const staticUrls = new Map(); | 
					
						
							| 
									
										
										
										
											2019-06-19 04:09:10 +08:00
										 |  |  | 		/** @type {Set<Chunk>} */ | 
					
						
							| 
									
										
										
										
											2019-02-18 17:03:07 +08:00
										 |  |  | 		const dynamicUrlChunks = new Set(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-19 04:09:10 +08:00
										 |  |  | 		/** | 
					
						
							|  |  |  | 		 * @param {Chunk} c the chunk | 
					
						
							|  |  |  | 		 * @param {string | FilenameFunction} chunkFilename the filename template for the chunk | 
					
						
							|  |  |  | 		 * @returns {void} | 
					
						
							|  |  |  | 		 */ | 
					
						
							| 
									
										
										
										
											2019-02-18 17:03:07 +08:00
										 |  |  | 		const addStaticUrl = (c, chunkFilename) => { | 
					
						
							| 
									
										
										
										
											2019-06-19 04:09:10 +08:00
										 |  |  | 			/** | 
					
						
							|  |  |  | 			 * @param {string | number} value a value | 
					
						
							|  |  |  | 			 * @returns {string} string to put in quotes | 
					
						
							|  |  |  | 			 */ | 
					
						
							| 
									
										
										
										
											2019-02-18 17:03:07 +08:00
										 |  |  | 			const unquotedStringify = value => { | 
					
						
							|  |  |  | 				const str = `${value}`; | 
					
						
							|  |  |  | 				if (str.length >= 5 && str === `${c.id}`) { | 
					
						
							|  |  |  | 					// This is shorter and generates the same result
 | 
					
						
							| 
									
										
										
										
											2019-06-19 04:09:10 +08:00
										 |  |  | 					return '" + chunkId + "'; | 
					
						
							| 
									
										
										
										
											2019-02-18 17:03:07 +08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 				const s = JSON.stringify(str); | 
					
						
							| 
									
										
										
										
											2019-12-19 17:51:55 +08:00
										 |  |  | 				return s.slice(1, s.length - 1); | 
					
						
							| 
									
										
										
										
											2019-02-18 17:03:07 +08:00
										 |  |  | 			}; | 
					
						
							|  |  |  | 			const unquotedStringifyWithLength = value => length => | 
					
						
							|  |  |  | 				unquotedStringify(`${value}`.slice(0, length)); | 
					
						
							| 
									
										
										
										
											2019-06-19 04:09:10 +08:00
										 |  |  | 			const chunkFilenameValue = | 
					
						
							|  |  |  | 				typeof chunkFilename === "function" | 
					
						
							|  |  |  | 					? JSON.stringify( | 
					
						
							|  |  |  | 							chunkFilename({ | 
					
						
							|  |  |  | 								chunk: c, | 
					
						
							|  |  |  | 								contentHashType: contentType | 
					
						
							|  |  |  | 							}) | 
					
						
							|  |  |  | 					  ) | 
					
						
							|  |  |  | 					: JSON.stringify(chunkFilename); | 
					
						
							| 
									
										
										
										
											2019-10-04 18:24:52 +08:00
										 |  |  | 			const staticChunkFilename = compilation.getPath(chunkFilenameValue, { | 
					
						
							|  |  |  | 				hash: `" + ${RuntimeGlobals.getFullHash}() + "`, | 
					
						
							|  |  |  | 				hashWithLength: length => | 
					
						
							|  |  |  | 					`" + ${RuntimeGlobals.getFullHash}().slice(0, ${length}) + "`, | 
					
						
							|  |  |  | 				chunk: { | 
					
						
							|  |  |  | 					id: unquotedStringify(c.id), | 
					
						
							|  |  |  | 					hash: unquotedStringify(c.renderedHash), | 
					
						
							|  |  |  | 					hashWithLength: unquotedStringifyWithLength(c.renderedHash), | 
					
						
							|  |  |  | 					name: unquotedStringify(c.name || c.id), | 
					
						
							|  |  |  | 					contentHash: { | 
					
						
							|  |  |  | 						[contentType]: unquotedStringify(c.contentHash[contentType]) | 
					
						
							| 
									
										
										
										
											2019-02-18 17:03:07 +08:00
										 |  |  | 					}, | 
					
						
							| 
									
										
										
										
											2019-10-04 18:24:52 +08:00
										 |  |  | 					contentHashWithLength: { | 
					
						
							|  |  |  | 						[contentType]: unquotedStringifyWithLength( | 
					
						
							|  |  |  | 							c.contentHash[contentType] | 
					
						
							|  |  |  | 						) | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 				contentHashType: contentType | 
					
						
							|  |  |  | 			}); | 
					
						
							| 
									
										
										
										
											2019-02-18 17:03:07 +08:00
										 |  |  | 			let set = staticUrls.get(staticChunkFilename); | 
					
						
							|  |  |  | 			if (set === undefined) { | 
					
						
							|  |  |  | 				staticUrls.set(staticChunkFilename, (set = new Set())); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			set.add(c.id); | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		for (const [filename, chunks] of chunkFilenames) { | 
					
						
							|  |  |  | 			if (filename !== dynamicFilename) { | 
					
						
							|  |  |  | 				for (const c of chunks) addStaticUrl(c, filename); | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				for (const c of chunks) dynamicUrlChunks.add(c); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-19 04:09:10 +08:00
										 |  |  | 		/** | 
					
						
							|  |  |  | 		 * @param {function(Chunk): string | number} fn function from chunk to value | 
					
						
							|  |  |  | 		 * @returns {string} code with static mapping of results of fn | 
					
						
							|  |  |  | 		 */ | 
					
						
							| 
									
										
										
										
											2019-02-18 17:03:07 +08:00
										 |  |  | 		const createMap = fn => { | 
					
						
							|  |  |  | 			const obj = {}; | 
					
						
							|  |  |  | 			let useId = false; | 
					
						
							|  |  |  | 			let lastKey; | 
					
						
							|  |  |  | 			let entries = 0; | 
					
						
							|  |  |  | 			for (const c of dynamicUrlChunks) { | 
					
						
							|  |  |  | 				const value = fn(c); | 
					
						
							|  |  |  | 				if (value === c.id) { | 
					
						
							|  |  |  | 					useId = true; | 
					
						
							|  |  |  | 				} else { | 
					
						
							|  |  |  | 					obj[c.id] = value; | 
					
						
							|  |  |  | 					lastKey = c.id; | 
					
						
							|  |  |  | 					entries++; | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if (entries === 0) return "chunkId"; | 
					
						
							|  |  |  | 			if (entries === 1) { | 
					
						
							|  |  |  | 				return useId | 
					
						
							|  |  |  | 					? `(chunkId === ${JSON.stringify(lastKey)} ? ${JSON.stringify( | 
					
						
							|  |  |  | 							obj[lastKey] | 
					
						
							|  |  |  | 					  )} : chunkId)`
 | 
					
						
							|  |  |  | 					: JSON.stringify(obj[lastKey]); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return useId | 
					
						
							|  |  |  | 				? `(${JSON.stringify(obj)}[chunkId] || chunkId)` | 
					
						
							|  |  |  | 				: `${JSON.stringify(obj)}[chunkId]`; | 
					
						
							|  |  |  | 		}; | 
					
						
							| 
									
										
										
										
											2019-06-19 04:09:10 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		/** | 
					
						
							|  |  |  | 		 * @param {function(Chunk): string | number} fn function from chunk to value | 
					
						
							|  |  |  | 		 * @returns {string} code with static mapping of results of fn for including in quoted string | 
					
						
							|  |  |  | 		 */ | 
					
						
							| 
									
										
										
										
											2019-02-18 17:03:07 +08:00
										 |  |  | 		const mapExpr = fn => { | 
					
						
							|  |  |  | 			return `" + ${createMap(fn)} + "`; | 
					
						
							|  |  |  | 		}; | 
					
						
							| 
									
										
										
										
											2019-06-19 04:09:10 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		/** | 
					
						
							|  |  |  | 		 * @param {function(Chunk): string | number} fn function from chunk to value | 
					
						
							|  |  |  | 		 * @returns {function(number): string} function which generates code with static mapping of results of fn for including in quoted string for specific length | 
					
						
							|  |  |  | 		 */ | 
					
						
							| 
									
										
										
										
											2019-02-18 17:03:07 +08:00
										 |  |  | 		const mapExprWithLength = fn => length => { | 
					
						
							|  |  |  | 			return `" + ${createMap(c => `${fn(c)}`.slice(0, length))} + "`; | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		const url = | 
					
						
							|  |  |  | 			dynamicFilename && | 
					
						
							| 
									
										
										
										
											2019-10-04 18:24:52 +08:00
										 |  |  | 			compilation.getPath(JSON.stringify(dynamicFilename), { | 
					
						
							| 
									
										
										
										
											2019-02-18 17:03:07 +08:00
										 |  |  | 				hash: `" + ${RuntimeGlobals.getFullHash}() + "`, | 
					
						
							|  |  |  | 				hashWithLength: length => | 
					
						
							|  |  |  | 					`" + ${RuntimeGlobals.getFullHash}().slice(0, ${length}) + "`, | 
					
						
							|  |  |  | 				chunk: { | 
					
						
							|  |  |  | 					id: `" + chunkId + "`, | 
					
						
							|  |  |  | 					hash: mapExpr(c => c.renderedHash), | 
					
						
							|  |  |  | 					hashWithLength: mapExprWithLength(c => c.renderedHash), | 
					
						
							|  |  |  | 					name: mapExpr(c => c.name || c.id), | 
					
						
							|  |  |  | 					contentHash: { | 
					
						
							|  |  |  | 						[contentType]: mapExpr(c => c.contentHash[contentType]) | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 					contentHashWithLength: { | 
					
						
							|  |  |  | 						[contentType]: mapExprWithLength(c => c.contentHash[contentType]) | 
					
						
							| 
									
										
										
										
											2018-11-21 18:32:10 +08:00
										 |  |  | 					} | 
					
						
							|  |  |  | 				}, | 
					
						
							| 
									
										
										
										
											2019-02-18 17:03:07 +08:00
										 |  |  | 				contentHashType: contentType | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-21 18:32:10 +08:00
										 |  |  | 		return Template.asString([ | 
					
						
							| 
									
										
										
										
											2019-02-18 17:03:07 +08:00
										 |  |  | 			`// This function allow to reference ${includedChunksMessages.join( | 
					
						
							|  |  |  | 				" and " | 
					
						
							|  |  |  | 			)}`,
 | 
					
						
							| 
									
										
										
										
											2019-08-27 02:21:07 +08:00
										 |  |  | 			`${global} = ${runtimeTemplate.basicFunction( | 
					
						
							|  |  |  | 				"chunkId", | 
					
						
							| 
									
										
										
										
											2019-02-18 17:03:07 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-08-27 02:21:07 +08:00
										 |  |  | 				staticUrls.size > 0 | 
					
						
							|  |  |  | 					? [ | 
					
						
							|  |  |  | 							"// return url for filenames not based on template", | 
					
						
							|  |  |  | 							// it minimizes to `x===1?"...":x===2?"...":"..."`
 | 
					
						
							|  |  |  | 							Template.asString( | 
					
						
							|  |  |  | 								Array.from(staticUrls, ([url, ids]) => { | 
					
						
							|  |  |  | 									const condition = | 
					
						
							|  |  |  | 										ids.size === 1 | 
					
						
							|  |  |  | 											? `chunkId === ${JSON.stringify( | 
					
						
							|  |  |  | 													ids.values().next().value | 
					
						
							|  |  |  | 											  )}`
 | 
					
						
							|  |  |  | 											: `{${Array.from( | 
					
						
							|  |  |  | 													ids, | 
					
						
							|  |  |  | 													id => `${JSON.stringify(id)}:1` | 
					
						
							|  |  |  | 											  ).join(",")}}[chunkId]`;
 | 
					
						
							|  |  |  | 									return `if (${condition}) return ${url};`; | 
					
						
							|  |  |  | 								}) | 
					
						
							|  |  |  | 							), | 
					
						
							|  |  |  | 							"// return url for filenames based on template", | 
					
						
							|  |  |  | 							`return ${url};` | 
					
						
							|  |  |  | 					  ] | 
					
						
							|  |  |  | 					: ["// return url for filenames based on template", `return ${url};`] | 
					
						
							|  |  |  | 			)};`
 | 
					
						
							| 
									
										
										
										
											2018-11-21 18:32:10 +08:00
										 |  |  | 		]); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module.exports = GetChunkFilenameRuntimeModule; |