Repository navigation
Description
Bug Description
RuntimeModule.updateHash hashes a runtime module by its generated code, but when generate() throws it hashes err.message instead:
try {
hash.update(this.getGeneratedCode());
} catch (err) {
hash.update(err.message);
}With experiments.outputModule and a [contenthash] in output.filename, ModuleChunkLoadingRuntimeModule.generate() (the import chunk loading module) resolves its own filename through compilation.getPath(..., { contentHashType: "javascript" }). During hashing content hashes do not exist yet, so TemplatedPathPlugin throws Path variable [contenthash] not implemented in this context. The hash of the module therefore collapses to a constant string that does not depend on the generated code at all.
That hash is the etag of the Compilation/codeGeneration cache entry. With cache.type: "filesystem" every later build gets whatever code of the module the cache holds, regardless of what the chunk graph looks like now.
We hit this in production: a commit removed the last webpackPreload hint reachable from an async chunk. The chunk preload function module (__webpack_require__.H = {}) was correctly gone, but the cached import chunk loading module still installed __webpack_require__.H.j = …. The runtime threw on that statement before __webpack_require__.C was assigned, and every page failed with __webpack_require__.C is not a function.
Link to Minimal Reproduction and steps to reproduce
Four files, no loaders (package.json has "type": "module"):
// webpack.config.cjs
const path = require('path');
module.exports = {
mode: 'production',
entry: { main: './src/main.js' },
output: { path: path.join(__dirname, 'dist'), module: true, filename: '[name].[contenthash].js', clean: true },
experiments: { outputModule: true },
optimization: { runtimeChunk: 'single', minimize: false },
cache: { type: 'filesystem', cacheDirectory: path.join(__dirname, '.cache') },
};// src/main.js
import('./lazy.js').then((m) => m.run());
// src/lazy.js
export function run() { import(/* webpackPreload: true */ './sentry.js').then((m) => m.init()); }
// src/sentry.js
export function init() { console.log('sentry'); }rm -rf .cache dist && webpack—dist/runtime.*.jscontains both__webpack_require__.H = {}and__webpack_require__.H.j = …, as expected.- Remove
/* webpackPreload: true */fromsrc/lazy.jsand runwebpackagain on the same cache.
Expected Behavior
The runtime chunk contains neither __webpack_require__.H = {} nor __webpack_require__.H.j (this is what a build with a fresh cache produces).
Actual Behavior
The runtime chunk contains __webpack_require__.H.j = … from the previous build and no __webpack_require__.H = {}. Loading it throws TypeError: Cannot set properties of undefined (setting 'j') on the first statement of the chunk-loading section; nothing after it in the runtime runs.
Wrapping generate of the import chunk loading module confirms it: during _createModuleHash it throws Path variable [contenthash] not implemented in this context: [name].[contenthash].js, and at code generation time generate is never called because the cache hits.
Environment
- webpack 5.104.1, 5.105.4, 5.107.2, 5.109.2 reproduce.
- webpack 5.110.0 and 5.110.3 do not:
RuntimeTemplate.chunkRootOutputDirnow replaces hash tokens with a probe before callinggetPath, sogenerate()no longer throws while hashing. - Node 24, macOS.
Is this a regression?
No. But the fallback in RuntimeModule.updateHash is still there in 5.110, so any runtime module whose generate() throws during hashing will again produce a stable-but-meaningless hash and a poisoned persistent cache. A hash that ignores the module content seems worse than failing loudly or marking the module uncacheable (e.g. treating a throwing generate() as fullHash, or hashing the runtime requirements and chunk graph state the module depends on).
Additional Context
The stale entry survives across commits in CI when the cache directory is restored, so the broken runtime shows up only in the build after the one that removed the hint, and only where the cache is warm; a clean checkout does not reproduce it. Users of 5.104–5.109 with ESM output and a persistent cache may want to know that a hashed filename plus any change in prefetch/preload usage can produce a runtime chunk that does not boot.
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Activity
devtechedge commentedon Sep 9, 2026
Working on a focused fix so
RuntimeModule.updateHashdoes not fall back to hashingerr.messagewhengenerate()throws during content-hash resolution - that collapses the filesystem cache etag and can serve a stale runtime module. Will add a regression test and open a PR linking this issue.devtechedge commentedon Sep 9, 2026
Stepping back after a closer look: the reported ESM preload/contenthash reproduction is already fixed on current main via
RuntimeTemplate.chunkRootOutputDir, and changing theupdateHasherror fallback (fullHash promotion vs failing loudly vs hashing dependency state) looks like a product/cache-policy choice that needs maintainer direction before a PR. Happy to pick it back up if maintainers want a specific approach.hai-x commentedon Sep 10, 2026
Yeah, i test it and couldn't repro it in the latest main. I think I need to dig into it deeper for the background of the fix.
fix(runtime): throw when runtime module code generation fails in upda…
Pcmhacker-piro commentedon Sep 10, 2026
Hi @Anonym-tsk,
I have submitted a fix for this in #22032.
The PR removes the
try-catchfallback toerr.messageinRuntimeModule.prototype.updateHash. Whengenerate()throws during hashing, the failure now cleanly propagates toCompilation._createModuleHashto record aModuleHashingError(failing loudly and accurately) rather than collapsing the module's hash into a static error string that can poison persistent filesystem caches.Feel free to check out #22032!
Anonym-tsk commentedon Sep 10, 2026
I tried #22032 against the reproduction above and against the real project that hit this in production (ESM output,
[contenthash]filenames,runtimeChunk: 'single',cache.type: 'filesystem').ModuleHashingErrorforwebpack/runtime/import chunk loadingandwebpack/runtime/publicPath(publicPath: 'auto'), both "Path variable [contenthash] not implemented in this context"import chunk loading__webpack_require__.H = {}nor__webpack_require__.H.jSo from my side:
err.message: with it, the build that broke our production would simply not have compiled.ModuleChunkLoadingRuntimeModule(andAutoPublicPathRuntimeModule) throw while hashing. It is only safe together withRuntimeTemplate._chunkNameShape/chunkRootOutputDirfrom 5.110.0, which is already onmain. It cannot be backported without that.try/catchwas introduced in 0f08918 ("improve hashing of runtime modules that need a full hash") with a comment that the compilation hash may not be ready during hashing, i.e. it deliberately toleratedgenerate()throwing forfullHashmodules. No built-in module relies on that today, but a third-party runtime module might, so "no breaking change" in the PR description is optimistic.Compilation._createModuleHashstill assigns the constant"XXXXXX"on error. The build fails, so the cache is not poisoned, but if that error is ever downgraded to a warning the original problem comes back through the same door.Happy to re-run either scenario if a different approach is chosen.
fix(runtime): fail when a runtime module cannot be hashed (webpack#22034