You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			404 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
		
		
			
		
	
	
			404 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
| 
											9 months ago
										 | /* | ||
|  | 	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 { compareModulesByIdentifier } = require("../util/comparators"); | ||
|  | const WebAssemblyUtils = require("./WebAssemblyUtils"); | ||
|  | 
 | ||
|  | /** @typedef {import("@webassemblyjs/ast").Signature} Signature */ | ||
|  | /** @typedef {import("../Chunk")} Chunk */ | ||
|  | /** @typedef {import("../ChunkGraph")} ChunkGraph */ | ||
|  | /** @typedef {import("../Compilation")} Compilation */ | ||
|  | /** @typedef {import("../Module")} Module */ | ||
|  | /** @typedef {import("../ModuleGraph")} ModuleGraph */ | ||
|  | /** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */ | ||
|  | 
 | ||
|  | // TODO webpack 6 remove the whole folder
 | ||
|  | 
 | ||
|  | // Get all wasm modules
 | ||
|  | /** | ||
|  |  * @param {ModuleGraph} moduleGraph the module graph | ||
|  |  * @param {ChunkGraph} chunkGraph the chunk graph | ||
|  |  * @param {Chunk} chunk the chunk | ||
|  |  * @returns {Module[]} all wasm modules | ||
|  |  */ | ||
|  | const getAllWasmModules = (moduleGraph, chunkGraph, chunk) => { | ||
|  | 	const wasmModules = chunk.getAllAsyncChunks(); | ||
|  | 	const array = []; | ||
|  | 	for (const chunk of wasmModules) { | ||
|  | 		for (const m of chunkGraph.getOrderedChunkModulesIterable( | ||
|  | 			chunk, | ||
|  | 			compareModulesByIdentifier | ||
|  | 		)) { | ||
|  | 			if (m.type.startsWith("webassembly")) { | ||
|  | 				array.push(m); | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	return array; | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * generates the import object function for a module | ||
|  |  * @param {ChunkGraph} chunkGraph the chunk graph | ||
|  |  * @param {Module} module the module | ||
|  |  * @param {boolean | undefined} mangle mangle imports | ||
|  |  * @param {string[]} declarations array where declarations are pushed to | ||
|  |  * @param {RuntimeSpec} runtime the runtime | ||
|  |  * @returns {string} source code | ||
|  |  */ | ||
|  | const generateImportObject = ( | ||
|  | 	chunkGraph, | ||
|  | 	module, | ||
|  | 	mangle, | ||
|  | 	declarations, | ||
|  | 	runtime | ||
|  | ) => { | ||
|  | 	const moduleGraph = chunkGraph.moduleGraph; | ||
|  | 	const waitForInstances = new Map(); | ||
|  | 	const properties = []; | ||
|  | 	const usedWasmDependencies = WebAssemblyUtils.getUsedDependencies( | ||
|  | 		moduleGraph, | ||
|  | 		module, | ||
|  | 		mangle | ||
|  | 	); | ||
|  | 	for (const usedDep of usedWasmDependencies) { | ||
|  | 		const dep = usedDep.dependency; | ||
|  | 		const importedModule = moduleGraph.getModule(dep); | ||
|  | 		const exportName = dep.name; | ||
|  | 		const usedName = | ||
|  | 			importedModule && | ||
|  | 			moduleGraph | ||
|  | 				.getExportsInfo(importedModule) | ||
|  | 				.getUsedName(exportName, runtime); | ||
|  | 		const description = dep.description; | ||
|  | 		const direct = dep.onlyDirectImport; | ||
|  | 
 | ||
|  | 		const module = usedDep.module; | ||
|  | 		const name = usedDep.name; | ||
|  | 
 | ||
|  | 		if (direct) { | ||
|  | 			const instanceVar = `m${waitForInstances.size}`; | ||
|  | 			waitForInstances.set(instanceVar, chunkGraph.getModuleId(importedModule)); | ||
|  | 			properties.push({ | ||
|  | 				module, | ||
|  | 				name, | ||
|  | 				value: `${instanceVar}[${JSON.stringify(usedName)}]` | ||
|  | 			}); | ||
|  | 		} else { | ||
|  | 			const params = | ||
|  | 				/** @type {Signature} */ | ||
|  | 				(description.signature).params.map( | ||
|  | 					(param, k) => "p" + k + param.valtype | ||
|  | 				); | ||
|  | 
 | ||
|  | 			const mod = `${RuntimeGlobals.moduleCache}[${JSON.stringify( | ||
|  | 				chunkGraph.getModuleId(importedModule) | ||
|  | 			)}]`;
 | ||
|  | 			const modExports = `${mod}.exports`; | ||
|  | 
 | ||
|  | 			const cache = `wasmImportedFuncCache${declarations.length}`; | ||
|  | 			declarations.push(`var ${cache};`); | ||
|  | 
 | ||
|  | 			properties.push({ | ||
|  | 				module, | ||
|  | 				name, | ||
|  | 				value: Template.asString([ | ||
|  | 					(importedModule.type.startsWith("webassembly") | ||
|  | 						? `${mod} ? ${modExports}[${JSON.stringify(usedName)}] : ` | ||
|  | 						: "") + `function(${params}) {`, | ||
|  | 					Template.indent([ | ||
|  | 						`if(${cache} === undefined) ${cache} = ${modExports};`, | ||
|  | 						`return ${cache}[${JSON.stringify(usedName)}](${params});` | ||
|  | 					]), | ||
|  | 					"}" | ||
|  | 				]) | ||
|  | 			}); | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	let importObject; | ||
|  | 	if (mangle) { | ||
|  | 		importObject = [ | ||
|  | 			"return {", | ||
|  | 			Template.indent([ | ||
|  | 				properties.map(p => `${JSON.stringify(p.name)}: ${p.value}`).join(",\n") | ||
|  | 			]), | ||
|  | 			"};" | ||
|  | 		]; | ||
|  | 	} else { | ||
|  | 		/** @type {Map<string, Array<{ name: string, value: string }>>} */ | ||
|  | 		const propertiesByModule = new Map(); | ||
|  | 		for (const p of properties) { | ||
|  | 			let list = propertiesByModule.get(p.module); | ||
|  | 			if (list === undefined) { | ||
|  | 				propertiesByModule.set(p.module, (list = [])); | ||
|  | 			} | ||
|  | 			list.push(p); | ||
|  | 		} | ||
|  | 		importObject = [ | ||
|  | 			"return {", | ||
|  | 			Template.indent([ | ||
|  | 				Array.from(propertiesByModule, ([module, list]) => { | ||
|  | 					return Template.asString([ | ||
|  | 						`${JSON.stringify(module)}: {`, | ||
|  | 						Template.indent([ | ||
|  | 							list.map(p => `${JSON.stringify(p.name)}: ${p.value}`).join(",\n") | ||
|  | 						]), | ||
|  | 						"}" | ||
|  | 					]); | ||
|  | 				}).join(",\n") | ||
|  | 			]), | ||
|  | 			"};" | ||
|  | 		]; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	const moduleIdStringified = JSON.stringify(chunkGraph.getModuleId(module)); | ||
|  | 	if (waitForInstances.size === 1) { | ||
|  | 		const moduleId = Array.from(waitForInstances.values())[0]; | ||
|  | 		const promise = `installedWasmModules[${JSON.stringify(moduleId)}]`; | ||
|  | 		const variable = Array.from(waitForInstances.keys())[0]; | ||
|  | 		return Template.asString([ | ||
|  | 			`${moduleIdStringified}: function() {`, | ||
|  | 			Template.indent([ | ||
|  | 				`return promiseResolve().then(function() { return ${promise}; }).then(function(${variable}) {`, | ||
|  | 				Template.indent(importObject), | ||
|  | 				"});" | ||
|  | 			]), | ||
|  | 			"}," | ||
|  | 		]); | ||
|  | 	} else if (waitForInstances.size > 0) { | ||
|  | 		const promises = Array.from( | ||
|  | 			waitForInstances.values(), | ||
|  | 			id => `installedWasmModules[${JSON.stringify(id)}]` | ||
|  | 		).join(", "); | ||
|  | 		const variables = Array.from( | ||
|  | 			waitForInstances.keys(), | ||
|  | 			(name, i) => `${name} = array[${i}]` | ||
|  | 		).join(", "); | ||
|  | 		return Template.asString([ | ||
|  | 			`${moduleIdStringified}: function() {`, | ||
|  | 			Template.indent([ | ||
|  | 				`return promiseResolve().then(function() { return Promise.all([${promises}]); }).then(function(array) {`, | ||
|  | 				Template.indent([`var ${variables};`, ...importObject]), | ||
|  | 				"});" | ||
|  | 			]), | ||
|  | 			"}," | ||
|  | 		]); | ||
|  | 	} else { | ||
|  | 		return Template.asString([ | ||
|  | 			`${moduleIdStringified}: function() {`, | ||
|  | 			Template.indent(importObject), | ||
|  | 			"}," | ||
|  | 		]); | ||
|  | 	} | ||
|  | }; | ||
|  | 
 | ||
|  | /** | ||
|  |  * @typedef {Object} WasmChunkLoadingRuntimeModuleOptions | ||
|  |  * @property {(path: string) => string} generateLoadBinaryCode | ||
|  |  * @property {boolean} [supportsStreaming] | ||
|  |  * @property {boolean} [mangleImports] | ||
|  |  * @property {Set<string>} runtimeRequirements | ||
|  |  */ | ||
|  | 
 | ||
|  | class WasmChunkLoadingRuntimeModule extends RuntimeModule { | ||
|  | 	/** | ||
|  | 	 * @param {WasmChunkLoadingRuntimeModuleOptions} options options | ||
|  | 	 */ | ||
|  | 	constructor({ | ||
|  | 		generateLoadBinaryCode, | ||
|  | 		supportsStreaming, | ||
|  | 		mangleImports, | ||
|  | 		runtimeRequirements | ||
|  | 	}) { | ||
|  | 		super("wasm chunk loading", RuntimeModule.STAGE_ATTACH); | ||
|  | 		this.generateLoadBinaryCode = generateLoadBinaryCode; | ||
|  | 		this.supportsStreaming = supportsStreaming; | ||
|  | 		this.mangleImports = mangleImports; | ||
|  | 		this._runtimeRequirements = runtimeRequirements; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 	 * @returns {string | null} runtime code | ||
|  | 	 */ | ||
|  | 	generate() { | ||
|  | 		const fn = RuntimeGlobals.ensureChunkHandlers; | ||
|  | 		const withHmr = this._runtimeRequirements.has( | ||
|  | 			RuntimeGlobals.hmrDownloadUpdateHandlers | ||
|  | 		); | ||
|  | 		const compilation = /** @type {Compilation} */ (this.compilation); | ||
|  | 		const { moduleGraph, outputOptions } = compilation; | ||
|  | 		const chunkGraph = /** @type {ChunkGraph} */ (this.chunkGraph); | ||
|  | 		const chunk = /** @type {Chunk} */ (this.chunk); | ||
|  | 		const wasmModules = getAllWasmModules(moduleGraph, chunkGraph, chunk); | ||
|  | 		const { mangleImports } = this; | ||
|  | 		/** @type {string[]} */ | ||
|  | 		const declarations = []; | ||
|  | 		const importObjects = wasmModules.map(module => { | ||
|  | 			return generateImportObject( | ||
|  | 				chunkGraph, | ||
|  | 				module, | ||
|  | 				mangleImports, | ||
|  | 				declarations, | ||
|  | 				chunk.runtime | ||
|  | 			); | ||
|  | 		}); | ||
|  | 		const chunkModuleIdMap = chunkGraph.getChunkModuleIdMap(chunk, m => | ||
|  | 			m.type.startsWith("webassembly") | ||
|  | 		); | ||
|  | 		/** | ||
|  | 		 * @param {string} content content | ||
|  | 		 * @returns {string} created import object | ||
|  | 		 */ | ||
|  | 		const createImportObject = content => | ||
|  | 			mangleImports | ||
|  | 				? `{ ${JSON.stringify(WebAssemblyUtils.MANGLED_MODULE)}: ${content} }` | ||
|  | 				: content; | ||
|  | 		const wasmModuleSrcPath = compilation.getPath( | ||
|  | 			JSON.stringify(outputOptions.webassemblyModuleFilename), | ||
|  | 			{ | ||
|  | 				hash: `" + ${RuntimeGlobals.getFullHash}() + "`, | ||
|  | 				hashWithLength: length => | ||
|  | 					`" + ${RuntimeGlobals.getFullHash}}().slice(0, ${length}) + "`, | ||
|  | 				module: { | ||
|  | 					id: '" + wasmModuleId + "', | ||
|  | 					hash: `" + ${JSON.stringify( | ||
|  | 						chunkGraph.getChunkModuleRenderedHashMap(chunk, m => | ||
|  | 							m.type.startsWith("webassembly") | ||
|  | 						) | ||
|  | 					)}[chunkId][wasmModuleId] + "`,
 | ||
|  | 					hashWithLength(length) { | ||
|  | 						return `" + ${JSON.stringify( | ||
|  | 							chunkGraph.getChunkModuleRenderedHashMap( | ||
|  | 								chunk, | ||
|  | 								m => m.type.startsWith("webassembly"), | ||
|  | 								length | ||
|  | 							) | ||
|  | 						)}[chunkId][wasmModuleId] + "`;
 | ||
|  | 					} | ||
|  | 				}, | ||
|  | 				runtime: chunk.runtime | ||
|  | 			} | ||
|  | 		); | ||
|  | 
 | ||
|  | 		const stateExpression = withHmr | ||
|  | 			? `${RuntimeGlobals.hmrRuntimeStatePrefix}_wasm` | ||
|  | 			: undefined; | ||
|  | 
 | ||
|  | 		return Template.asString([ | ||
|  | 			"// object to store loaded and loading wasm modules", | ||
|  | 			`var installedWasmModules = ${ | ||
|  | 				stateExpression ? `${stateExpression} = ${stateExpression} || ` : "" | ||
|  | 			}{};`,
 | ||
|  | 			"", | ||
|  | 			// This function is used to delay reading the installed wasm module promises
 | ||
|  | 			// by a microtask. Sorting them doesn't help because there are edge cases where
 | ||
|  | 			// sorting is not possible (modules splitted into different chunks).
 | ||
|  | 			// So we not even trying and solve this by a microtask delay.
 | ||
|  | 			"function promiseResolve() { return Promise.resolve(); }", | ||
|  | 			"", | ||
|  | 			Template.asString(declarations), | ||
|  | 			"var wasmImportObjects = {", | ||
|  | 			Template.indent(importObjects), | ||
|  | 			"};", | ||
|  | 			"", | ||
|  | 			`var wasmModuleMap = ${JSON.stringify( | ||
|  | 				chunkModuleIdMap, | ||
|  | 				undefined, | ||
|  | 				"\t" | ||
|  | 			)};`,
 | ||
|  | 			"", | ||
|  | 			"// object with all WebAssembly.instance exports", | ||
|  | 			`${RuntimeGlobals.wasmInstances} = {};`, | ||
|  | 			"", | ||
|  | 			"// Fetch + compile chunk loading for webassembly", | ||
|  | 			`${fn}.wasm = function(chunkId, promises) {`, | ||
|  | 			Template.indent([ | ||
|  | 				"", | ||
|  | 				`var wasmModules = wasmModuleMap[chunkId] || [];`, | ||
|  | 				"", | ||
|  | 				"wasmModules.forEach(function(wasmModuleId, idx) {", | ||
|  | 				Template.indent([ | ||
|  | 					"var installedWasmModuleData = installedWasmModules[wasmModuleId];", | ||
|  | 					"", | ||
|  | 					'// a Promise means "currently loading" or "already loaded".', | ||
|  | 					"if(installedWasmModuleData)", | ||
|  | 					Template.indent(["promises.push(installedWasmModuleData);"]), | ||
|  | 					"else {", | ||
|  | 					Template.indent([ | ||
|  | 						`var importObject = wasmImportObjects[wasmModuleId]();`, | ||
|  | 						`var req = ${this.generateLoadBinaryCode(wasmModuleSrcPath)};`, | ||
|  | 						"var promise;", | ||
|  | 						this.supportsStreaming | ||
|  | 							? Template.asString([ | ||
|  | 									"if(importObject && typeof importObject.then === 'function' && typeof WebAssembly.compileStreaming === 'function') {", | ||
|  | 									Template.indent([ | ||
|  | 										"promise = Promise.all([WebAssembly.compileStreaming(req), importObject]).then(function(items) {", | ||
|  | 										Template.indent([ | ||
|  | 											`return WebAssembly.instantiate(items[0], ${createImportObject( | ||
|  | 												"items[1]" | ||
|  | 											)});`
 | ||
|  | 										]), | ||
|  | 										"});" | ||
|  | 									]), | ||
|  | 									"} else if(typeof WebAssembly.instantiateStreaming === 'function') {", | ||
|  | 									Template.indent([ | ||
|  | 										`promise = WebAssembly.instantiateStreaming(req, ${createImportObject( | ||
|  | 											"importObject" | ||
|  | 										)});`
 | ||
|  | 									]) | ||
|  | 							  ]) | ||
|  | 							: Template.asString([ | ||
|  | 									"if(importObject && typeof importObject.then === 'function') {", | ||
|  | 									Template.indent([ | ||
|  | 										"var bytesPromise = req.then(function(x) { return x.arrayBuffer(); });", | ||
|  | 										"promise = Promise.all([", | ||
|  | 										Template.indent([ | ||
|  | 											"bytesPromise.then(function(bytes) { return WebAssembly.compile(bytes); }),", | ||
|  | 											"importObject" | ||
|  | 										]), | ||
|  | 										"]).then(function(items) {", | ||
|  | 										Template.indent([ | ||
|  | 											`return WebAssembly.instantiate(items[0], ${createImportObject( | ||
|  | 												"items[1]" | ||
|  | 											)});`
 | ||
|  | 										]), | ||
|  | 										"});" | ||
|  | 									]) | ||
|  | 							  ]), | ||
|  | 						"} else {", | ||
|  | 						Template.indent([ | ||
|  | 							"var bytesPromise = req.then(function(x) { return x.arrayBuffer(); });", | ||
|  | 							"promise = bytesPromise.then(function(bytes) {", | ||
|  | 							Template.indent([ | ||
|  | 								`return WebAssembly.instantiate(bytes, ${createImportObject( | ||
|  | 									"importObject" | ||
|  | 								)});`
 | ||
|  | 							]), | ||
|  | 							"});" | ||
|  | 						]), | ||
|  | 						"}", | ||
|  | 						"promises.push(installedWasmModules[wasmModuleId] = promise.then(function(res) {", | ||
|  | 						Template.indent([ | ||
|  | 							`return ${RuntimeGlobals.wasmInstances}[wasmModuleId] = (res.instance || res).exports;` | ||
|  | 						]), | ||
|  | 						"}));" | ||
|  | 					]), | ||
|  | 					"}" | ||
|  | 				]), | ||
|  | 				"});" | ||
|  | 			]), | ||
|  | 			"};" | ||
|  | 		]); | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | module.exports = WasmChunkLoadingRuntimeModule; |