Skip to content

About

Minimal reproduction: webpack optimization.inlineExports + filesystem cache keeps stale constant values in consumer modules

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

webpack: optimization.inlineExports + filesystem cache keeps stale constant values

Minimal reproduction for a cache-correctness bug in webpack.

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.

The provider module is rebuilt correctly, but its consumers keep the value that was inlined during a previous build. The emitted bundle silently differs from what a cold build produces for the same inputs.

Setup

npm install

Case A — a DefinePlugin / EnvironmentPlugin 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.js
build expected actual
1st (FLAG=on) if ((/* inlined export .FLAG */"on") === "on") same
2nd (FLAG=off) if ((/* inlined export .FLAG */"off") === "on") "on" — stale

On the second build the inlined literal should have become "off", but it is still "on" from the first build.

minimize is disabled in this reproduction so that the inlined values stay visible in the output. The if statement itself is therefore present in both builds — what matters is the literal inside it.

Case 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.js
build expected actual
1st (NUM = 5) (/* inlined export .NUM */5) + 1 same
2nd (NUM = 6) (/* inlined export .NUM */6) + 1 5 — stale

Control — a cold build produces the correct output

npm run clean
FLAG=off npm run build && grep -o 'NUM_PLUS_ONE.*' dist/out.js
# => NUM_PLUS_ONE", (/* inlined export .NUM */6) + 1);

The inputs are correct; only the cached build is wrong.

Workaround

Uncomment inlineExports: false in webpack.config.js, then re-run Case B:

1st (NUM = 5) => const NUM = 5;  /  console.log("NUM_PLUS_ONE", NUM + 1);
2nd (NUM = 6) => const NUM = 6;  /  console.log("NUM_PLUS_ONE", NUM + 1);

The consumer references the export instead of embedding the value, so the change propagates correctly.

Affected versions

webpack inlineExports Case A Case B
5.107.2 n/a (option does not exist) 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

Background

optimization.inlineExports was added and enabled by default for production in v5.108.0 (D(optimization, "inlineExports", production) in lib/config/defaults.js).

Notes

These are observations from running the builds above; I have not traced the cause in webpack's source.

Only changes where both the old and the new value are inlinable go unnoticed. A change that crosses the inlinable size boundary is picked up correctly. Starting from export const NUM = 5;, 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

Which declarations get inlined. The documented scope of the option is:

Inline ESM exports that bind to small primitive constants (≤6-byte null/undefined/boolean/number/string). Inlining makes the import dependency inactive so DCE can drop the export and possibly the module.

Testing declaration shapes one by one matches this: export default, computed expressions such as 1 + 2, and access through import * as ns are inlined, while let, objects and arrays are not. Note that booleans, null and undefined are inlined regardless of length — the 6-byte limit only applies to strings and numbers.

About

Minimal reproduction: webpack optimization.inlineExports + filesystem cache keeps stale constant values in consumer modules

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages