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