UNPKG

11.1 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3*/
4
5"use strict";
6
7const RuntimeGlobals = require("../RuntimeGlobals");
8const RuntimeModule = require("../RuntimeModule");
9const Template = require("../Template");
10const { compareModulesByIdentifier } = require("../util/comparators");
11const WebAssemblyUtils = require("./WebAssemblyUtils");
12
13/** @typedef {import("../ChunkGraph")} ChunkGraph */
14/** @typedef {import("../Compilation")} Compilation */
15/** @typedef {import("../Module")} Module */
16/** @typedef {import("../util/runtime").RuntimeSpec} RuntimeSpec */
17
18// TODO webpack 6 remove the whole folder
19
20// Get all wasm modules
21const getAllWasmModules = (moduleGraph, chunkGraph, chunk) => {
22 const wasmModules = chunk.getAllAsyncChunks();
23 const array = [];
24 for (const chunk of wasmModules) {
25 for (const m of chunkGraph.getOrderedChunkModulesIterable(
26 chunk,
27 compareModulesByIdentifier
28 )) {
29 if (m.type.startsWith("webassembly")) {
30 array.push(m);
31 }
32 }
33 }
34
35 return array;
36};
37
38/**
39 * generates the import object function for a module
40 * @param {ChunkGraph} chunkGraph the chunk graph
41 * @param {Module} module the module
42 * @param {boolean} mangle mangle imports
43 * @param {string[]} declarations array where declarations are pushed to
44 * @param {RuntimeSpec} runtime the runtime
45 * @returns {string} source code
46 */
47const generateImportObject = (
48 chunkGraph,
49 module,
50 mangle,
51 declarations,
52 runtime
53) => {
54 const moduleGraph = chunkGraph.moduleGraph;
55 const waitForInstances = new Map();
56 const properties = [];
57 const usedWasmDependencies = WebAssemblyUtils.getUsedDependencies(
58 moduleGraph,
59 module,
60 mangle
61 );
62 for (const usedDep of usedWasmDependencies) {
63 const dep = usedDep.dependency;
64 const importedModule = moduleGraph.getModule(dep);
65 const exportName = dep.name;
66 const usedName =
67 importedModule &&
68 moduleGraph
69 .getExportsInfo(importedModule)
70 .getUsedName(exportName, runtime);
71 const description = dep.description;
72 const direct = dep.onlyDirectImport;
73
74 const module = usedDep.module;
75 const name = usedDep.name;
76
77 if (direct) {
78 const instanceVar = `m${waitForInstances.size}`;
79 waitForInstances.set(instanceVar, chunkGraph.getModuleId(importedModule));
80 properties.push({
81 module,
82 name,
83 value: `${instanceVar}[${JSON.stringify(usedName)}]`
84 });
85 } else {
86 const params = description.signature.params.map(
87 (param, k) => "p" + k + param.valtype
88 );
89
90 const mod = `${RuntimeGlobals.moduleCache}[${JSON.stringify(
91 chunkGraph.getModuleId(importedModule)
92 )}]`;
93 const modExports = `${mod}.exports`;
94
95 const cache = `wasmImportedFuncCache${declarations.length}`;
96 declarations.push(`var ${cache};`);
97
98 properties.push({
99 module,
100 name,
101 value: Template.asString([
102 (importedModule.type.startsWith("webassembly")
103 ? `${mod} ? ${modExports}[${JSON.stringify(usedName)}] : `
104 : "") + `function(${params}) {`,
105 Template.indent([
106 `if(${cache} === undefined) ${cache} = ${modExports};`,
107 `return ${cache}[${JSON.stringify(usedName)}](${params});`
108 ]),
109 "}"
110 ])
111 });
112 }
113 }
114
115 let importObject;
116 if (mangle) {
117 importObject = [
118 "return {",
119 Template.indent([
120 properties.map(p => `${JSON.stringify(p.name)}: ${p.value}`).join(",\n")
121 ]),
122 "};"
123 ];
124 } else {
125 const propertiesByModule = new Map();
126 for (const p of properties) {
127 let list = propertiesByModule.get(p.module);
128 if (list === undefined) {
129 propertiesByModule.set(p.module, (list = []));
130 }
131 list.push(p);
132 }
133 importObject = [
134 "return {",
135 Template.indent([
136 Array.from(propertiesByModule, ([module, list]) => {
137 return Template.asString([
138 `${JSON.stringify(module)}: {`,
139 Template.indent([
140 list.map(p => `${JSON.stringify(p.name)}: ${p.value}`).join(",\n")
141 ]),
142 "}"
143 ]);
144 }).join(",\n")
145 ]),
146 "};"
147 ];
148 }
149
150 const moduleIdStringified = JSON.stringify(chunkGraph.getModuleId(module));
151 if (waitForInstances.size === 1) {
152 const moduleId = Array.from(waitForInstances.values())[0];
153 const promise = `installedWasmModules[${JSON.stringify(moduleId)}]`;
154 const variable = Array.from(waitForInstances.keys())[0];
155 return Template.asString([
156 `${moduleIdStringified}: function() {`,
157 Template.indent([
158 `return promiseResolve().then(function() { return ${promise}; }).then(function(${variable}) {`,
159 Template.indent(importObject),
160 "});"
161 ]),
162 "},"
163 ]);
164 } else if (waitForInstances.size > 0) {
165 const promises = Array.from(
166 waitForInstances.values(),
167 id => `installedWasmModules[${JSON.stringify(id)}]`
168 ).join(", ");
169 const variables = Array.from(
170 waitForInstances.keys(),
171 (name, i) => `${name} = array[${i}]`
172 ).join(", ");
173 return Template.asString([
174 `${moduleIdStringified}: function() {`,
175 Template.indent([
176 `return promiseResolve().then(function() { return Promise.all([${promises}]); }).then(function(array) {`,
177 Template.indent([`var ${variables};`, ...importObject]),
178 "});"
179 ]),
180 "},"
181 ]);
182 } else {
183 return Template.asString([
184 `${moduleIdStringified}: function() {`,
185 Template.indent(importObject),
186 "},"
187 ]);
188 }
189};
190
191class WasmChunkLoadingRuntimeModule extends RuntimeModule {
192 constructor({
193 generateLoadBinaryCode,
194 supportsStreaming,
195 mangleImports,
196 runtimeRequirements
197 }) {
198 super("wasm chunk loading", RuntimeModule.STAGE_ATTACH);
199 this.generateLoadBinaryCode = generateLoadBinaryCode;
200 this.supportsStreaming = supportsStreaming;
201 this.mangleImports = mangleImports;
202 this._runtimeRequirements = runtimeRequirements;
203 }
204
205 /**
206 * @returns {string} runtime code
207 */
208 generate() {
209 const { chunkGraph, compilation, chunk, mangleImports } = this;
210 const { moduleGraph, outputOptions } = compilation;
211 const fn = RuntimeGlobals.ensureChunkHandlers;
212 const withHmr = this._runtimeRequirements.has(
213 RuntimeGlobals.hmrDownloadUpdateHandlers
214 );
215 const wasmModules = getAllWasmModules(moduleGraph, chunkGraph, chunk);
216 const declarations = [];
217 const importObjects = wasmModules.map(module => {
218 return generateImportObject(
219 chunkGraph,
220 module,
221 this.mangleImports,
222 declarations,
223 chunk.runtime
224 );
225 });
226 const chunkModuleIdMap = chunkGraph.getChunkModuleIdMap(chunk, m =>
227 m.type.startsWith("webassembly")
228 );
229 const createImportObject = content =>
230 mangleImports
231 ? `{ ${JSON.stringify(WebAssemblyUtils.MANGLED_MODULE)}: ${content} }`
232 : content;
233 const wasmModuleSrcPath = compilation.getPath(
234 JSON.stringify(outputOptions.webassemblyModuleFilename),
235 {
236 hash: `" + ${RuntimeGlobals.getFullHash}() + "`,
237 hashWithLength: length =>
238 `" + ${RuntimeGlobals.getFullHash}}().slice(0, ${length}) + "`,
239 module: {
240 id: '" + wasmModuleId + "',
241 hash: `" + ${JSON.stringify(
242 chunkGraph.getChunkModuleRenderedHashMap(chunk, m =>
243 m.type.startsWith("webassembly")
244 )
245 )}[chunkId][wasmModuleId] + "`,
246 hashWithLength(length) {
247 return `" + ${JSON.stringify(
248 chunkGraph.getChunkModuleRenderedHashMap(
249 chunk,
250 m => m.type.startsWith("webassembly"),
251 length
252 )
253 )}[chunkId][wasmModuleId] + "`;
254 }
255 },
256 runtime: chunk.runtime
257 }
258 );
259
260 const stateExpression = withHmr
261 ? `${RuntimeGlobals.hmrRuntimeStatePrefix}_wasm`
262 : undefined;
263
264 return Template.asString([
265 "// object to store loaded and loading wasm modules",
266 `var installedWasmModules = ${
267 stateExpression ? `${stateExpression} = ${stateExpression} || ` : ""
268 }{};`,
269 "",
270 // This function is used to delay reading the installed wasm module promises
271 // by a microtask. Sorting them doesn't help because there are edge cases where
272 // sorting is not possible (modules splitted into different chunks).
273 // So we not even trying and solve this by a microtask delay.
274 "function promiseResolve() { return Promise.resolve(); }",
275 "",
276 Template.asString(declarations),
277 "var wasmImportObjects = {",
278 Template.indent(importObjects),
279 "};",
280 "",
281 `var wasmModuleMap = ${JSON.stringify(
282 chunkModuleIdMap,
283 undefined,
284 "\t"
285 )};`,
286 "",
287 "// object with all WebAssembly.instance exports",
288 `${RuntimeGlobals.wasmInstances} = {};`,
289 "",
290 "// Fetch + compile chunk loading for webassembly",
291 `${fn}.wasm = function(chunkId, promises) {`,
292 Template.indent([
293 "",
294 `var wasmModules = wasmModuleMap[chunkId] || [];`,
295 "",
296 "wasmModules.forEach(function(wasmModuleId, idx) {",
297 Template.indent([
298 "var installedWasmModuleData = installedWasmModules[wasmModuleId];",
299 "",
300 '// a Promise means "currently loading" or "already loaded".',
301 "if(installedWasmModuleData)",
302 Template.indent(["promises.push(installedWasmModuleData);"]),
303 "else {",
304 Template.indent([
305 `var importObject = wasmImportObjects[wasmModuleId]();`,
306 `var req = ${this.generateLoadBinaryCode(wasmModuleSrcPath)};`,
307 "var promise;",
308 this.supportsStreaming
309 ? Template.asString([
310 "if(importObject && typeof importObject.then === 'function' && typeof WebAssembly.compileStreaming === 'function') {",
311 Template.indent([
312 "promise = Promise.all([WebAssembly.compileStreaming(req), importObject]).then(function(items) {",
313 Template.indent([
314 `return WebAssembly.instantiate(items[0], ${createImportObject(
315 "items[1]"
316 )});`
317 ]),
318 "});"
319 ]),
320 "} else if(typeof WebAssembly.instantiateStreaming === 'function') {",
321 Template.indent([
322 `promise = WebAssembly.instantiateStreaming(req, ${createImportObject(
323 "importObject"
324 )});`
325 ])
326 ])
327 : Template.asString([
328 "if(importObject && typeof importObject.then === 'function') {",
329 Template.indent([
330 "var bytesPromise = req.then(function(x) { return x.arrayBuffer(); });",
331 "promise = Promise.all([",
332 Template.indent([
333 "bytesPromise.then(function(bytes) { return WebAssembly.compile(bytes); }),",
334 "importObject"
335 ]),
336 "]).then(function(items) {",
337 Template.indent([
338 `return WebAssembly.instantiate(items[0], ${createImportObject(
339 "items[1]"
340 )});`
341 ]),
342 "});"
343 ])
344 ]),
345 "} else {",
346 Template.indent([
347 "var bytesPromise = req.then(function(x) { return x.arrayBuffer(); });",
348 "promise = bytesPromise.then(function(bytes) {",
349 Template.indent([
350 `return WebAssembly.instantiate(bytes, ${createImportObject(
351 "importObject"
352 )});`
353 ]),
354 "});"
355 ]),
356 "}",
357 "promises.push(installedWasmModules[wasmModuleId] = promise.then(function(res) {",
358 Template.indent([
359 `return ${RuntimeGlobals.wasmInstances}[wasmModuleId] = (res.instance || res).exports;`
360 ]),
361 "}));"
362 ]),
363 "}"
364 ]),
365 "});"
366 ]),
367 "};"
368 ]);
369 }
370}
371
372module.exports = WasmChunkLoadingRuntimeModule;