| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491 | /*	MIT License http://www.opensource.org/licenses/mit-license.php	Author Sergey Melyukov @smelukov*/"use strict";const mimeTypes = require("mime-types");const path = require("path");const { RawSource } = require("webpack-sources");const ConcatenationScope = require("../ConcatenationScope");const Generator = require("../Generator");const RuntimeGlobals = require("../RuntimeGlobals");const createHash = require("../util/createHash");const { makePathsRelative } = require("../util/identifier");const nonNumericOnlyHash = require("../util/nonNumericOnlyHash");/** @typedef {import("webpack-sources").Source} Source *//** @typedef {import("../../declarations/WebpackOptions").AssetGeneratorOptions} AssetGeneratorOptions *//** @typedef {import("../../declarations/WebpackOptions").AssetModuleOutputPath} AssetModuleOutputPath *//** @typedef {import("../../declarations/WebpackOptions").RawPublicPath} RawPublicPath *//** @typedef {import("../Compilation")} Compilation *//** @typedef {import("../Compiler")} Compiler *//** @typedef {import("../Generator").GenerateContext} GenerateContext *//** @typedef {import("../Generator").UpdateHashContext} UpdateHashContext *//** @typedef {import("../Module")} Module *//** @typedef {import("../Module").ConcatenationBailoutReasonContext} ConcatenationBailoutReasonContext *//** @typedef {import("../NormalModule")} NormalModule *//** @typedef {import("../RuntimeTemplate")} RuntimeTemplate *//** @typedef {import("../util/Hash")} Hash */const mergeMaybeArrays = (a, b) => {	const set = new Set();	if (Array.isArray(a)) for (const item of a) set.add(item);	else set.add(a);	if (Array.isArray(b)) for (const item of b) set.add(item);	else set.add(b);	return Array.from(set);};const mergeAssetInfo = (a, b) => {	const result = { ...a, ...b };	for (const key of Object.keys(a)) {		if (key in b) {			if (a[key] === b[key]) continue;			switch (key) {				case "fullhash":				case "chunkhash":				case "modulehash":				case "contenthash":					result[key] = mergeMaybeArrays(a[key], b[key]);					break;				case "immutable":				case "development":				case "hotModuleReplacement":				case "javascriptModule":					result[key] = a[key] || b[key];					break;				case "related":					result[key] = mergeRelatedInfo(a[key], b[key]);					break;				default:					throw new Error(`Can't handle conflicting asset info for ${key}`);			}		}	}	return result;};const mergeRelatedInfo = (a, b) => {	const result = { ...a, ...b };	for (const key of Object.keys(a)) {		if (key in b) {			if (a[key] === b[key]) continue;			result[key] = mergeMaybeArrays(a[key], b[key]);		}	}	return result;};const encodeDataUri = (encoding, source) => {	let encodedContent;	switch (encoding) {		case "base64": {			encodedContent = source.buffer().toString("base64");			break;		}		case false: {			const content = source.source();			if (typeof content !== "string") {				encodedContent = content.toString("utf-8");			}			encodedContent = encodeURIComponent(encodedContent).replace(				/[!'()*]/g,				character => "%" + character.codePointAt(0).toString(16)			);			break;		}		default:			throw new Error(`Unsupported encoding '${encoding}'`);	}	return encodedContent;};const decodeDataUriContent = (encoding, content) => {	const isBase64 = encoding === "base64";	return isBase64		? Buffer.from(content, "base64")		: Buffer.from(decodeURIComponent(content), "ascii");};const JS_TYPES = new Set(["javascript"]);const JS_AND_ASSET_TYPES = new Set(["javascript", "asset"]);const DEFAULT_ENCODING = "base64";class AssetGenerator extends Generator {	/**	 * @param {AssetGeneratorOptions["dataUrl"]=} dataUrlOptions the options for the data url	 * @param {string=} filename override for output.assetModuleFilename	 * @param {RawPublicPath=} publicPath override for output.assetModulePublicPath	 * @param {AssetModuleOutputPath=} outputPath the output path for the emitted file which is not included in the runtime import	 * @param {boolean=} emit generate output asset	 */	constructor(dataUrlOptions, filename, publicPath, outputPath, emit) {		super();		this.dataUrlOptions = dataUrlOptions;		this.filename = filename;		this.publicPath = publicPath;		this.outputPath = outputPath;		this.emit = emit;	}	/**	 * @param {NormalModule} module module	 * @param {RuntimeTemplate} runtimeTemplate runtime template	 * @returns {string} source file name	 */	getSourceFileName(module, runtimeTemplate) {		return makePathsRelative(			runtimeTemplate.compilation.compiler.context,			module.matchResource || module.resource,			runtimeTemplate.compilation.compiler.root		).replace(/^\.\//, "");	}	/**	 * @param {NormalModule} module module for which the bailout reason should be determined	 * @param {ConcatenationBailoutReasonContext} context context	 * @returns {string | undefined} reason why this module can't be concatenated, undefined when it can be concatenated	 */	getConcatenationBailoutReason(module, context) {		return undefined;	}	/**	 * @param {NormalModule} module module	 * @returns {string} mime type	 */	getMimeType(module) {		if (typeof this.dataUrlOptions === "function") {			throw new Error(				"This method must not be called when dataUrlOptions is a function"			);		}		let mimeType = this.dataUrlOptions.mimetype;		if (mimeType === undefined) {			const ext = path.extname(module.nameForCondition());			if (				module.resourceResolveData &&				module.resourceResolveData.mimetype !== undefined			) {				mimeType =					module.resourceResolveData.mimetype +					module.resourceResolveData.parameters;			} else if (ext) {				mimeType = mimeTypes.lookup(ext);				if (typeof mimeType !== "string") {					throw new Error(						"DataUrl can't be generated automatically, " +							`because there is no mimetype for "${ext}" in mimetype database. ` +							'Either pass a mimetype via "generator.mimetype" or ' +							'use type: "asset/resource" to create a resource file instead of a DataUrl'					);				}			}		}		if (typeof mimeType !== "string") {			throw new Error(				"DataUrl can't be generated automatically. " +					'Either pass a mimetype via "generator.mimetype" or ' +					'use type: "asset/resource" to create a resource file instead of a DataUrl'			);		}		return mimeType;	}	/**	 * @param {NormalModule} module module for which the code should be generated	 * @param {GenerateContext} generateContext context for generate	 * @returns {Source} generated code	 */	generate(		module,		{			runtime,			concatenationScope,			chunkGraph,			runtimeTemplate,			runtimeRequirements,			type,			getData		}	) {		switch (type) {			case "asset":				return module.originalSource();			default: {				let content;				const originalSource = module.originalSource();				if (module.buildInfo.dataUrl) {					let encodedSource;					if (typeof this.dataUrlOptions === "function") {						encodedSource = this.dataUrlOptions.call(							null,							originalSource.source(),							{								filename: module.matchResource || module.resource,								module							}						);					} else {						/** @type {string | false | undefined} */						let encoding = this.dataUrlOptions.encoding;						if (encoding === undefined) {							if (								module.resourceResolveData &&								module.resourceResolveData.encoding !== undefined							) {								encoding = module.resourceResolveData.encoding;							}						}						if (encoding === undefined) {							encoding = DEFAULT_ENCODING;						}						const mimeType = this.getMimeType(module);						let encodedContent;						if (							module.resourceResolveData &&							module.resourceResolveData.encoding === encoding &&							decodeDataUriContent(								module.resourceResolveData.encoding,								module.resourceResolveData.encodedContent							).equals(originalSource.buffer())						) {							encodedContent = module.resourceResolveData.encodedContent;						} else {							encodedContent = encodeDataUri(encoding, originalSource);						}						encodedSource = `data:${mimeType}${							encoding ? `;${encoding}` : ""						},${encodedContent}`;					}					const data = getData();					data.set("url", Buffer.from(encodedSource));					content = JSON.stringify(encodedSource);				} else {					const assetModuleFilename =						this.filename || runtimeTemplate.outputOptions.assetModuleFilename;					const hash = createHash(runtimeTemplate.outputOptions.hashFunction);					if (runtimeTemplate.outputOptions.hashSalt) {						hash.update(runtimeTemplate.outputOptions.hashSalt);					}					hash.update(originalSource.buffer());					const fullHash = /** @type {string} */ (						hash.digest(runtimeTemplate.outputOptions.hashDigest)					);					const contentHash = nonNumericOnlyHash(						fullHash,						runtimeTemplate.outputOptions.hashDigestLength					);					module.buildInfo.fullContentHash = fullHash;					const sourceFilename = this.getSourceFileName(						module,						runtimeTemplate					);					let { path: filename, info: assetInfo } =						runtimeTemplate.compilation.getAssetPathWithInfo(							assetModuleFilename,							{								module,								runtime,								filename: sourceFilename,								chunkGraph,								contentHash							}						);					let assetPath;					if (this.publicPath !== undefined) {						const { path, info } =							runtimeTemplate.compilation.getAssetPathWithInfo(								this.publicPath,								{									module,									runtime,									filename: sourceFilename,									chunkGraph,									contentHash								}							);						assetInfo = mergeAssetInfo(assetInfo, info);						assetPath = JSON.stringify(path + filename);					} else {						runtimeRequirements.add(RuntimeGlobals.publicPath); // add __webpack_require__.p						assetPath = runtimeTemplate.concatenation(							{ expr: RuntimeGlobals.publicPath },							filename						);					}					assetInfo = {						sourceFilename,						...assetInfo					};					if (this.outputPath) {						const { path: outputPath, info } =							runtimeTemplate.compilation.getAssetPathWithInfo(								this.outputPath,								{									module,									runtime,									filename: sourceFilename,									chunkGraph,									contentHash								}							);						assetInfo = mergeAssetInfo(assetInfo, info);						filename = path.posix.join(outputPath, filename);					}					module.buildInfo.filename = filename;					module.buildInfo.assetInfo = assetInfo;					if (getData) {						// Due to code generation caching module.buildInfo.XXX can't used to store such information						// It need to be stored in the code generation results instead, where it's cached too						// TODO webpack 6 For back-compat reasons we also store in on module.buildInfo						const data = getData();						data.set("fullContentHash", fullHash);						data.set("filename", filename);						data.set("assetInfo", assetInfo);					}					content = assetPath;				}				if (concatenationScope) {					concatenationScope.registerNamespaceExport(						ConcatenationScope.NAMESPACE_OBJECT_EXPORT					);					return new RawSource(						`${runtimeTemplate.supportsConst() ? "const" : "var"} ${							ConcatenationScope.NAMESPACE_OBJECT_EXPORT						} = ${content};`					);				} else {					runtimeRequirements.add(RuntimeGlobals.module);					return new RawSource(						`${RuntimeGlobals.module}.exports = ${content};`					);				}			}		}	}	/**	 * @param {NormalModule} module fresh module	 * @returns {Set<string>} available types (do not mutate)	 */	getTypes(module) {		if ((module.buildInfo && module.buildInfo.dataUrl) || this.emit === false) {			return JS_TYPES;		} else {			return JS_AND_ASSET_TYPES;		}	}	/**	 * @param {NormalModule} module the module	 * @param {string=} type source type	 * @returns {number} estimate size of the module	 */	getSize(module, type) {		switch (type) {			case "asset": {				const originalSource = module.originalSource();				if (!originalSource) {					return 0;				}				return originalSource.size();			}			default:				if (module.buildInfo && module.buildInfo.dataUrl) {					const originalSource = module.originalSource();					if (!originalSource) {						return 0;					}					// roughly for data url					// Example: m.exports="data:image/png;base64,ag82/f+2=="					// 4/3 = base64 encoding					// 34 = ~ data url header + footer + rounding					return originalSource.size() * 1.34 + 36;				} else {					// it's only estimated so this number is probably fine					// Example: m.exports=r.p+"0123456789012345678901.ext"					return 42;				}		}	}	/**	 * @param {Hash} hash hash that will be modified	 * @param {UpdateHashContext} updateHashContext context for updating hash	 */	updateHash(hash, { module, runtime, runtimeTemplate, chunkGraph }) {		if (module.buildInfo.dataUrl) {			hash.update("data-url");			// this.dataUrlOptions as function should be pure and only depend on input source and filename			// therefore it doesn't need to be hashed			if (typeof this.dataUrlOptions === "function") {				const ident = /** @type {{ ident?: string }} */ (this.dataUrlOptions)					.ident;				if (ident) hash.update(ident);			} else {				if (					this.dataUrlOptions.encoding &&					this.dataUrlOptions.encoding !== DEFAULT_ENCODING				) {					hash.update(this.dataUrlOptions.encoding);				}				if (this.dataUrlOptions.mimetype)					hash.update(this.dataUrlOptions.mimetype);				// computed mimetype depends only on module filename which is already part of the hash			}		} else {			hash.update("resource");			const pathData = {				module,				runtime,				filename: this.getSourceFileName(module, runtimeTemplate),				chunkGraph,				contentHash: runtimeTemplate.contentHashReplacement			};			if (typeof this.publicPath === "function") {				hash.update("path");				const assetInfo = {};				hash.update(this.publicPath(pathData, assetInfo));				hash.update(JSON.stringify(assetInfo));			} else if (this.publicPath) {				hash.update("path");				hash.update(this.publicPath);			} else {				hash.update("no-path");			}			const assetModuleFilename =				this.filename || runtimeTemplate.outputOptions.assetModuleFilename;			const { path: filename, info } =				runtimeTemplate.compilation.getAssetPathWithInfo(					assetModuleFilename,					pathData				);			hash.update(filename);			hash.update(JSON.stringify(info));		}	}}module.exports = AssetGenerator;
 |