UNPKG

10.3 kBJavaScriptView Raw
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5"use strict";
6
7const Template = require("../Template");
8const WebAssemblyUtils = require("./WebAssemblyUtils");
9
10/** @typedef {import("../Module")} Module */
11/** @typedef {import("../MainTemplate")} MainTemplate */
12
13// Get all wasm modules
14const 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 * generates the import object function for a module
30 * @param {Module} module the module
31 * @param {boolean} mangle mangle imports
32 * @returns {string} source code
33 */
34const 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
157class WasmMainTemplatePlugin {
158 constructor({ generateLoadBinaryCode, supportsStreaming, mangleImports }) {
159 this.generateLoadBinaryCode = generateLoadBinaryCode;
160 this.supportsStreaming = supportsStreaming;
161 this.mangleImports = mangleImports;
162 }
163
164 /**
165 * @param {MainTemplate} mainTemplate main template
166 * @returns {void}
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 // This function is used to delay reading the installed wasm module promises
184 // by a microtask. Sorting them doesn't help because there are egdecases where
185 // sorting is not possible (modules splitted into different chunks).
186 // So we not even trying and solve this by a microtask delay.
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
343module.exports = WasmMainTemplatePlugin;