1 |
|
2 |
|
3 |
|
4 |
|
5 | "use strict";
|
6 |
|
7 | const Template = require("../Template");
|
8 | const WebAssemblyUtils = require("./WebAssemblyUtils");
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 | const getAllWasmModules = chunk => {
|
15 | const wasmModules = chunk.getAllAsyncChunks();
|
16 | const array = [];
|
17 | for (const chunk of wasmModules) {
|
18 | for (const m of chunk.modulesIterable) {
|
19 | if (m.type.startsWith("webassembly")) {
|
20 | array.push(m);
|
21 | }
|
22 | }
|
23 | }
|
24 |
|
25 | return array;
|
26 | };
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 | const generateImportObject = (module, mangle) => {
|
35 | const waitForInstances = new Map();
|
36 | const properties = [];
|
37 | const usedWasmDependencies = WebAssemblyUtils.getUsedDependencies(
|
38 | module,
|
39 | mangle
|
40 | );
|
41 | for (const usedDep of usedWasmDependencies) {
|
42 | const dep = usedDep.dependency;
|
43 | const importedModule = dep.module;
|
44 | const exportName = dep.name;
|
45 | const usedName = importedModule && importedModule.isUsed(exportName);
|
46 | const description = dep.description;
|
47 | const direct = dep.onlyDirectImport;
|
48 |
|
49 | const module = usedDep.module;
|
50 | const name = usedDep.name;
|
51 |
|
52 | if (direct) {
|
53 | const instanceVar = `m${waitForInstances.size}`;
|
54 | waitForInstances.set(instanceVar, importedModule.id);
|
55 | properties.push({
|
56 | module,
|
57 | name,
|
58 | value: `${instanceVar}[${JSON.stringify(usedName)}]`
|
59 | });
|
60 | } else {
|
61 | const params = description.signature.params.map(
|
62 | (param, k) => "p" + k + param.valtype
|
63 | );
|
64 |
|
65 | const mod = `installedModules[${JSON.stringify(importedModule.id)}]`;
|
66 | const func = `${mod}.exports[${JSON.stringify(usedName)}]`;
|
67 |
|
68 | properties.push({
|
69 | module,
|
70 | name,
|
71 | value: Template.asString([
|
72 | (importedModule.type.startsWith("webassembly")
|
73 | ? `${mod} ? ${func} : `
|
74 | : "") + `function(${params}) {`,
|
75 | Template.indent([`return ${func}(${params});`]),
|
76 | "}"
|
77 | ])
|
78 | });
|
79 | }
|
80 | }
|
81 |
|
82 | let importObject;
|
83 | if (mangle) {
|
84 | importObject = [
|
85 | "return {",
|
86 | Template.indent([
|
87 | properties.map(p => `${JSON.stringify(p.name)}: ${p.value}`).join(",\n")
|
88 | ]),
|
89 | "};"
|
90 | ];
|
91 | } else {
|
92 | const propertiesByModule = new Map();
|
93 | for (const p of properties) {
|
94 | let list = propertiesByModule.get(p.module);
|
95 | if (list === undefined) {
|
96 | propertiesByModule.set(p.module, (list = []));
|
97 | }
|
98 | list.push(p);
|
99 | }
|
100 | importObject = [
|
101 | "return {",
|
102 | Template.indent([
|
103 | Array.from(propertiesByModule, ([module, list]) => {
|
104 | return Template.asString([
|
105 | `${JSON.stringify(module)}: {`,
|
106 | Template.indent([
|
107 | list.map(p => `${JSON.stringify(p.name)}: ${p.value}`).join(",\n")
|
108 | ]),
|
109 | "}"
|
110 | ]);
|
111 | }).join(",\n")
|
112 | ]),
|
113 | "};"
|
114 | ];
|
115 | }
|
116 |
|
117 | if (waitForInstances.size === 1) {
|
118 | const moduleId = Array.from(waitForInstances.values())[0];
|
119 | const promise = `installedWasmModules[${JSON.stringify(moduleId)}]`;
|
120 | const variable = Array.from(waitForInstances.keys())[0];
|
121 | return Template.asString([
|
122 | `${JSON.stringify(module.id)}: function() {`,
|
123 | Template.indent([
|
124 | `return promiseResolve().then(function() { return ${promise}; }).then(function(${variable}) {`,
|
125 | Template.indent(importObject),
|
126 | "});"
|
127 | ]),
|
128 | "},"
|
129 | ]);
|
130 | } else if (waitForInstances.size > 0) {
|
131 | const promises = Array.from(
|
132 | waitForInstances.values(),
|
133 | id => `installedWasmModules[${JSON.stringify(id)}]`
|
134 | ).join(", ");
|
135 | const variables = Array.from(
|
136 | waitForInstances.keys(),
|
137 | (name, i) => `${name} = array[${i}]`
|
138 | ).join(", ");
|
139 | return Template.asString([
|
140 | `${JSON.stringify(module.id)}: function() {`,
|
141 | Template.indent([
|
142 | `return promiseResolve().then(function() { return Promise.all([${promises}]); }).then(function(array) {`,
|
143 | Template.indent([`var ${variables};`, ...importObject]),
|
144 | "});"
|
145 | ]),
|
146 | "},"
|
147 | ]);
|
148 | } else {
|
149 | return Template.asString([
|
150 | `${JSON.stringify(module.id)}: function() {`,
|
151 | Template.indent(importObject),
|
152 | "},"
|
153 | ]);
|
154 | }
|
155 | };
|
156 |
|
157 | class WasmMainTemplatePlugin {
|
158 | constructor({ generateLoadBinaryCode, supportsStreaming, mangleImports }) {
|
159 | this.generateLoadBinaryCode = generateLoadBinaryCode;
|
160 | this.supportsStreaming = supportsStreaming;
|
161 | this.mangleImports = mangleImports;
|
162 | }
|
163 |
|
164 | |
165 |
|
166 |
|
167 |
|
168 | apply(mainTemplate) {
|
169 | mainTemplate.hooks.localVars.tap(
|
170 | "WasmMainTemplatePlugin",
|
171 | (source, chunk) => {
|
172 | const wasmModules = getAllWasmModules(chunk);
|
173 | if (wasmModules.length === 0) return source;
|
174 | const importObjects = wasmModules.map(module => {
|
175 | return generateImportObject(module, this.mangleImports);
|
176 | });
|
177 | return Template.asString([
|
178 | source,
|
179 | "",
|
180 | "// object to store loaded and loading wasm modules",
|
181 | "var installedWasmModules = {};",
|
182 | "",
|
183 |
|
184 |
|
185 |
|
186 |
|
187 | "function promiseResolve() { return Promise.resolve(); }",
|
188 | "",
|
189 | "var wasmImportObjects = {",
|
190 | Template.indent(importObjects),
|
191 | "};"
|
192 | ]);
|
193 | }
|
194 | );
|
195 | mainTemplate.hooks.requireEnsure.tap(
|
196 | "WasmMainTemplatePlugin",
|
197 | (source, chunk, hash) => {
|
198 | const webassemblyModuleFilename =
|
199 | mainTemplate.outputOptions.webassemblyModuleFilename;
|
200 |
|
201 | const chunkModuleMaps = chunk.getChunkModuleMaps(m =>
|
202 | m.type.startsWith("webassembly")
|
203 | );
|
204 | if (Object.keys(chunkModuleMaps.id).length === 0) return source;
|
205 | const wasmModuleSrcPath = mainTemplate.getAssetPath(
|
206 | JSON.stringify(webassemblyModuleFilename),
|
207 | {
|
208 | hash: `" + ${mainTemplate.renderCurrentHashCode(hash)} + "`,
|
209 | hashWithLength: length =>
|
210 | `" + ${mainTemplate.renderCurrentHashCode(hash, length)} + "`,
|
211 | module: {
|
212 | id: '" + wasmModuleId + "',
|
213 | hash: `" + ${JSON.stringify(
|
214 | chunkModuleMaps.hash
|
215 | )}[wasmModuleId] + "`,
|
216 | hashWithLength(length) {
|
217 | const shortChunkHashMap = Object.create(null);
|
218 | for (const wasmModuleId of Object.keys(chunkModuleMaps.hash)) {
|
219 | if (typeof chunkModuleMaps.hash[wasmModuleId] === "string") {
|
220 | shortChunkHashMap[wasmModuleId] = chunkModuleMaps.hash[
|
221 | wasmModuleId
|
222 | ].substr(0, length);
|
223 | }
|
224 | }
|
225 | return `" + ${JSON.stringify(
|
226 | shortChunkHashMap
|
227 | )}[wasmModuleId] + "`;
|
228 | }
|
229 | }
|
230 | }
|
231 | );
|
232 | const createImportObject = content =>
|
233 | this.mangleImports
|
234 | ? `{ ${JSON.stringify(
|
235 | WebAssemblyUtils.MANGLED_MODULE
|
236 | )}: ${content} }`
|
237 | : content;
|
238 | return Template.asString([
|
239 | source,
|
240 | "",
|
241 | "// Fetch + compile chunk loading for webassembly",
|
242 | "",
|
243 | `var wasmModules = ${JSON.stringify(
|
244 | chunkModuleMaps.id
|
245 | )}[chunkId] || [];`,
|
246 | "",
|
247 | "wasmModules.forEach(function(wasmModuleId) {",
|
248 | Template.indent([
|
249 | "var installedWasmModuleData = installedWasmModules[wasmModuleId];",
|
250 | "",
|
251 | '// a Promise means "currently loading" or "already loaded".',
|
252 | "if(installedWasmModuleData)",
|
253 | Template.indent(["promises.push(installedWasmModuleData);"]),
|
254 | "else {",
|
255 | Template.indent([
|
256 | `var importObject = wasmImportObjects[wasmModuleId]();`,
|
257 | `var req = ${this.generateLoadBinaryCode(wasmModuleSrcPath)};`,
|
258 | "var promise;",
|
259 | this.supportsStreaming
|
260 | ? Template.asString([
|
261 | "if(importObject instanceof Promise && typeof WebAssembly.compileStreaming === 'function') {",
|
262 | Template.indent([
|
263 | "promise = Promise.all([WebAssembly.compileStreaming(req), importObject]).then(function(items) {",
|
264 | Template.indent([
|
265 | `return WebAssembly.instantiate(items[0], ${createImportObject(
|
266 | "items[1]"
|
267 | )});`
|
268 | ]),
|
269 | "});"
|
270 | ]),
|
271 | "} else if(typeof WebAssembly.instantiateStreaming === 'function') {",
|
272 | Template.indent([
|
273 | `promise = WebAssembly.instantiateStreaming(req, ${createImportObject(
|
274 | "importObject"
|
275 | )});`
|
276 | ])
|
277 | ])
|
278 | : Template.asString([
|
279 | "if(importObject instanceof Promise) {",
|
280 | Template.indent([
|
281 | "var bytesPromise = req.then(function(x) { return x.arrayBuffer(); });",
|
282 | "promise = Promise.all([",
|
283 | Template.indent([
|
284 | "bytesPromise.then(function(bytes) { return WebAssembly.compile(bytes); }),",
|
285 | "importObject"
|
286 | ]),
|
287 | "]).then(function(items) {",
|
288 | Template.indent([
|
289 | `return WebAssembly.instantiate(items[0], ${createImportObject(
|
290 | "items[1]"
|
291 | )});`
|
292 | ]),
|
293 | "});"
|
294 | ])
|
295 | ]),
|
296 | "} else {",
|
297 | Template.indent([
|
298 | "var bytesPromise = req.then(function(x) { return x.arrayBuffer(); });",
|
299 | "promise = bytesPromise.then(function(bytes) {",
|
300 | Template.indent([
|
301 | `return WebAssembly.instantiate(bytes, ${createImportObject(
|
302 | "importObject"
|
303 | )});`
|
304 | ]),
|
305 | "});"
|
306 | ]),
|
307 | "}",
|
308 | "promises.push(installedWasmModules[wasmModuleId] = promise.then(function(res) {",
|
309 | Template.indent([
|
310 | `return ${
|
311 | mainTemplate.requireFn
|
312 | }.w[wasmModuleId] = (res.instance || res).exports;`
|
313 | ]),
|
314 | "}));"
|
315 | ]),
|
316 | "}"
|
317 | ]),
|
318 | "});"
|
319 | ]);
|
320 | }
|
321 | );
|
322 | mainTemplate.hooks.requireExtensions.tap(
|
323 | "WasmMainTemplatePlugin",
|
324 | (source, chunk) => {
|
325 | if (!chunk.hasModuleInGraph(m => m.type.startsWith("webassembly"))) {
|
326 | return source;
|
327 | }
|
328 | return Template.asString([
|
329 | source,
|
330 | "",
|
331 | "// object with all WebAssembly.instance exports",
|
332 | `${mainTemplate.requireFn}.w = {};`
|
333 | ]);
|
334 | }
|
335 | );
|
336 | mainTemplate.hooks.hash.tap("WasmMainTemplatePlugin", hash => {
|
337 | hash.update("WasmMainTemplatePlugin");
|
338 | hash.update("2");
|
339 | });
|
340 | }
|
341 | }
|
342 |
|
343 | module.exports = WasmMainTemplatePlugin;
|