There's a bug in Webpack's SideEffectsFlagPlugin
that causes nondeterministic build output. Depending on the order of compilation.modules
(an unordered Set
), the SideEffectsFlagPlugin
will sometimes not remove a side-effect-free module from the graph when it's re-exporting a module via a namespace export.
In this example, foo.js
re-exports bar.js
via a namespace re-export (export * as bar from "./bar.js"
). If foo.js
is ordered after bar.js
in compilation.modules
, then it correctly gets removed from the module graph. However, if bar.js
is ordered before foo.js
, then foo.js
does not get removed from the graph.
Because this is a small bundle with small modules, the module order is pretty much guaranteed to be the same between runs. In order to repro the issue, I created two small plugins to ensure module orders that replicate the bug (EnsureOrderForTreeShakingPlugin
and WrongOrderForTreeShakingPlugin
).
To run webpack and repro the bug, you can run pnpm start
.
When foo.js
is ordered before bar.js
, the optimizeDependencies
hook callback in SideEffectsFlagPlugin
:
- Deactivates the connection between
app.js
andfoo.js
and creates a new one betweenapp.js
andbar.js
(when processingfoo.js
'sincomingConnections
) - Deactivates the new connection between
app.js
andbar.js
and creates a new one betweenapp.js
andbaz.js
(when processingbar.js
's incomingConnections)
When bar.js
is ordered before foo.js
, the optimizeDependencies
hook callback in SideEffectsFlagPlugin
:
- Does not create a new connection when processing
bar.js
(hits this code path: https://github.com/webpack/webpack/blob/64707c9c4fd5307d4997f0e73145ca26c5245bc3/lib/ModuleGraph.js#L229) - Deactivates the connection between
app.js
andfoo.js
and creates a new one betweenapp.js
andbar.js
(when processingfoo.js
'sincomingConnections
)
Because it never re-processes bar.js
's incomingConnections
, it is never able to deactivate the connection between app.js
and bar.js
+ create a new one between app.js
and baz.js
.