Repository navigation
Parenthesized accessor call breaks ASI between adjacent require() statements in concatenated modules (regression in 5.110.3) #21956
Description
Filed via the API, so the issue-form dropdowns could not be selected. Their answers:
Field Answer Have you used AI? Yes Which part of webpack is affected? optimization (tree-shaking, splitChunks, concatenation) Is this a regression? Yes Last Working Version v5.110.2
Bug Description
Parenthesizing a wrapped module's accessor call (#21876, shipped in v5.110.3) defeats automatic semicolon insertion between two adjacent require() expression statements inside a concatenated (scope-hoisted) module.
Two require() calls on separate lines, where the first line has no trailing semicolon, are valid JavaScript: ASI terminates the first statement. Since v5.110.3, webpack emits each replaced accessor call wrapped in parentheses. (...) followed by (...) on the next line is a call expression, so the two statements collapse into one, and the bundle throws at runtime:
Uncaught TypeError: __webpack_require__(...) is not a function
The module reference is emitted as (reference), and the ; guard is only added when asiSafe === false:
https://github.com/webpack/webpack/blob/main/lib/optimize/ConcatenatedModule.js#L984
return asiSafe === false ? `;(${reference})` : `(${reference})`;At this position asiSafe is not false, so no guard is emitted. The asiSafe check describes whether the position itself is safe, but the newly added parentheses make the position unsafe for the following statement — a leading ( can continue the preceding expression. So the condition no longer covers the cases the parenthesization creates.
This is not a "you forgot a semicolon" problem: the input is valid JavaScript that v5.110.2 and every earlier version compiled correctly, and nothing warns.
Link to Minimal Reproduction and steps to reproduce
Four files, no dependencies beyond webpack and webpack-cli.
src/index.js — the import is what makes this module ESM and therefore scope-hoisted:
import X from "./x";
window.X = X;
require("./a")
require("./b");src/x.js:
export default function X() {
return 42;
}src/a.js:
window.a = true;src/b.js:
window.b = true;webpack.config.js:
const path = require("path");
module.exports = {
mode: "production",
entry: path.resolve(__dirname, "src/index.js"),
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js"
}
};Steps:
npm i webpack@5.110.3 webpack-clinpx webpack- Load
dist/bundle.jsin a browser, or run
node -e "global.window={}; require('./dist/bundle.js')" - It throws
TypeError: __webpack_require__(...) is not a function(minified:o(...) is not a function) - Repeat with
webpack@5.110.2— no error
Expected Behavior
The two require() calls stay two separate statements, as in v5.110.2 and earlier:
window.X = X;
a_namespaceFn()
b_namespaceFn();Both modules are evaluated, the bundle runs.
Actual Behavior
Since v5.110.3 the accessor calls are parenthesized without an ASI guard, so the second becomes an argument to the first:
window.X = X;
(a_namespaceFn())
(b_namespaceFn());Minified, this is the end of the bundle:
(()=>{"use strict";window.X=function(){return 42},o(967)(o(170))})()o(967) returns the module exports object of a side-effect-only module, calling it throws, and because this happens in the entry module nothing after it is initialized.
Environment
System:
OS: Linux 6.18 Ubuntu 24.04.4 LTS (Noble Numbat)
CPU: (20) x64 12th Gen Intel(R) Core(TM) i7-12700
Memory: 13.53 GB / 23.47 GB
Binaries:
Node: 20.20.2
npm: 10.8.2
Yarn: 1.22.22
Packages:
webpack: 5.110.3
webpack-cli: 7.2.3
terser-webpack-plugin: 5.6.1
Additional Context
Bisect
Same reproduction, only the webpack version changed:
| webpack | collapsed |
|---|---|
| 5.109.2 | no |
| 5.110.0 | no |
| 5.110.1 | no |
| 5.110.2 | no |
| 5.110.3 | yes |
Terser is not involved — the collapse is already present in the unminified output, and crossing the versions confirms it:
| webpack | terser | collapsed |
|---|---|---|
| 5.110.3 | 5.51.2 | yes |
| 5.110.3 | 5.49.0 | yes |
| 5.109.2 | 5.51.2 | no |
| 5.109.2 | 5.49.0 | no |
Attribution
Patching the line above in an installed v5.110.3 isolates the cause to #21876:
| variant | collapsed |
|---|---|
| unchanged v5.110.3 | yes |
`(${reference})` → `${reference}` (parenthesization reverted) |
no |
always `;(${reference})` |
no |
For completeness: #21907 landed in the same release and removed the ConcatenationScope.registerReplacedRequire / isInsideReplacedRequire containment guard, which looked related in the source diff but turned out not to cause this — reverting only the parenthesization is sufficient.
Impact
Any codebase with two adjacent require() statements where the first lacks a semicolon, inside a module that contains at least one import, silently produces a bundle that throws in the entry module. In our case it took down a production site after a routine dependency update; the source had been unchanged for over two years. There is no build error or warning.
Note on AI usage
The reproduction, the bisect, the version crossing and the attribution were produced with Claude Code. Every number in the tables comes from an actual build in an isolated directory, not from reading the source.
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Activity
blankse commentedon Sep 4, 2026
Duplicate of #21955, which was filed about twenty minutes earlier and describes the same defect. I verified it is not merely the same line but the same branch:
ConcatenatedModule.jscontains the parenthesizing expression twice, and neutralizing them individually in an installed 5.110.3 shows that only line 989,else if (accessorCall && !asCall), is responsible — both reports land there.Closing in favour of #21955. The parts of my investigation that report does not cover are now a comment over there: a four-file reproduction with no third-party dependency, the fact that the trigger needs no eliminated re-export (two adjacent
require()statements are enough, so a plainCommonJsRequireDependencyis affected as well), the 5.109.2–5.110.2 bisect, and the version crossing that rules out terser.fix(concatenate-modules): guard a module reference against ASI
fix(concatenate-modules): guard a module reference against ASI
fix(concatenate-modules): guard a module reference against ASI (#21962)