Skip to content

Commit 758f622

Browse files
martinjlowmclaudehardfist
authored
chore: gracefully explain unsupported interfaces (#12782)
* feat(core): add helpful error messages for unsupported webpack hooks When webpack plugins try to access hooks that don't exist in rspack, provide a clear error message instead of the cryptic "Cannot read properties of undefined (reading 'tap')". The error message explains: - Which hook is not supported - That this typically happens with webpack plugins - Lists all available hooks for reference This helps developers quickly identify webpack compatibility issues. * chore: add regression test on messaging unsupported hooks * chore: update snapshot --------- Co-authored-by: Claude <[email protected]> Co-authored-by: hardfist <[email protected]>
1 parent cae3ee7 commit 758f622

File tree

6 files changed

+175
-0
lines changed

6 files changed

+175
-0
lines changed

packages/rspack/src/Compilation.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,26 @@ BREAKING CHANGE: Asset processing hooks in Compilation has been merged into a si
389389
afterSeal: new liteTapable.AsyncSeriesHook([]),
390390
needAdditionalPass: new liteTapable.SyncBailHook([]),
391391
};
392+
393+
// Wrap hooks with a Proxy to provide helpful error messages when
394+
// webpack plugins try to access hooks that don't exist in rspack
395+
const availableHooks = Object.keys(this.hooks);
396+
this.hooks = new Proxy(this.hooks, {
397+
get(target, prop, receiver) {
398+
const value = Reflect.get(target, prop, receiver);
399+
if (value === undefined && typeof prop === 'string') {
400+
const hooksList = availableHooks.join(', ');
401+
throw new Error(
402+
`Compilation.hooks.${prop} is not supported in rspack. ` +
403+
`This typically happens when using webpack plugins that rely on webpack-specific hooks. ` +
404+
`Consider using an rspack-compatible alternative or removing the incompatible plugin.\n\n` +
405+
`Available compilation hooks: ${hooksList}`,
406+
);
407+
}
408+
return value;
409+
},
410+
});
411+
392412
this.compiler = compiler;
393413
this.resolverFactory = compiler.resolverFactory;
394414
this.inputFileSystem = compiler.inputFileSystem;

packages/rspack/src/Compiler.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,25 @@ class Compiler {
260260
additionalPass: new liteTapable.AsyncSeriesHook([]),
261261
};
262262

263+
// Wrap hooks with a Proxy to provide helpful error messages when
264+
// webpack plugins try to access hooks that don't exist in rspack
265+
const availableCompilerHooks = Object.keys(this.hooks);
266+
this.hooks = new Proxy(this.hooks, {
267+
get(target, prop, receiver) {
268+
const value = Reflect.get(target, prop, receiver);
269+
if (value === undefined && typeof prop === 'string') {
270+
const hooksList = availableCompilerHooks.join(', ');
271+
throw new Error(
272+
`Compiler.hooks.${prop} is not supported in rspack. ` +
273+
`This typically happens when using webpack plugins that rely on webpack-specific hooks. ` +
274+
`Consider using an rspack-compatible alternative or removing the incompatible plugin.\n\n` +
275+
`Available compiler hooks: ${hooksList}`,
276+
);
277+
}
278+
return value;
279+
},
280+
});
281+
263282
const compilerRuntimeGlobals = createCompilerRuntimeGlobals(options);
264283
const compilerFn = function (...params: Parameters<typeof rspackFn>) {
265284
return rspackFn(...params);
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
```js title=main.js
2+
(() => {
3+
var __webpack_modules__ = ({
4+
402(module) {
5+
module.exports = "This is hook"
6+
7+
8+
},
9+
10+
});
11+
// The module cache
12+
var __webpack_module_cache__ = {};
13+
14+
// The require function
15+
function __webpack_require__(moduleId) {
16+
17+
// Check if module is in cache
18+
var cachedModule = __webpack_module_cache__[moduleId];
19+
if (cachedModule !== undefined) {
20+
return cachedModule.exports;
21+
}
22+
// Create a new module (and put it into the cache)
23+
var module = (__webpack_module_cache__[moduleId] = {
24+
exports: {}
25+
});
26+
// Execute the module function
27+
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
28+
29+
// Return the exports of the module
30+
return module.exports;
31+
32+
}
33+
34+
// startup
35+
// Load entry module and return exports
36+
// This entry module is referenced by other modules so it can't be inlined
37+
var __webpack_exports__ = __webpack_require__(402);
38+
})()
39+
;
40+
```
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/** @type {import("@rspack/test-tools").THookCaseConfig} */
2+
module.exports = {
3+
description: "should throw helpful error for unsupported compilation hooks",
4+
options() {
5+
return {
6+
plugins: [
7+
{
8+
apply(compiler) {
9+
compiler.hooks.compilation.tap("test", compilation => {
10+
// Test accessing an unsupported hook that exists in webpack but not rspack
11+
expect(() => {
12+
compilation.hooks.nonExistentWebpackHook.tap("test", () => {});
13+
}).toThrow(/Compilation\.hooks\.nonExistentWebpackHook is not supported in rspack/);
14+
15+
// Verify the error message includes the available hooks list
16+
try {
17+
compilation.hooks.nonExistentWebpackHook;
18+
} catch (e) {
19+
expect(e.message).toContain("Available compilation hooks:");
20+
expect(e.message).toContain("processAssets");
21+
expect(e.message).toContain("seal");
22+
}
23+
});
24+
}
25+
}
26+
]
27+
};
28+
}
29+
};
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
```js title=main.js
2+
(() => {
3+
var __webpack_modules__ = ({
4+
402(module) {
5+
module.exports = "This is hook"
6+
7+
8+
},
9+
10+
});
11+
// The module cache
12+
var __webpack_module_cache__ = {};
13+
14+
// The require function
15+
function __webpack_require__(moduleId) {
16+
17+
// Check if module is in cache
18+
var cachedModule = __webpack_module_cache__[moduleId];
19+
if (cachedModule !== undefined) {
20+
return cachedModule.exports;
21+
}
22+
// Create a new module (and put it into the cache)
23+
var module = (__webpack_module_cache__[moduleId] = {
24+
exports: {}
25+
});
26+
// Execute the module function
27+
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
28+
29+
// Return the exports of the module
30+
return module.exports;
31+
32+
}
33+
34+
// startup
35+
// Load entry module and return exports
36+
// This entry module is referenced by other modules so it can't be inlined
37+
var __webpack_exports__ = __webpack_require__(402);
38+
})()
39+
;
40+
```
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/** @type {import("@rspack/test-tools").THookCaseConfig} */
2+
module.exports = {
3+
description: "should throw helpful error for unsupported compiler hooks",
4+
options() {
5+
return {
6+
plugins: [
7+
{
8+
apply(compiler) {
9+
// Test accessing an unsupported hook that exists in webpack but not rspack
10+
expect(() => {
11+
compiler.hooks.nonExistentWebpackHook.tap("test", () => {});
12+
}).toThrow(/Compiler\.hooks\.nonExistentWebpackHook is not supported in rspack/);
13+
14+
// Verify the error message includes the available hooks list
15+
try {
16+
compiler.hooks.nonExistentWebpackHook;
17+
} catch (e) {
18+
expect(e.message).toContain("Available compiler hooks:");
19+
expect(e.message).toContain("compilation");
20+
expect(e.message).toContain("emit");
21+
}
22+
}
23+
}
24+
]
25+
};
26+
}
27+
};

0 commit comments

Comments
 (0)