fix: scope parsed module sources by asset - #734
Conversation
`getViewerData()` merged the parsed module map of every asset into a single flat object, but Webpack module IDs are only unique within a compilation. Assets produced by separate compilations - a main bundle and a worker bundle, or a multi-config build - both number their modules from `0`, so the last parsed asset overwrote the earlier ones and modules were attributed another asset's parsed source and size. Store parsed sources per asset name and look them up using only the current asset's map, so each module reports the source that was actually parsed out of the asset it belongs to. Fixes webpack#732
🦋 Changeset detectedLatest commit: 4e4250a The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Scoping the parsed module maps by asset was not enough on its own. A module that belongs to several assets is represented by a single stats object, and every asset containing it receives that same object. The module tree keeps a live reference to it and reads `parsedSrc` lazily, after the per-asset loop has finished, so the asset processed last decided what all of them displayed. `Module.mergeData()` accumulated `size` and `parsedSrc` onto those shared objects for the same reason. Give each asset its own copies of its modules before attributing any parsed source, so writing a source for one asset can no longer change what another asset reports. `test/analyzerUtils.js` asserted on the stats objects it passed in, so it now asserts on the returned chart data instead, matching parsed size against the source length and pinning the source itself through its gzip size.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #734 +/- ##
==========================================
+ Coverage 78.58% 85.35% +6.76%
==========================================
Files 17 17
Lines 1060 1065 +5
Branches 383 387 +4
==========================================
+ Hits 833 909 +76
+ Misses 199 142 -57
+ Partials 28 14 -14 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
valscion
left a comment
There was a problem hiding this comment.
Thanks! Can you explain what kind of source code and webpack configuration produces these test bundles and the stats.json? I'm not sure I follow the test fixture logic here. The stats.json seems misleasing already from the webpack side? The shared module seems like it should have same size in both bundles if stats.jsln stat size is to be believed but the bundle contents differ for the shared module.
Replace the hand-written shared-module fixture with output from a documented Webpack 5 build. Include the source and configuration, use purpose-based bundle names, cover the stat-only fallback, and remove the unreachable array fallback.
|
This test reproduces a Webpack build with two entry points. Both entry points import the same // messages.js
export const longMessage = "This message is longer.";
export const shortMessage = "x";
// long-message-entry.js
import { longMessage } from "./messages.js";
console.log(longMessage);
// short-message-entry.js
import { shortMessage } from "./messages.js";
console.log(shortMessage);
Webpack creates one bundle for each entry:
The configuration disables module concatenation, split chunks, and the separate runtime chunk. Webpack gives Webpack removes the unused export from each bundle. The emitted factory in The stats contain one record for module ID The analyzer wrote each asset's I added the source files, Webpack configuration, and a README to the fixture. I also removed the |
Fixes #732.
Problem
Parsed module sources were attributed globally in two ways.
1. Module IDs were treated as globally unique.
getViewerData()merged every asset's parsedmodule map into one object:
Webpack module IDs are unique only within a compilation. Separate compilations can reuse the same
IDs. The last parsed asset then overwrote sources from earlier assets.
2. Modules shared by several assets were changed in place. Each asset received the same stats
object for a shared module. The analyzer wrote
parsedSrcto that object. The module tree read thevalue after all assets were processed, so the last asset set the value for every asset.
Both cases made modules report parsed and compressed sizes from another asset.
Fix
Object.hasOwnfor asset and module lookups.parsedSrc.The asset and module indexes added in #723 remain unchanged. Lookup complexity remains linear.
Regression tests
The tests cover both failure modes.
test/stats/with-worker-loader-dynamic-importfixture has a root bundle and a worker bundle.Both compilations use module ID
0for different modules.test/stats/with-module-in-multiple-assetsfixture comesfrom a Webpack 5.105.2 production build. Two entry files import different exports from
messages.js. Webpack reports the source size as87for both assets, but tree shaking createsfactories of 59 and 37 characters.
The new fixture includes its source files, Webpack configuration, generated bundles, reduced
stats, and a README.
A separate test covers the fallback used when the analyzer cannot parse any bundle. The local
coverage report covers all changed statements and branches in
src/analyzer.js.test/analyzerUtils.jsnow checks the returned chart data instead of depending on mutation of theinput stats.
Notes
bundleStatsasset array, asset names, orisChildflags.AI assistance
This change was developed with meaningful AI assistance for investigation, patch drafting, and
test authoring. I reviewed and verified all code, tests, and validation output before submitting.