You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							341 lines
						
					
					
						
							10 KiB
						
					
					
				
			
		
		
	
	
							341 lines
						
					
					
						
							10 KiB
						
					
					
				/* | 
						|
	MIT License http://www.opensource.org/licenses/mit-license.php | 
						|
	Author Tobias Koppers @sokra | 
						|
*/ | 
						|
"use strict"; | 
						|
 | 
						|
const Template = require("../Template"); | 
						|
const WebAssemblyUtils = require("./WebAssemblyUtils"); | 
						|
 | 
						|
/** @typedef {import("../Module")} Module */ | 
						|
/** @typedef {import("../MainTemplate")} MainTemplate */ | 
						|
 | 
						|
// Get all wasm modules | 
						|
const getAllWasmModules = chunk => { | 
						|
	const wasmModules = chunk.getAllAsyncChunks(); | 
						|
	const array = []; | 
						|
	for (const chunk of wasmModules) { | 
						|
		for (const m of chunk.modulesIterable) { | 
						|
			if (m.type.startsWith("webassembly")) { | 
						|
				array.push(m); | 
						|
			} | 
						|
		} | 
						|
	} | 
						|
 | 
						|
	return array; | 
						|
}; | 
						|
 | 
						|
/** | 
						|
 * generates the import object function for a module | 
						|
 * @param {Module} module the module | 
						|
 * @param {boolean} mangle mangle imports | 
						|
 * @returns {string} source code | 
						|
 */ | 
						|
