123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 |
- /*
- 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("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */
- /** @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;
- /** @type {Map<string, string | number>} */
- 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(/** @type {Module} */ (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(/** @type {Module} */ (importedModule))
- )}]`;
- const modExports = `${mod}.exports`;
- const cache = `wasmImportedFuncCache${declarations.length}`;
- declarations.push(`var ${cache};`);
- const modCode =
- /** @type {Module} */
- (importedModule).type.startsWith("webassembly")
- ? `${mod} ? ${modExports}[${JSON.stringify(usedName)}] : `
- : "";
- properties.push({
- module,
- name,
- value: Template.asString([
- modCode + `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 {ReadOnlyRuntimeRequirements} 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;
|