Skip to content

5.111.0: removing the try/catch in RuntimeModule.updateHash breaks plugin runtime modules that resolve chunk filenames #22199

Closed

Description

Have you used AI?

Yes

Which part of webpack is affected?

module-federation, runtime (emitted runtime, chunk loading)

Bug Description

Since 5.111.0, a plugin runtime module that resolves its own chunk's filename
template fails the build when output.filename contains a hash placeholder:

Path variable [contenthash:8] not implemented in this context:
static/js/[name].[contenthash:8].js

RuntimeModule.updateHash calls generate() during Compilation.createHash, before
the chunk's contenthash exists. A generate() that calls compilation.getPath() on
the chunk filename template therefore resolves [contenthash:8] to undefined and
TemplatedPathPlugin throws.

Up to 5.110.3 that throw was caught in RuntimeModule.updateHash and the error
message hashed instead. 5.111.0 removed the catch, so it now propagates and fails
the compilation.

I understand why the catch was removed — hashing the error message pins the
codegen cache etag to a constant and can serve stale code, so it was masking a
real correctness problem. Two things still seem worth addressing:

  1. The error doesn't name the runtime module or plugin that triggered it. It only
    shows the filename template, which points the user at their own output.filename
    and suggests their config is wrong when it isn't. This is very hard to trace —
    I only found it by patching TemplatedPathPlugin to print a stack.

  2. RuntimeTemplate.chunkRootOutputDir() is the correct replacement and does fix it
    (verified on 5.111.1), but it was only added in 5.110.0 — one minor before the
    safety net was removed. A plugin supporting a range of webpack 5 versions has
    no single call that works across them and needs a feature check.

This currently reaches users through @module-federation/enhanced (through 2.9.0),
where any CRA-style output.filename with [contenthash] fails to build on webpack

= 5.111.0. The repro below uses no Module Federation — it's a ~30 line plugin.

Link to Minimal Reproduction and steps to reproduce

Minimal repro, no Module Federation involved — a plugin runtime module that computes the undo path back to the output root, exactly as AutoPublicPathRuntimeModule does:

plugin.js

const { RuntimeModule, RuntimeGlobals } = require("webpack");
const JavascriptModulesPlugin = require("webpack/lib/javascript/JavascriptModulesPlugin");
const { getUndoPath } = require("webpack/lib/util/identifier");

class MyRuntimeModule extends RuntimeModule {
constructor() { super("my runtime", 10); }
generate() {
const { compilation, chunk } = this;
const dir = getUndoPath(
compilation.getPath(
JavascriptModulesPlugin.getChunkFilenameTemplate(chunk, compilation.outputOptions),
{ chunk, contentHashType: "javascript" }
),
compilation.outputOptions.path || "",
false
);
return __webpack_require__.myRootDir = ${JSON.stringify(dir)};;
}
}

module.exports = class MyPlugin {
apply(compiler) {
compiler.hooks.compilation.tap("MyPlugin", (compilation) => {
compilation.hooks.additionalTreeRuntimeRequirements.tap("MyPlugin", (chunk, set) => {
set.add(RuntimeGlobals.ensureChunkHandlers);
compilation.addRuntimeModule(chunk, new MyRuntimeModule());
});
});
}
};

webpack.config.js

module.exports = {
mode: "production",
entry: "./src/index.js",
output: { filename: "static/js/[name].[contenthash:8].js", publicPath: "auto" },
optimization: { runtimeChunk: false },
plugins: [new (require("./plugin.js"))()],
};

src/index.js only needs import("./lazy.js"); plus a trivial src/lazy.js (an async chunk, so ensureChunkHandlers is required).

Results:

webpack result
5.110.3 build OK, emits myRootDir="../../"
5.111.0 Path variable [contenthash:8] not implemented in this context
5.111.1 same

Bisected to 5.111.0 — the removal of the try/catch in RuntimeModule.updateHash.

Stack trace

fn                              (lib/TemplatedPathPlugin.js:333)
Compilation.getAssetPath        (lib/Compilation.js:6086)
Compilation.getPath             (lib/Compilation.js:6058)
MyRuntimeModule.generate
RuntimeModule.getGeneratedCode  (lib/RuntimeModule.js:242)
RuntimeModule.updateHash        (lib/RuntimeModule.js:146)
Compilation._createModuleHash   (lib/Compilation.js:5150)
Compilation.createHash          (lib/Compilation.js:5402)

Expected Behavior

Either the call keeps working as it did through 5.110.3, or the thrown error
identifies which runtime module triggered it so the responsible plugin can be found.

Actual Behavior

The build fails with:

Path variable [contenthash:8] not implemented in this context: static/js/[name].[contenthash:8].js

Stack:
fn lib/TemplatedPathPlugin.js:333
Compilation.getAssetPath lib/Compilation.js:6086
Compilation.getPath lib/Compilation.js:6058
MyRuntimeModule.generate plugin.js
RuntimeModule.getGeneratedCode lib/RuntimeModule.js:242
RuntimeModule.updateHash lib/RuntimeModule.js:146
Compilation._createModuleHash lib/Compilation.js:5150
Compilation.createHash lib/Compilation.js:5402

Environment

System:
  OS:      Linux 6.6 Ubuntu 24.04.3 LTS (Noble Numbat)
  CPU:     (4) x64 Intel(R) Core(TM) Ultra 7 255H
  Memory:  2.64 GB / 11.68 GB
Binaries:
  Node:  24.13.0
  Yarn:  1.22.22
  npm:   11.6.2
Packages:
  webpack:  ^5.111.1 → 5.111.1

Is this a regression?

Yes (please specify version below)

Last Working Version

5.110.3

Additional Context

Bisected:
5.110.3 build OK, emits myRootDir="../../"
5.111.0 fails
5.111.1 fails

Verified fix on 5.111.1 — replacing the getPath call with
compilation.runtimeTemplate.chunkRootOutputDir(chunk, false) builds and emits the
correct myRootDir="../../".

Not Node-dependent: reproduces on Node 20 and 24.

Workaround for affected users: pin webpack to ~5.110.3.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: module-federationModule Federation (lib/container, lib/sharing)area: runtimeEmitted runtime and chunk loading across targets (lib/runtime, lib/web, lib/node, lib/esm)regressionWorked in a previous release; pair with the Affected version field

    Type

    Fields

    Priority

    None yet

    Effort

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions