-
-
Notifications
You must be signed in to change notification settings - Fork 9.3k
Description
Bug Description
The module existence check added in #20221 (shipped in v5.104.0) changes the observable control flow of __webpack_require__ in a way that breaks plugins relying on interceptModuleExecution to handle missing modules gracefully.
What is the current behavior?
When output.pathinfo is enabled (default in development), the new check in __webpack_require__ throws before interceptModuleExecution runs:
function __webpack_require__(moduleId) {
// 1. Cache check
var cachedModule = __webpack_module_cache__[moduleId];
if (cachedModule !== undefined) return cachedModule.exports;
// 2. NEW: pathinfo existence check — throws here
if (__webpack_modules__[moduleId] === undefined) {
var e = new Error("Cannot find module '" + moduleId + "'");
e.code = 'MODULE_NOT_FOUND';
throw e;
}
// 3. Create module
var module = __webpack_module_cache__[moduleId] = { ... };
// 4. interceptModuleExecution — never reached
var execOptions = { id: moduleId, module: module, factory: __webpack_modules__[moduleId], require: __webpack_require__ };
__webpack_require__.i.forEach(function(handler) { handler(execOptions); });
...
}Previously (≤v5.103), when __webpack_modules__[moduleId] was undefined, execution reached step 4. The factory was passed as undefined in execOptions.factory, and plugins hooking interceptModuleExecution could detect this and recover (e.g., trigger a page reload instead of crashing).
What is the expected behavior?
The interceptModuleExecution hook should still have a chance to handle missing modules. Either:
- Move the existence check after
interceptModuleExecutionruns (so plugins can intervene), or - Move it after
execOptions.factoryis read but before.call(), so the check still fires but only when no interceptor handled it, or - Gate the check on whether
interceptModuleExecutionis in use — if interceptors are registered, skip the early throw and let the interceptor handle it.
How is this affecting a real-world project?
Next.js uses interceptModuleExecution in its ReactRefreshWebpackPlugin to detect missing module factories and recover with a page reload:
// If the original factory is missing, e.g. due to race condition
// when compiling multiple entries concurrently, recover by doing
// a full page reload.
'if (!originalFactory) {',
Template.indent('document.location.reload();'),
Template.indent('return;'),
'}',This handles a legitimate scenario in development: during HMR with concurrent server component rendering, React's Flight protocol can serialize client references from one page that aren't present in another page's webpack module registry. The old behavior allowed the React Refresh interceptor to catch this and reload; the new check makes it a hard error before the interceptor runs.
Link to Minimal Reproduction and step to reproduce
checkout vercel/next.js#89569 which updates webpack for next.js to 5.105
pnpm i
pnpm build
pnpm test-dev-webpack test/development/app-dir/hmr-iframe/hmr-iframe.test.ts
Expected Behavior
the test should continue passing as interception hooks should be able to run before the dev mode missing module check
Actual Behavior
loading a missing module immediately fails with an Error
Environment
- webpack: 5.105.0 (also affects 5.104.0+)
- Node.js: v22.x
- OS: macOSIs this a regression?
Yes (please specify version below)
Last Working Version
v5.98.0
Additional Context
No response