Skip to content

Commit c2f1c4f

Browse files
committed
refactor: use base plugin for web and node
1 parent d2dd6c1 commit c2f1c4f

3 files changed

Lines changed: 238 additions & 191 deletions

File tree

lib/BaseWasmMainTemplatePlugin.js

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
/*
2+
MIT License http://www.opensource.org/licenses/mit-license.php
3+
Author Tobias Koppers @sokra
4+
*/
5+
"use strict";
6+
7+
const Template = require("./Template");
8+
const WebAssemblyImportDependency = require("./dependencies/WebAssemblyImportDependency");
9+
10+
// Get all wasm modules
11+
function getAllWasmModules(chunk) {
12+
const wasmModules = chunk.getAllAsyncChunks();
13+
const array = [];
14+
for (const chunk of wasmModules) {
15+
for (const m of chunk.modulesIterable) {
16+
if (m.type.startsWith("webassembly")) {
17+
array.push(m);
18+
}
19+
}
20+
}
21+
22+
return array;
23+
}
24+
25+
function generateImportObject(module) {
26+
const depsByRequest = new Map();
27+
for (const dep of module.dependencies) {
28+
if (dep instanceof WebAssemblyImportDependency) {
29+
// Ignore global they will be handled later
30+
if (dep.description.type === "GlobalType") {
31+
continue;
32+
}
33+
34+
const request = dep.request;
35+
let array = depsByRequest.get(request);
36+
if (!array) {
37+
depsByRequest.set(request, (array = []));
38+
}
39+
const exportName = dep.name;
40+
const usedName = dep.module && dep.module.isUsed(exportName);
41+
array.push({
42+
exportName,
43+
usedName,
44+
module: dep.module,
45+
description: dep.description
46+
});
47+
}
48+
}
49+
const importsCode = [];
50+
for (const pair of depsByRequest) {
51+
const properties = [];
52+
for (const data of pair[1]) {
53+
let params = "";
54+
let result = "void 0";
55+
56+
if (data.description.type === "FuncImportDescr") {
57+
params = data.description.params.map(
58+
(param, k) => "p" + k + param.valtype
59+
);
60+
61+
result = `__webpack_require__(${JSON.stringify(
62+
data.module.id
63+
)})[${JSON.stringify(data.usedName)}](${params})`;
64+
}
65+
66+
properties.push(
67+
`\n\t\t${JSON.stringify(data.exportName)}: function(${params}) {
68+
return ${result};
69+
}`
70+
);
71+
}
72+
importsCode.push(
73+
`\n\t${JSON.stringify(pair[0])}: {${properties.join(",")}\n\t}`
74+
);
75+
}
76+
77+
return JSON.stringify(module.id) + ": {" + importsCode.join(",") + "\n},";
78+
}
79+
80+
class BaseWasmMainTemplatePlugin {
81+
applyWeb(mainTemplate) {
82+
const generateLoadBinaryCode = path =>
83+
`fetch(${mainTemplate.requireFn}.p + ${path})`;
84+
85+
this.apply(mainTemplate, generateLoadBinaryCode);
86+
}
87+
88+
applyNode(mainTemplate) {
89+
const generateLoadBinaryCode = path => `
90+
new Promise(function (resolve, reject) {
91+
try {
92+
fs.readFile(${path}, function(err, buffer){
93+
if (err) reject(err); else resolve(buffer);
94+
});
95+
} catch (err) {
96+
reject(err);
97+
}
98+
});
99+
`;
100+
101+
this.apply(mainTemplate, generateLoadBinaryCode);
102+
}
103+
104+
apply(mainTemplate, generateLoadBinaryCode) {
105+
mainTemplate.hooks.localVars.tap(
106+
"BaseWasmMainTemplatePlugin",
107+
(source, chunk) => {
108+
if (!chunk.hasModuleInGraph(m => m.type.startsWith("webassembly")))
109+
return source;
110+
return Template.asString([
111+
source,
112+
"",
113+
"// object to store loaded and loading wasm modules",
114+
"var installedWasmModules = {};"
115+
]);
116+
}
117+
);
118+
mainTemplate.hooks.requireEnsure.tap(
119+
"BaseWasmMainTemplatePlugin",
120+
(source, chunk, hash) => {
121+
const webassemblyModuleFilename =
122+
mainTemplate.outputOptions.webassemblyModuleFilename;
123+
124+
const wasmModules = getAllWasmModules(chunk);
125+
const importObjects = wasmModules.map(generateImportObject);
126+
127+
const chunkModuleMaps = chunk.getChunkModuleMaps(m =>
128+
m.type.startsWith("webassembly")
129+
);
130+
if (Object.keys(chunkModuleMaps.id).length === 0) return source;
131+
const wasmModuleSrcPath = mainTemplate.getAssetPath(
132+
JSON.stringify(webassemblyModuleFilename),
133+
{
134+
hash: `" + ${mainTemplate.renderCurrentHashCode(hash)} + "`,
135+
hashWithLength: length =>
136+
`" + ${mainTemplate.renderCurrentHashCode(hash, length)} + "`,
137+
module: {
138+
id: '" + wasmModuleId + "',
139+
hash: `" + ${JSON.stringify(
140+
chunkModuleMaps.hash
141+
)}[wasmModuleId] + "`,
142+
hashWithLength(length) {
143+
const shortChunkHashMap = Object.create(null);
144+
for (const wasmModuleId of Object.keys(chunkModuleMaps.hash)) {
145+
if (typeof chunkModuleMaps.hash[wasmModuleId] === "string")
146+
shortChunkHashMap[wasmModuleId] = chunkModuleMaps.hash[
147+
wasmModuleId
148+
].substr(0, length);
149+
}
150+
return `" + ${JSON.stringify(
151+
shortChunkHashMap
152+
)}[wasmModuleId] + "`;
153+
}
154+
}
155+
}
156+
);
157+
return Template.asString([
158+
source,
159+
"",
160+
"// Fetch + compile chunk loading for webassembly",
161+
"",
162+
"var importObjects = {",
163+
Template.indent([importObjects]),
164+
"}",
165+
"",
166+
`var wasmModules = ${JSON.stringify(
167+
chunkModuleMaps.id
168+
)}[chunkId] || [];`,
169+
"",
170+
"wasmModules.forEach(function(wasmModuleId) {",
171+
Template.indent([
172+
"var installedWasmModuleData = installedWasmModules[wasmModuleId];",
173+
"",
174+
'// a Promise means "currently loading" or "already loaded".',
175+
Template.indent([
176+
`var importObject = importObjects[wasmModuleId]`,
177+
`var req = ${generateLoadBinaryCode(wasmModuleSrcPath)}`,
178+
"if(typeof WebAssembly.instantiateStreaming !== 'function') {",
179+
Template.indent([
180+
"promises.push(WebAssembly.instantiateStreaming(req, importObject)",
181+
".then(function(res) {",
182+
Template.indent([
183+
`${
184+
mainTemplate.requireFn
185+
}.w[wasmModuleId] = installedWasmModules[wasmModuleId] = res.instance;`
186+
]),
187+
"}))"
188+
]),
189+
"} else {",
190+
Template.indent([
191+
"var promise = req.then(x => x.arrayBuffer()).then(function(bytes) {",
192+
Template.indent([
193+
"return WebAssembly.instantiate(bytes, importObject);"
194+
]),
195+
"}).then(function(res) {",
196+
Template.indent([
197+
`${
198+
mainTemplate.requireFn
199+
}.w[wasmModuleId] = installedWasmModules[wasmModuleId] = res.instance;`,
200+
"return res.instance"
201+
]),
202+
"})",
203+
"promises.push(promise);"
204+
]),
205+
"}"
206+
])
207+
]),
208+
"});"
209+
]);
210+
}
211+
);
212+
mainTemplate.hooks.requireExtensions.tap(
213+
"BaseWasmMainTemplatePlugin",
214+
(source, chunk) => {
215+
if (!chunk.hasModuleInGraph(m => m.type.startsWith("webassembly")))
216+
return source;
217+
return Template.asString([
218+
source,
219+
"",
220+
"// object with all WebAssembly.instance",
221+
`${mainTemplate.requireFn}.w = {};`
222+
]);
223+
}
224+
);
225+
mainTemplate.hooks.hash.tap("BaseWasmMainTemplatePlugin", hash => {
226+
hash.update("BaseWasmMainTemplatePlugin");
227+
hash.update("1");
228+
hash.update(`${mainTemplate.outputOptions.webassemblyModuleFilename}`);
229+
});
230+
}
231+
}
232+
233+
module.exports = BaseWasmMainTemplatePlugin;

lib/node/ReadFileCompileWasmTemplatePlugin.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,15 @@
44
*/
55
"use strict";
66

7-
const FetchCompileWasmMainTemplatePlugin = require("../web/FetchCompileWasmMainTemplatePlugin");
7+
const BaseWasmMainTemplatePlugin = require("../BaseWasmMainTemplatePlugin");
88
const WasmModuleTemplatePlugin = require("../wasm/WasmModuleTemplatePlugin");
99

1010
class ReadFileCompileWasmTemplatePlugin {
1111
apply(compiler) {
1212
compiler.hooks.thisCompilation.tap(
1313
"ReadFileCompileWasmTemplatePlugin",
1414
compilation => {
15-
new FetchCompileWasmMainTemplatePlugin().apply(
16-
compilation.mainTemplate
17-
);
15+
new BaseWasmMainTemplatePlugin().applyNode(compilation.mainTemplate);
1816
new WasmModuleTemplatePlugin().apply(
1917
compilation.moduleTemplates.javascript
2018
);

0 commit comments

Comments
 (0)