mirror of https://github.com/webpack/webpack.git
				
				
				
			
		
			
				
	
	
		
			376 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			376 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
| /*
 | |
| 	MIT License http://www.opensource.org/licenses/mit-license.php
 | |
| 	Author Tobias Koppers @sokra
 | |
| */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| const Template = require("../Template");
 | |
| const { compareModulesById } = require("../util/comparators");
 | |
| const WebAssemblyUtils = require("./WebAssemblyUtils");
 | |
| 
 | |
| /** @typedef {import("../ChunkGraph")} ChunkGraph */
 | |
| /** @typedef {import("../Compilation")} Compilation */
 | |
| /** @typedef {import("../MainTemplate")} MainTemplate */
 | |
| /** @typedef {import("../Module")} Module */
 | |
| /** @typedef {import("../ModuleGraph")} ModuleGraph */
 | |
| 
 | |
| // Get 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,
 | |
| 			compareModulesById(chunkGraph)
 | |
| 		)) {
 | |
| 			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} mangle mangle imports
 | |
|  * @returns {string} source code
 | |
|  */
 | |
| const generateImportObject = (chunkGraph, module, mangle) => {
 | |
| 	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 && importedModule.getUsedName(moduleGraph, 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, chunkGraph.getModuleId(importedModule));
 | |
| 			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(
 | |
| 				chunkGraph.getModuleId(importedModule)
 | |
| 			)}]`;
 | |
| 			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")
 | |
| 			]),
 | |
| 			"};"
 | |
| 		];
 | |
| 	}
 | |
| 
 | |
| 	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),
 | |
| 			"},"
 | |
| 		]);
 | |
| 	}
 | |
| };
 | |
| 
 | |
| class WasmMainTemplatePlugin {
 | |
| 	/**
 | |
| 	 * @param {Compilation} compilation the compilation
 | |
| 	 * @param {Object} options options
 | |
| 	 * @param {function(string): string} options.generateLoadBinaryCode function to generate the load code
 | |
| 	 * @param {boolean=} options.supportsStreaming whether the generateLoadBinaryCode function supports streaming compilation
 | |
| 	 * @param {boolean=} options.mangleImports whether imports should be mangled
 | |
| 	 */
 | |
| 	constructor(
 | |
| 		compilation,
 | |
| 		{ generateLoadBinaryCode, supportsStreaming, mangleImports }
 | |
| 	) {
 | |
| 		this.compilation = compilation;
 | |
| 		this.generateLoadBinaryCode = generateLoadBinaryCode;
 | |
| 		this.supportsStreaming = supportsStreaming;
 | |
| 		this.mangleImports = mangleImports;
 | |
| 	}
 | |
| 
 | |
| 	/**
 | |
| 	 * @param {MainTemplate} mainTemplate main template
 | |
| 	 * @returns {void}
 | |
| 	 */
 | |
| 	apply(mainTemplate) {
 | |
| 		const moduleGraph = this.compilation.moduleGraph;
 | |
| 		mainTemplate.hooks.localVars.tap(
 | |
| 			"WasmMainTemplatePlugin",
 | |
| 			(source, chunk) => {
 | |
| 				const chunkGraph = this.compilation.chunkGraph;
 | |
| 				const wasmModules = getAllWasmModules(moduleGraph, chunkGraph, chunk);
 | |
| 				if (wasmModules.length === 0) return source;
 | |
| 				const importObjects = wasmModules.map(module => {
 | |
| 					return generateImportObject(chunkGraph, 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, chunkIdExpression, { chunk, hash }) => {
 | |
| 				const webassemblyModuleFilename =
 | |
| 					mainTemplate.outputOptions.webassemblyModuleFilename;
 | |
| 
 | |
| 				const chunkModuleMaps = this.compilation.chunkGraph.getChunkModuleMaps(
 | |
| 					chunk,
 | |
| 					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
 | |
| 					)}[${chunkIdExpression}] || [];`,
 | |
| 					"",
 | |
| 					"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 __webpack_require__.w[wasmModuleId] = (res.instance || res).exports;`
 | |
| 							]),
 | |
| 							"}));"
 | |
| 						]),
 | |
| 						"}"
 | |
| 					]),
 | |
| 					"});"
 | |
| 				]);
 | |
| 			}
 | |
| 		);
 | |
| 		mainTemplate.hooks.requireExtensions.tap(
 | |
| 			"WasmMainTemplatePlugin",
 | |
| 			(source, { chunk }) => {
 | |
| 				const chunkGraph = this.compilation.chunkGraph;
 | |
| 				if (
 | |
| 					!chunkGraph.hasModuleInGraph(chunk, m =>
 | |
| 						m.type.startsWith("webassembly")
 | |
| 					)
 | |
| 				) {
 | |
| 					return source;
 | |
| 				}
 | |
| 				return Template.asString([
 | |
| 					source,
 | |
| 					"",
 | |
| 					"// object with all WebAssembly.instance exports",
 | |
| 					`__webpack_require__.w = {};`
 | |
| 				]);
 | |
| 			}
 | |
| 		);
 | |
| 		mainTemplate.hooks.hash.tap("WasmMainTemplatePlugin", hash => {
 | |
| 			hash.update("WasmMainTemplatePlugin");
 | |
| 			hash.update("2");
 | |
| 		});
 | |
| 	}
 | |
| }
 | |
| 
 | |
| module.exports = WasmMainTemplatePlugin;
 |