const generateImportObject = (module, mangle) => { | 
						|
	const waitForInstances = new Map(); | 
						|
	const properties = []; | 
						|
	const usedWasmDependencies = WebAssemblyUtils.getUsedDependencies( | 
						|
		module, | 
						|
		mangle | 
						|
	); | 
						|
	for (const usedDep of usedWasmDependencies) { | 
						|
		const dep = usedDep.dependency; | 
						|
		const importedModule = dep.module; | 
						|
		const exportName = dep.name; | 
						|
		const usedName = importedModule && importedModule.isUsed(exportName); | 
						|
		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, importedModule.id); | 
						|
			properties.push({ | 
						|
				module, | 
						|
				name, | 
						|
				value: `${instanceVar}[${JSON.stringify(usedName)}]` | 
						|
			}); | 
						|
		} else { | 
						|
			const params = description.signature.params.map( | 
						|
				(param, k) => "p" + k + param.valtype | 
						|
			); | 
						|
 | 
						|
			const mod = `installedModules[${JSON.stringify(importedModule.id)}]`; | 
						|
			const func = `${mod}.exports[${JSON.stringify(usedName)}]`; | 
						|
 | 
						|
			properties.push({ | 
						|
				module, | 
						|
				name, | 
						|
				value: Template.asString([ | 
						|
					(importedModule.type.startsWith("webassembly") | 
						|
						? `${mod} ? ${func} : ` | 
						|
						: "") + `function(${params}) {`, | 
						|
					Template.indent([`return ${func}(${params});`]), | 
						|
					"}" | 
						|
				]) | 
						|
			}); | 
						|
		} | 
						|
	} | 
						|
 | 
						|
	let importObject; | 
						|
	if (mangle) { | 
						|
		importObject = [ | 
						|
			"return {", | 
						|
			Template.indent([ | 
						|
				properties.map(p => `${JSON.stringify(p.name)}: ${p.value}`).join(",\n") | 
						|
			]), | 
						|
			"};" | 
						|
		]; | 
						|
	} else { | 
						|
		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") | 
						|
			]), | 
						|
			"};" | 
						|
		]; | 
						|
	} | 
						|
 | 
						|
	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([ | 
						|
			`${JSON.stringify(module.id)}: 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([ | 
						|
			`${JSON.stringify(module.id)}: function() {`, | 
						|
			Template.indent([ | 
						|
				`return promiseResolve().then(function() { return Promise.all([${promises}]); }).then(function(array) {`, | 
						|
				Template.indent([`var ${variables};`, ...importObject]), | 
						|
				"});" | 
						|
			]), | 
						|
			"}," | 
						|
		]); | 
						|
	} else { | 
						|
		return Template.asString([ | 
						|
			`${JSON.stringify(module.id)}: function() {`, | 
						|
			Template.indent(importObject), | 
						|
			"}," | 
						|
		]); | 
						|
	} | 
						|
}; | 
						|
 | 
						|
class WasmMainTemplatePlugin { | 
						|
	constructor({ generateLoadBinaryCode, supportsStreaming, mangleImports }) { | 
						|
		this.generateLoadBinaryCode = generateLoadBinaryCode; | 
						|
		this.supportsStreaming = supportsStreaming; | 
						|
		this.mangleImports = mangleImports; | 
						|
	} | 
						|
 | 
						|
	/** | 
						|
	 * @param {MainTemplate} mainTemplate main template | 
						|
	 * @returns {void} | 
						|
	 */ | 
						|
	apply(mainTemplate) { | 
						|
		mainTemplate.hooks.localVars.tap( | 
						|
			"WasmMainTemplatePlugin", | 
						|
			(source, chunk) => { | 
						|
				const wasmModules = getAllWasmModules(chunk); | 
						|
				if (wasmModules.length === 0) return source; | 
						|
				const importObjects = wasmModules.map(module => { | 
						|
					return generateImportObject(module, this.mangleImports); | 
						|
				}); | 
						|
				return Template.asString([ | 
						|
					source, | 
						|
					"", | 
						|
					"// object to store loaded and loading wasm modules", | 
						|
					"var installedWasmModules = {};", | 
						|
					"", | 
						|
					// This function is used to delay reading the installed wasm module promises | 
						|
					// by a microtask. Sorting them doesn't help because there are egdecases 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(); }", | 
						|
					"", | 
						|
					"var wasmImportObjects = {", | 
						|
					Template.indent(importObjects), | 
						|
					"};" | 
						|
				]); | 
						|
			} | 
						|
		); | 
						|
		mainTemplate.hooks.requireEnsure.tap( | 
						|
			"WasmMainTemplatePlugin", | 
						|
			(source, chunk, hash) => { | 
						|
				const webassemblyModuleFilename = | 
						|
					mainTemplate.outputOptions.webassemblyModuleFilename; | 
						|
 | 
						|
				const chunkModuleMaps = chunk.getChunkModuleMaps(m => | 
						|
					m.type.startsWith("webassembly") | 
						|
				); | 
						|
				if (Object.keys(chunkModuleMaps.id).length === 0) return source; | 
						|
				const wasmModuleSrcPath = mainTemplate.getAssetPath( | 
						|
					JSON.stringify(webassemblyModuleFilename), | 
						|
					{ | 
						|
						hash: `" + ${mainTemplate.renderCurrentHashCode(hash)} + "`, | 
						|
						hashWithLength: length => | 
						|
							`" + ${mainTemplate.renderCurrentHashCode(hash, length)} + "`, | 
						|
						module: { | 
						|
							id: '" + wasmModuleId + "', | 
						|
							hash: `" + ${JSON.stringify( | 
						|
								chunkModuleMaps.hash | 
						|
							)}[wasmModuleId] + "`, | 
						|
							hashWithLength(length) { | 
						|
								const shortChunkHashMap = Object.create(null); | 
						|
								for (const wasmModuleId of Object.keys(chunkModuleMaps.hash)) { | 
						|
									if (typeof chunkModuleMaps.hash[wasmModuleId] === "string") { | 
						|
										shortChunkHashMap[wasmModuleId] = chunkModuleMaps.hash[ | 
						|
											wasmModuleId | 
						|
										].substr(0, length); | 
						|
									} | 
						|
								} | 
						|
								return `" + ${JSON.stringify( | 
						|
									shortChunkHashMap | 
						|
								)}[wasmModuleId] + "`; | 
						|
							} | 
						|
						} | 
						|
					} | 
						|
				); | 
						|
				const createImportObject = content => | 
						|
					this.mangleImports | 
						|
						? `{ ${JSON.stringify( | 
						|
								WebAssemblyUtils.MANGLED_MODULE | 
						|
						  )}: ${content} }` | 
						|
						: content; | 
						|
				return Template.asString([ | 
						|
					source, | 
						|
					"", | 
						|
					"// Fetch + compile chunk loading for webassembly", | 
						|
					"", | 
						|
					`var wasmModules = ${JSON.stringify( | 
						|
						chunkModuleMaps.id | 
						|
					)}[chunkId] || [];`, | 
						|
					"", | 
						|
					"wasmModules.forEach(function(wasmModuleId) {", | 
						|
					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 instanceof Promise && 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 instanceof Promise) {", | 
						|
										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 ${mainTemplate.requireFn}.w[wasmModuleId] = (res.instance || res).exports;` | 
						|
							]), | 
						|
							"}));" | 
						|
						]), | 
						|
						"}" | 
						|
					]), | 
						|
					"});" | 
						|
				]); | 
						|
			} | 
						|
		); | 
						|
		mainTemplate.hooks.requireExtensions.tap( | 
						|
			"WasmMainTemplatePlugin", | 
						|
			(source, chunk) => { | 
						|
				if (!chunk.hasModuleInGraph(m => m.type.startsWith("webassembly"))) { | 
						|
					return source; | 
						|
				} | 
						|
				return Template.asString([ | 
						|
					source, | 
						|
					"", | 
						|
					"// object with all WebAssembly.instance exports", | 
						|
					`${mainTemplate.requireFn}.w = {};` | 
						|
				]); | 
						|
			} | 
						|
		); | 
						|
		mainTemplate.hooks.hash.tap("WasmMainTemplatePlugin", hash => { | 
						|
			hash.update("WasmMainTemplatePlugin"); | 
						|
			hash.update("2"); | 
						|
		}); | 
						|
	} | 
						|
} | 
						|
 | 
						|
module.exports = WasmMainTemplatePlugin;
 | 
						|
 |