| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 | /*	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 modulesconst 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;
 |