Repository navigation
Description
Have you used AI?
Yes
Which part of webpack is affected?
cache (persistent/filesystem cache), optimization (tree-shaking, splitChunks, concatenation)
Bug Description
When optimization.inlineExports is enabled (the default in production mode since v5.108.0) together with cache.type: "filesystem", changing the value of an inlined constant export does not invalidate the code generation cache of the modules that consume it.
Consuming modules keep the value that was inlined during a previous build, so the emitted bundle silently differs from what a cold build produces for the same inputs.
This affects both:
- values injected via
DefinePlugin/EnvironmentPlugin(e.g.export const FLAG = process.env.FLAG) - plain source constants (e.g.
export const NUM = 5changed to6)
Link to Minimal Reproduction and steps to reproduce
https://github.com/YuyaYoshioka/webpack-inline-exports-cache-repro
git clone https://github.com/YuyaYoshioka/webpack-inline-exports-cache-repro
cd webpack-inline-exports-cache-repro
npm ciThe whole project is three files:
src/env.js
export const FLAG = process.env.FLAG;
export const NUM = 5;src/index.js
import { FLAG, NUM } from "./env";
if (FLAG === "on") {
console.log("DEBUG_BLOCK_VISIBLE");
}
console.log("NUM_PLUS_ONE", NUM + 1);webpack.config.js uses mode: "production", cache: { type: "filesystem" },
optimization: { minimize: false } and new webpack.EnvironmentPlugin({ FLAG: null }).
minimize is off so that the inlined literals stay visible in the output.
Case A — a DefinePlugin value changes
npm run clean
FLAG=on npm run build && grep -o 'if ((.*=== "on")' dist/out.js
FLAG=off npm run build && grep -o 'if ((.*=== "on")' dist/out.jsCase B — a plain source constant changes
src/index.js is not modified in this case; only the provider changes.
npm run clean
FLAG=off npm run build && grep -o 'NUM_PLUS_ONE.*' dist/out.js
sed -i '' 's/NUM = 5/NUM = 6/' src/env.js # GNU sed: sed -i 's/NUM = 5/NUM = 6/'
FLAG=off npm run build && grep -o 'NUM_PLUS_ONE.*' dist/out.jsControl — a cold build
npm run clean
FLAG=off npm run build && grep -o 'NUM_PLUS_ONE.*' dist/out.jsExpected Behavior
A build that reuses the filesystem cache should produce the same output as a cold build with the same inputs.
- Case A: the second build emits
if ((/* inlined export .FLAG */"off") === "on"). - Case B: the second build emits
(/* inlined export .NUM */6) + 1.
Deleting the cache directory and rebuilding does produce exactly this output, which confirms the inputs are correct.
Actual Behavior
The consuming module keeps the previously inlined literal.
- Case A: the second build still emits
if ((/* inlined export .FLAG */"on") === "on"), even thoughFLAGis now"off". - Case B: the second build still emits
(/* inlined export .NUM */5) + 1.
Setting optimization.inlineExports: false produces the correct output in both cases, as does webpack v5.107.2 (where the option did not exist yet).
| webpack | inlineExports |
Case A (env value) | Case B (source constant) |
|---|---|---|---|
| 5.107.2 | n/a (option absent) | correct | correct |
| 5.108.0 | default (true) |
stale | stale |
| 5.109.2 | default (true) |
stale | stale |
| 5.109.2 | false |
correct | correct |
| 5.110.3 | default (true) |
stale | stale |
| 5.110.3 | false |
correct | correct |
Environment
⬡ webpack info
────────────────────────────────────────────────────────────────────────
System and environment information.
⬡ System:
────────────────────────────────────────────────────────────────────────
OS: macOS 15.5
CPU: (12) arm64 Apple M3 Pro
Memory: 442.55 MB / 36.00 GB
⬡ Binaries:
────────────────────────────────────────────────────────────────────────
Node: 22.19.0 - /Users/yuya.yoshioka/.nodenv/versions/22.19.0/bin/node
Yarn: 1.22.22 - /Users/yuya.yoshioka/.nodenv/versions/22.19.0/bin/yarn
npm: 11.12.1 - /Users/yuya.yoshioka/.nodenv/versions/22.19.0/bin/npm
⬡ Browsers:
────────────────────────────────────────────────────────────────────────
Chrome: 152.0.7977.83
Safari: 18.5
⬡ Packages:
────────────────────────────────────────────────────────────────────────
webpack: 5.110.3 → 5.110.3
webpack-cli: ^7.2.3 → 7.2.3
────────────────────────────────────────────────────────────────────────
ℹ Run 'webpack --help=verbose' to see all available commands and options.
Webpack documentation: https://webpack.js.org/
CLI documentation: https://webpack.js.org/api/cli/
Made with ♥ by the webpack teamIs this a regression?
Yes (please specify version below)
Last Working Version
v5.107.2
Additional Context
Additional observations
I have not traced this in webpack's source, so I cannot say where the invalidation is missed. The following are observations from running builds. The first two can be reproduced directly with the linked repository; the third needs a few extra exports added to src/env.js.
A change that crosses the "inlinable" size boundary is detected; a change that stays inside it is not.
Starting from export const NUM = 5; and building once, then changing the value and rebuilding with the same cache:
2nd build changes NUM to |
inlinable? | result |
|---|---|---|
6 (1 digit) |
yes → yes | (/* inlined export .NUM */5) + 1 — stale |
1234567 (7 digits) |
yes → no | NUM + 1 — updated correctly |
So the problem is not "the cache never notices this module". Something about the change is noticed when the value stops being inlinable, but a change from one inlinable value to another goes unnoticed.
Deleting the cache directory always produces the correct output, which is how I concluded the inputs are fine and only the cached build is wrong.
Which declarations are affected. Testing declaration shapes one by one matches the documented scope of the option ("Inline ESM exports that bind to small primitive constants (≤6-byte null/undefined/boolean/number/string)"): export default, computed expressions such as 1 + 2, and access through import * as ns are inlined, while let, objects and arrays are not. Booleans, null and undefined are inlined regardless of length.
I also noticed #20800, which looks similar in nature (a code generation cache not reflecting connection changes). I have not verified whether the two are actually related.
Why this matters
The failure is silent. Nothing errors, nothing is logged, and the build succeeds — the bundle is simply generated from stale code. The only ways to notice are to diff against a cold build or to inspect the emitted output directly.
Because inlineExports is on by default in production and cache.type: "filesystem" is widely used — including CI setups that persist the cache across builds — I suspect other projects may be affected without noticing. A plain npm run build run twice with a constant change in between is enough to trigger it.
Workaround
optimization: { inlineExports: false }Note on AI usage
Per the AI policy: I used an AI assistant to help investigate this. All of the results above come from builds I ran myself, and I verified the reproduction, the version boundary, and the workaround before filing. I am happy to answer questions and to test patches.
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Effort
Activity
refactor(cache): represent inlined imports as INACTIVE_INLINED
fix(cache): invalidate consumer codegen when inlined exports change (#…