Skip to content

Commit 1ad71e0

Browse files
committed
add helpful error when importing wasm in initial chunk
1 parent e8dc361 commit 1ad71e0

18 files changed

+215
-2
lines changed

lib/Chunk.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ class Chunk {
298298
}
299299

300300
/**
301-
* @returns {SortableSet} the chunkGroups that said chunk is referenced in
301+
* @returns {SortableSet<ChunkGroup>} the chunkGroups that said chunk is referenced in
302302
*/
303303
get groupsIterable() {
304304
return this._groups;

lib/ChunkTemplate.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ module.exports = class ChunkTemplate extends Tapable {
2626
super();
2727
this.outputOptions = outputOptions || {};
2828
this.hooks = {
29+
/** @type {SyncWaterfallHook<TODO[], RenderManifestOptions>} */
2930
renderManifest: new SyncWaterfallHook(["result", "options"]),
3031
modules: new SyncWaterfallHook([
3132
"source",

lib/Compilation.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,11 @@ class Compilation extends Tapable {
232232
/** @type {SyncHook} */
233233
seal: new SyncHook([]),
234234

235+
/** @type {SyncHook} */
236+
beforeChunks: new SyncHook([]),
237+
/** @type {SyncHook<Chunk[]>} */
238+
afterChunks: new SyncHook(["chunks"]),
239+
235240
/** @type {SyncBailHook<Module[]>} */
236241
optimizeDependenciesBasic: new SyncBailHook(["modules"]),
237242
/** @type {SyncBailHook<Module[]>} */
@@ -1150,6 +1155,7 @@ class Compilation extends Tapable {
11501155
}
11511156
this.hooks.afterOptimizeDependencies.call(this.modules);
11521157

1158+
this.hooks.beforeChunks.call();
11531159
for (const preparedEntrypoint of this._preparedEntrypoints) {
11541160
const module = preparedEntrypoint.module;
11551161
const name = preparedEntrypoint.name;
@@ -1171,6 +1177,8 @@ class Compilation extends Tapable {
11711177
}
11721178
this.processDependenciesBlocksForChunkGroups(this.chunkGroups.slice());
11731179
this.sortModules(this.modules);
1180+
this.hooks.afterChunks.call(this.chunks);
1181+
11741182
this.hooks.optimize.call();
11751183

11761184
while (

lib/MainTemplate.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const {
1919
const Template = require("./Template");
2020

2121
/** @typedef {import("webpack-sources").ConcatSource} ConcatSource */
22+
/** @typedef {import("webpack-sources").Source} Source */
2223
/** @typedef {import("./ModuleTemplate")} ModuleTemplate */
2324
/** @typedef {import("./Chunk")} Chunk */
2425
/** @typedef {import("./Module")} Module} */
@@ -93,7 +94,9 @@ module.exports = class MainTemplate extends Tapable {
9394
localVars: new SyncWaterfallHook(["source", "chunk", "hash"]),
9495
require: new SyncWaterfallHook(["source", "chunk", "hash"]),
9596
requireExtensions: new SyncWaterfallHook(["source", "chunk", "hash"]),
97+
/** @type {SyncWaterfallHook<string, Chunk, string>} */
9698
beforeStartup: new SyncWaterfallHook(["source", "chunk", "hash"]),
99+
/** @type {SyncWaterfallHook<string, Chunk, string>} */
97100
startup: new SyncWaterfallHook(["source", "chunk", "hash"]),
98101
render: new SyncWaterfallHook([
99102
"source",
@@ -448,7 +451,7 @@ module.exports = class MainTemplate extends Tapable {
448451
/**
449452
*
450453
* @param {string} hash string hash
451-
* @param {number} length length
454+
* @param {number=} length length
452455
* @returns {string} call hook return
453456
*/
454457
renderCurrentHashCode(hash, length) {

lib/Module.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,9 @@ class Module extends DependenciesBlock {
182182
);
183183
}
184184

185+
/**
186+
* @returns {Chunk[]} all chunks which contain the module
187+
*/
185188
getChunks() {
186189
return Array.from(this._chunks);
187190
}

lib/ModuleReason.js

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

7+
/** @typedef {import("./Module")} Module */
8+
/** @typedef {import("./Dependency")} Dependency */
9+
710
class ModuleReason {
11+
/**
12+
* @param {Module} module the referencing module
13+
* @param {Dependency} dependency the referencing dependency
14+
* @param {string=} explanation some extra detail
15+
*/
816
constructor(module, dependency, explanation) {
917
this.module = module;
1018
this.dependency = dependency;

lib/wasm/WasmMainTemplatePlugin.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const Template = require("../Template");
88
const WebAssemblyUtils = require("./WebAssemblyUtils");
99

1010
/** @typedef {import("../Module")} Module */
11+
/** @typedef {import("../MainTemplate")} MainTemplate */
1112

1213
// Get all wasm modules
1314
const getAllWasmModules = chunk => {
@@ -159,6 +160,11 @@ class WasmMainTemplatePlugin {
159160
this.supportsStreaming = supportsStreaming;
160161
this.mangleImports = mangleImports;
161162
}
163+
164+
/**
165+
* @param {MainTemplate} mainTemplate main template
166+
* @returns {void}
167+
*/
162168
apply(mainTemplate) {
163169
mainTemplate.hooks.localVars.tap(
164170
"WasmMainTemplatePlugin",
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
MIT License http://www.opensource.org/licenses/mit-license.php
3+
*/
4+
"use strict";
5+
6+
const WebpackError = require("../WebpackError");
7+
8+
/** @typedef {import("../Module")} Module */
9+
/** @typedef {import("../RequestShortener")} RequestShortener */
10+
11+
/**
12+
* @param {Module} module module to get chains from
13+
* @param {RequestShortener} requestShortener to make readable identifiers
14+
* @returns {string[]} all chains to the module
15+
*/
16+
const getInitialModuleChains = (module, requestShortener) => {
17+
const queue = [
18+
{ head: module, message: module.readableIdentifier(requestShortener) }
19+
];
20+
/** @type {Set<string>} */
21+
const results = new Set();
22+
/** @type {Set<string>} */
23+
const incompleteResults = new Set();
24+
/** @type {Set<Module>} */
25+
const visitedModules = new Set();
26+
27+
for (const chain of queue) {
28+
const { head, message } = chain;
29+
let final = true;
30+
/** @type {Set<Module>} */
31+
const alreadyReferencedModules = new Set();
32+
for (const reason of head.reasons) {
33+
const newHead = reason.module;
34+
if (newHead) {
35+
if (!newHead.getChunks().some(c => c.canBeInitial())) continue;
36+
final = false;
37+
if (alreadyReferencedModules.has(newHead)) continue;
38+
alreadyReferencedModules.add(newHead);
39+
const moduleName = newHead.readableIdentifier(requestShortener);
40+
const detail = reason.explanation ? ` (${reason.explanation})` : "";
41+
const newMessage = `${moduleName}${detail} --> ${message}`;
42+
if (visitedModules.has(newHead)) {
43+
incompleteResults.add(`... --> ${newMessage}`);
44+
continue;
45+
}
46+
visitedModules.add(newHead);
47+
queue.push({
48+
head: newHead,
49+
message: newMessage
50+
});
51+
} else {
52+
final = false;
53+
const newMessage = reason.explanation
54+
? `(${reason.explanation}) --> ${message}`
55+
: message;
56+
results.add(newMessage);
57+
}
58+
}
59+
if (final) {
60+
results.add(message);
61+
}
62+
}
63+
for (const result of incompleteResults) {
64+
results.add(result);
65+
}
66+
return Array.from(results);
67+
};
68+
69+
module.exports = class WebAssemblyInInitialChunkError extends WebpackError {
70+
/**
71+
* @param {Module} module WASM module
72+
* @param {RequestShortener} requestShortener request shortener
73+
*/
74+
constructor(module, requestShortener) {
75+
const moduleChains = getInitialModuleChains(module, requestShortener);
76+
const message = `WebAssembly module is included in initial chunk.
77+
This is not allowed, because WebAssembly download and compilation must happen asynchronous.
78+
Add an async splitpoint (i. e. import()) somewhere between your entrypoint and the WebAssembly module:
79+
${moduleChains.map(s => `* ${s}`).join("\n")}`;
80+
81+
super(message);
82+
this.name = "WebAssemblyInInitialChunkError";
83+
this.hideStack = true;
84+
this.module = module;
85+
86+
Error.captureStackTrace(this, this.constructor);
87+
}
88+
};

lib/wasm/WebAssemblyModulesPlugin.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,19 @@ const WebAssemblyGenerator = require("./WebAssemblyGenerator");
1010
const WebAssemblyJavascriptGenerator = require("./WebAssemblyJavascriptGenerator");
1111
const WebAssemblyImportDependency = require("../dependencies/WebAssemblyImportDependency");
1212
const WebAssemblyExportImportedDependency = require("../dependencies/WebAssemblyExportImportedDependency");
13+
const WebAssemblyInInitialChunkError = require("./WebAssemblyInInitialChunkError");
14+
15+
/** @typedef {import("../Compiler")} Compiler */
1316

1417
class WebAssemblyModulesPlugin {
1518
constructor(options) {
1619
this.options = options;
1720
}
1821

22+
/**
23+
* @param {Compiler} compiler compiler
24+
* @returns {void}
25+
*/
1926
apply(compiler) {
2027
compiler.hooks.compilation.tap(
2128
"WebAssemblyModulesPlugin",
@@ -78,6 +85,27 @@ class WebAssemblyModulesPlugin {
7885
return result;
7986
}
8087
);
88+
89+
compilation.hooks.afterChunks.tap("WebAssemblyModulesPlugin", () => {
90+
const initialWasmModules = new Set();
91+
for (const chunk of compilation.chunks) {
92+
if (chunk.canBeInitial()) {
93+
for (const module of chunk.modulesIterable) {
94+
if (module.type.startsWith("webassembly")) {
95+
initialWasmModules.add(module);
96+
}
97+
}
98+
}
99+
}
100+
for (const module of initialWasmModules) {
101+
compilation.errors.push(
102+
new WebAssemblyInInitialChunkError(
103+
module,
104+
compilation.requestShortener
105+
)
106+
);
107+
}
108+
});
81109
}
82110
);
83111
}

test/__snapshots__/StatsTestCases.test.js.snap

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2854,3 +2854,39 @@ WARNING in UglifyJs Plugin: Dropping unused function someUnRemoteUsedFunction4 [
28542854
28552855
WARNING in UglifyJs Plugin: Dropping unused function someUnRemoteUsedFunction5 [./a.js:7,0] in bundle.js"
28562856
`;
2857+
2858+
exports[`StatsTestCases should print correct stats for wasm-in-initial-chunk-error 1`] = `
2859+
"Hash: 9f32353d97d5973caae9
2860+
Time: Xms
2861+
Built at: Thu Jan 01 1970 00:00:00 GMT
2862+
Asset Size Chunks Chunk Names
2863+
0.js 130 bytes 0
2864+
main.js 9.54 KiB 1 main
2865+
Entrypoint main = main.js
2866+
[0] ./wasm.wat 42 bytes {1} [built]
2867+
[1] ./module2.js 45 bytes {1} [built]
2868+
[2] ./module3.js 47 bytes {1} [built]
2869+
[3] ./wasm2.wat 42 bytes {1} [built]
2870+
[4] ./index.js + 1 modules 124 bytes {1} [built]
2871+
| ./index.js 19 bytes [built]
2872+
| ./module.js 100 bytes [built]
2873+
[5] ./async.js 0 bytes {0} [built]
2874+
2875+
WARNING in configuration
2876+
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
2877+
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/
2878+
2879+
ERROR in ./wasm2.wat
2880+
WebAssembly module is included in initial chunk.
2881+
This is not allowed, because WebAssembly download and compilation must happen asynchronous.
2882+
Add an async splitpoint (i. e. import()) somewhere between your entrypoint and the WebAssembly module:
2883+
* ./index.js --> ./module.js --> ./module2.js --> ./module3.js --> ./wasm2.wat
2884+
2885+
ERROR in ./wasm.wat
2886+
WebAssembly module is included in initial chunk.
2887+
This is not allowed, because WebAssembly download and compilation must happen asynchronous.
2888+
Add an async splitpoint (i. e. import()) somewhere between your entrypoint and the WebAssembly module:
2889+
* ./index.js --> ./module.js --> ./wasm.wat
2890+
* ... --> ./module.js --> ./module2.js --> ./wasm.wat
2891+
* ... --> ./module2.js --> ./module3.js --> ./wasm.wat"
2892+
`;

0 commit comments

Comments
 (0)