mirror of https://github.com/webpack/webpack.git
				
				
				
			
		
			
				
	
	
		
			283 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			283 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
/*
 | 
						|
	MIT License http://www.opensource.org/licenses/mit-license.php
 | 
						|
*/
 | 
						|
 | 
						|
"use strict";
 | 
						|
 | 
						|
const RuntimeGlobals = require("../RuntimeGlobals");
 | 
						|
const RuntimeModule = require("../RuntimeModule");
 | 
						|
const Template = require("../Template");
 | 
						|
const { first } = require("../util/SetHelpers");
 | 
						|
 | 
						|
/** @typedef {import("../Chunk")} Chunk */
 | 
						|
/** @typedef {import("../Compilation")} Compilation */
 | 
						|
/** @typedef {import("../Compilation").AssetInfo} AssetInfo */
 | 
						|
/** @typedef {import("../Compilation").PathData} PathData */
 | 
						|
 | 
						|
/** @typedef {function(PathData, AssetInfo=): string} FilenameFunction */
 | 
						|
 | 
						|
class GetChunkFilenameRuntimeModule extends RuntimeModule {
 | 
						|
	/**
 | 
						|
	 * @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
 | 
						|
	 */
 | 
						|
	constructor(contentType, name, global, getFilenameForChunk, allChunks) {
 | 
						|
		super(`get ${name} chunk filename`);
 | 
						|
		this.contentType = contentType;
 | 
						|
		this.global = global;
 | 
						|
		this.getFilenameForChunk = getFilenameForChunk;
 | 
						|
		this.allChunks = allChunks;
 | 
						|
		this.dependentHash = true;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * @returns {string} runtime code
 | 
						|
	 */
 | 
						|
	generate() {
 | 
						|
		const {
 | 
						|
			global,
 | 
						|
			chunk,
 | 
						|
			chunkGraph,
 | 
						|
			contentType,
 | 
						|
			getFilenameForChunk,
 | 
						|
			allChunks,
 | 
						|
			compilation
 | 
						|
		} = this;
 | 
						|
		const { runtimeTemplate } = compilation;
 | 
						|
 | 
						|
		/** @type {Map<string | FilenameFunction, Set<Chunk>>} */
 | 
						|
		const chunkFilenames = new Map();
 | 
						|
		let maxChunks = 0;
 | 
						|
		/** @type {string} */
 | 
						|
		let dynamicFilename;
 | 
						|
 | 
						|
		/**
 | 
						|
		 * @param {Chunk} c the chunk
 | 
						|
		 * @returns {void}
 | 
						|
		 */
 | 
						|
		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);
 | 
						|
				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;
 | 
						|
						}
 | 
						|
					}
 | 
						|
					maxChunks = set.size;
 | 
						|
					dynamicFilename = chunkFilename;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		};
 | 
						|
 | 
						|
		/** @type {string[]} */
 | 
						|
		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 = chunkGraph
 | 
						|
				.getTreeRuntimeRequirements(chunk)
 | 
						|
				.has(RuntimeGlobals.ensureChunkIncludeEntries);
 | 
						|
			if (includeEntries) {
 | 
						|
				includedChunksMessages.push("sibling chunks for the entrypoint");
 | 
						|
				for (const c of chunkGraph.getChunkEntryDependentChunksIterable(
 | 
						|
					chunk
 | 
						|
				)) {
 | 
						|
					addChunk(c);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		for (const entrypoint of chunk.getAllReferencedAsyncEntrypoints()) {
 | 
						|
			addChunk(entrypoint.chunks[entrypoint.chunks.length - 1]);
 | 
						|
		}
 | 
						|
 | 
						|
		/** @type {Map<string, Set<string | number>>} */
 | 
						|
		const staticUrls = new Map();
 | 
						|
		/** @type {Set<Chunk>} */
 | 
						|
		const dynamicUrlChunks = new Set();
 | 
						|
 | 
						|
		/**
 | 
						|
		 * @param {Chunk} c the chunk
 | 
						|
		 * @param {string | FilenameFunction} chunkFilename the filename template for the chunk
 | 
						|
		 * @returns {void}
 | 
						|
		 */
 | 
						|
		const addStaticUrl = (c, chunkFilename) => {
 | 
						|
			/**
 | 
						|
			 * @param {string | number} value a value
 | 
						|
			 * @returns {string} string to put in quotes
 | 
						|
			 */
 | 
						|
			const unquotedStringify = value => {
 | 
						|
				const str = `${value}`;
 | 
						|
				if (str.length >= 5 && str === `${c.id}`) {
 | 
						|
					// This is shorter and generates the same result
 | 
						|
					return '" + chunkId + "';
 | 
						|
				}
 | 
						|
				const s = JSON.stringify(str);
 | 
						|
				return s.slice(1, s.length - 1);
 | 
						|
			};
 | 
						|
			const unquotedStringifyWithLength = value => length =>
 | 
						|
				unquotedStringify(`${value}`.slice(0, length));
 | 
						|
			const chunkFilenameValue =
 | 
						|
				typeof chunkFilename === "function"
 | 
						|
					? JSON.stringify(
 | 
						|
							chunkFilename({
 | 
						|
								chunk: c,
 | 
						|
								contentHashType: contentType
 | 
						|
							})
 | 
						|
					  )
 | 
						|
					: JSON.stringify(chunkFilename);
 | 
						|
			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])
 | 
						|
					},
 | 
						|
					contentHashWithLength: {
 | 
						|
						[contentType]: unquotedStringifyWithLength(
 | 
						|
							c.contentHash[contentType]
 | 
						|
						)
 | 
						|
					}
 | 
						|
				},
 | 
						|
				contentHashType: contentType
 | 
						|
			});
 | 
						|
			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);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		/**
 | 
						|
		 * @param {function(Chunk): string | number} fn function from chunk to value
 | 
						|
		 * @returns {string} code with static mapping of results of fn
 | 
						|
		 */
 | 
						|
		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]`;
 | 
						|
		};
 | 
						|
 | 
						|
		/**
 | 
						|
		 * @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
 | 
						|
		 */
 | 
						|
		const mapExpr = fn => {
 | 
						|
			return `" + ${createMap(fn)} + "`;
 | 
						|
		};
 | 
						|
 | 
						|
		/**
 | 
						|
		 * @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
 | 
						|
		 */
 | 
						|
		const mapExprWithLength = fn => length => {
 | 
						|
			return `" + ${createMap(c => `${fn(c)}`.slice(0, length))} + "`;
 | 
						|
		};
 | 
						|
 | 
						|
		const url =
 | 
						|
			dynamicFilename &&
 | 
						|
			compilation.getPath(JSON.stringify(dynamicFilename), {
 | 
						|
				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])
 | 
						|
					}
 | 
						|
				},
 | 
						|
				contentHashType: contentType
 | 
						|
			});
 | 
						|
 | 
						|
		return Template.asString([
 | 
						|
			`// This function allow to reference ${includedChunksMessages.join(
 | 
						|
				" and "
 | 
						|
			)}`,
 | 
						|
			`${global} = ${runtimeTemplate.basicFunction(
 | 
						|
				"chunkId",
 | 
						|
 | 
						|
				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(first(ids))}`
 | 
						|
											: `{${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};`]
 | 
						|
			)};`
 | 
						|
		]);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
module.exports = GetChunkFilenameRuntimeModule;
 |