From 155b64bf47dda4a4831d87d647f640af9ed7a90e Mon Sep 17 00:00:00 2001 From: Menny Mezamer-Tov <3127778+Menny1337@users.noreply.github.com> Date: Sun, 23 Aug 2026 17:52:23 +0300 Subject: [PATCH 1/3] fix: scope parsed module sources by asset `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 #732 --- .changeset/nervous-workers-attribute.md | 5 ++++ src/analyzer.js | 23 +++++++++++------ test/analyzer.js | 34 +++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 .changeset/nervous-workers-attribute.md diff --git a/.changeset/nervous-workers-attribute.md b/.changeset/nervous-workers-attribute.md new file mode 100644 index 00000000..66dfcad8 --- /dev/null +++ b/.changeset/nervous-workers-attribute.md @@ -0,0 +1,5 @@ +--- +"webpack-bundle-analyzer": patch +--- + +Scope parsed module sources to the asset they were parsed from, so assets produced by separate compilations (for example a main bundle and a worker bundle) no longer overwrite each other's parsed sizes when they reuse the same module IDs. diff --git a/src/analyzer.js b/src/analyzer.js index 076fd6a1..70fda671 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -334,12 +334,14 @@ function getViewerData(bundleStats, bundleDir, opts) { // Trying to parse bundle assets and get real module sizes if `bundleDir` is provided /** @type {Record | null} */ let bundlesSources = null; - /** @type {Record | null} */ - let parsedModules = null; + // Parsed sources are stored per asset because different assets can reuse the same module IDs + // (e.g. a root bundle and a worker bundle both numbering their modules from `0`). + /** @type {Record | null} */ + let parsedModulesByAsset = null; if (bundleDir) { bundlesSources = {}; - parsedModules = {}; + parsedModulesByAsset = {}; for (const statAsset of bundleStats.assets) { const assetFile = path.join(bundleDir, statAsset.name); @@ -380,12 +382,12 @@ function getViewerData(bundleStats, bundleDir, opts) { src: bundleInfo.src, runtimeSrc: bundleInfo.runtimeSrc, }; - Object.assign(parsedModules, bundleInfo.modules); + parsedModulesByAsset[statAsset.name] = bundleInfo.modules; } if (Object.keys(bundlesSources).length === 0) { bundlesSources = null; - parsedModules = null; + parsedModulesByAsset = null; logger.warn( "\nNo bundles were parsed. Analyzer will show only original module sizes from stats file.\n", ); @@ -422,6 +424,11 @@ function getViewerData(bundleStats, bundleDir, opts) { bundlesSources && Object.hasOwn(bundlesSources, statAsset.name) ? bundlesSources[statAsset.name] : null; + const assetParsedModules = + parsedModulesByAsset && + Object.hasOwn(parsedModulesByAsset, statAsset.name) + ? parsedModulesByAsset[statAsset.name] + : null; if (assetSources) { asset.parsedSize = Buffer.byteLength(assetSources.src); @@ -440,16 +447,16 @@ function getViewerData(bundleStats, bundleDir, opts) { } // Adding parsed sources - if (parsedModules) { + if (assetParsedModules) { /** @type {StatsModule[]} */ const unparsedEntryModules = []; for (const statsModule of assetModules) { if ( typeof statsModule.id !== "undefined" && - parsedModules[statsModule.id] + Object.hasOwn(assetParsedModules, statsModule.id) ) { - statsModule.parsedSrc = parsedModules[statsModule.id]; + statsModule.parsedSrc = assetParsedModules[statsModule.id]; } else if (isEntryModule(statsModule)) { unparsedEntryModules.push(statsModule); } diff --git a/test/analyzer.js b/test/analyzer.js index 4980c8f6..a11c28b8 100644 --- a/test/analyzer.js +++ b/test/analyzer.js @@ -4,6 +4,7 @@ const path = require("node:path"); const url = require("node:url"); const puppeteer = require("puppeteer"); const { getViewerData } = require("../src/analyzer"); +const { parseBundle } = require("../src/parseUtils"); const { isZstdSupported } = require("../src/sizeUtils"); let browser; @@ -126,6 +127,39 @@ describe("Analyzer", () => { }); }); + it("should not attribute parsed sources across assets reusing module IDs", () => { + const statsDir = path.resolve( + __dirname, + "./stats/with-worker-loader-dynamic-import", + ); + // Reading the stats file instead of `require`ing it because `getViewerData` mutates it. + const stats = JSON.parse( + fs.readFileSync(path.join(statsDir, "stats.json"), "utf8"), + ); + + // The root compilation and the worker child compilation both number their modules from `0`, + // so module ID `0` refers to a different module in each asset. + const duplicateModuleId = "0"; + const { modules: rootModules } = parseBundle( + path.join(statsDir, "bundle.js"), + ); + const { modules: workerModules } = parseBundle( + path.join(statsDir, "bundle.worker.js"), + ); + const rootModuleSrc = rootModules[duplicateModuleId]; + + expect(rootModuleSrc).toEqual(expect.any(String)); + expect(workerModules[duplicateModuleId]).not.toBe(rootModuleSrc); + + const chartData = getViewerData(stats, statsDir); + const rootAsset = chartData.find((asset) => asset.label === "bundle.js"); + + expect(rootAsset.groups).toHaveLength(1); + expect(rootAsset.groups[0].parsedSize).toBe( + Buffer.byteLength(rootModuleSrc), + ); + }); + it("should update the treemap when a chunk is deselected", async () => { generateReportFrom("with-worker-loader-dynamic-import/stats.json"); const page = await browser.newPage(); From 12c7afc07b0318deedc975bc2b9d99268fdb2d85 Mon Sep 17 00:00:00 2001 From: Menny Mezamer-Tov <3127778+Menny1337@users.noreply.github.com> Date: Sun, 23 Aug 2026 18:47:44 +0300 Subject: [PATCH 2/3] fix: isolate per-asset parsed source writes 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. --- .changeset/nervous-workers-attribute.md | 2 +- src/analyzer.js | 8 ++++ test/analyzer.js | 41 ++++++++++++++++++- test/analyzerUtils.js | 19 +++++++-- .../with-module-in-multiple-assets/bridge.js | 1 + .../with-module-in-multiple-assets/main.js | 1 + .../with-module-in-multiple-assets/stats.json | 37 +++++++++++++++++ 7 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 test/stats/with-module-in-multiple-assets/bridge.js create mode 100644 test/stats/with-module-in-multiple-assets/main.js create mode 100644 test/stats/with-module-in-multiple-assets/stats.json diff --git a/.changeset/nervous-workers-attribute.md b/.changeset/nervous-workers-attribute.md index 66dfcad8..47f6485d 100644 --- a/.changeset/nervous-workers-attribute.md +++ b/.changeset/nervous-workers-attribute.md @@ -2,4 +2,4 @@ "webpack-bundle-analyzer": patch --- -Scope parsed module sources to the asset they were parsed from, so assets produced by separate compilations (for example a main bundle and a worker bundle) no longer overwrite each other's parsed sizes when they reuse the same module IDs. +Scope parsed module sources to the asset they were parsed from, so assets that reuse the same module IDs, or that share a module, no longer report each other's parsed and compressed sizes. diff --git a/src/analyzer.js b/src/analyzer.js index 70fda671..fab024c3 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -417,6 +417,14 @@ function getViewerData(bundleStats, bundleDir, opts) { ); } + // A module that belongs to several assets is represented by a single stats object, and the + // module tree reads `parsedSrc` lazily, after every asset has been processed. Copying the + // modules per asset keeps attributing a parsed source to one asset from changing what the + // other assets containing that module report. + assetModules = (assetModules || []).map((statsModule) => ({ + ...statsModule, + })); + const asset = (result[statAsset.name] = /** @type {Asset} */ ({ size: statAsset.size, })); diff --git a/test/analyzer.js b/test/analyzer.js index a11c28b8..deb43d91 100644 --- a/test/analyzer.js +++ b/test/analyzer.js @@ -155,9 +155,46 @@ describe("Analyzer", () => { const rootAsset = chartData.find((asset) => asset.label === "bundle.js"); expect(rootAsset.groups).toHaveLength(1); - expect(rootAsset.groups[0].parsedSize).toBe( - Buffer.byteLength(rootModuleSrc), + // Module `parsedSize` is the length of the parsed source. + expect(rootAsset.groups[0].parsedSize).toBe(rootModuleSrc.length); + }); + + it("should not attribute parsed sources across assets sharing a module", () => { + const statsDir = path.resolve( + __dirname, + "./stats/with-module-in-multiple-assets", + ); + // Reading the stats file instead of `require`ing it because `getViewerData` mutates it. + const stats = JSON.parse( + fs.readFileSync(path.join(statsDir, "stats.json"), "utf8"), + ); + + // `./src/shared.js` is a single stats module belonging to both assets, but each asset + // embeds its own copy of it, so both assets have a different parsed source for module ID `1`. + const sharedModuleId = "1"; + // Module `parsedSize` is the length of the parsed source. + const expectedParsedSizes = { + "main.js": parseBundle(path.join(statsDir, "main.js")).modules[ + sharedModuleId + ].length, + "bridge.js": parseBundle(path.join(statsDir, "bridge.js")).modules[ + sharedModuleId + ].length, + }; + expect(expectedParsedSizes["main.js"]).not.toBe( + expectedParsedSizes["bridge.js"], ); + + const chartData = getViewerData(stats, statsDir); + const sharedModuleSizes = Object.fromEntries( + chartData.map((asset) => [ + asset.label, + asset.groups[0].groups.find((group) => group.label === "shared.js") + .parsedSize, + ]), + ); + + expect(sharedModuleSizes).toEqual(expectedParsedSizes); }); it("should update the treemap when a chunk is deselected", async () => { diff --git a/test/analyzerUtils.js b/test/analyzerUtils.js index 3e06846d..f3197572 100644 --- a/test/analyzerUtils.js +++ b/test/analyzerUtils.js @@ -2,6 +2,7 @@ const fs = require("node:fs"); const path = require("node:path"); const { getViewerData } = require("../src/analyzer"); +const { getCompressedSize } = require("../src/sizeUtils"); const BUNDLES_DIR = path.resolve(__dirname, "./bundles"); @@ -76,10 +77,22 @@ describe("getViewerData", () => { }, }; - getViewerData(stats, BUNDLES_DIR); + const chartData = getViewerData(stats, BUNDLES_DIR); + const modulesByPath = Object.fromEntries( + chartData[0].groups[0].groups.map((group) => [group.path, group]), + ); - expect(dependencyModule.parsedSrc).toBe(expectedModules[447]); - expect(entryModule.parsedSrc).toBe(expectedModules[956]); + // Asserting on the returned chart data rather than on the input stats objects, because + // `getViewerData` copies the modules of every asset before attributing parsed sources to them. + // Module `parsedSize` is the source length, and the gzip size pins the source itself. + expect(modulesByPath["./src/dependency.js"]).toMatchObject({ + parsedSize: expectedModules[447].length, + gzipSize: getCompressedSize("gzip", expectedModules[447]), + }); + expect(modulesByPath["./src/entry.js"]).toMatchObject({ + parsedSize: expectedModules[956].length, + gzipSize: getCompressedSize("gzip", expectedModules[956]), + }); expect(chunksAccessCount).toBe(1); }); }); diff --git a/test/stats/with-module-in-multiple-assets/bridge.js b/test/stats/with-module-in-multiple-assets/bridge.js new file mode 100644 index 00000000..14401b36 --- /dev/null +++ b/test/stats/with-module-in-multiple-assets/bridge.js @@ -0,0 +1 @@ +!function(m){var i={};function r(e){if(i[e])return i[e].exports;var t=i[e]={i:e,l:!1,exports:{}};return m[e].call(t.exports,t,t.exports,r),t.l=!0,t.exports}r.m=m,r.c=i,r(r.s=3)}({1:function(e,t,r){"use strict";t.shared=function(){return"s"}},3:function(e,t,r){"use strict";r(1),console.log("bridge-entry-with-extra-code-here")}}); diff --git a/test/stats/with-module-in-multiple-assets/main.js b/test/stats/with-module-in-multiple-assets/main.js new file mode 100644 index 00000000..a9421a9c --- /dev/null +++ b/test/stats/with-module-in-multiple-assets/main.js @@ -0,0 +1 @@ +!function(m){var i={};function r(e){if(i[e])return i[e].exports;var t=i[e]={i:e,l:!1,exports:{}};return m[e].call(t.exports,t,t.exports,r),t.l=!0,t.exports}r.m=m,r.c=i,r(r.s=2)}({1:function(e,t,r){"use strict";t.shared=function(){return"shared-in-main"}},2:function(e,t,r){"use strict";r(1),console.log("main")}}); diff --git a/test/stats/with-module-in-multiple-assets/stats.json b/test/stats/with-module-in-multiple-assets/stats.json new file mode 100644 index 00000000..66f86199 --- /dev/null +++ b/test/stats/with-module-in-multiple-assets/stats.json @@ -0,0 +1,37 @@ +{ + "assets": [ + { "name": "main.js", "size": 300, "chunks": [0], "info": {} }, + { "name": "bridge.js", "size": 320, "chunks": [1], "info": {} } + ], + "chunks": [], + "entrypoints": { + "main": { "name": "main", "assets": [{ "name": "main.js" }] }, + "bridge": { "name": "bridge", "assets": [{ "name": "bridge.js" }] } + }, + "modules": [ + { + "id": 1, + "identifier": "./src/shared.js", + "name": "./src/shared.js", + "size": 20, + "depth": 1, + "chunks": [0, 1] + }, + { + "id": 2, + "identifier": "./src/main.js", + "name": "./src/main.js", + "size": 30, + "depth": 1, + "chunks": [0] + }, + { + "id": 3, + "identifier": "./src/bridge.js", + "name": "./src/bridge.js", + "size": 30, + "depth": 1, + "chunks": [1] + } + ] +} From 4e4250a97768ec5511a7bf7dec137f2d4254e683 Mon Sep 17 00:00:00 2001 From: Menny Mezamer-Tov <3127778+Menny1337@users.noreply.github.com> Date: Mon, 24 Aug 2026 11:29:12 +0300 Subject: [PATCH 3/3] test: clarify shared-module fixture and coverage 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. --- src/analyzer.js | 12 +-- test/analyzer.js | 89 ++++++++++------- .../with-module-in-multiple-assets/README.md | 45 +++++++++ .../with-module-in-multiple-assets/bridge.js | 1 - .../long-message.js | 1 + .../with-module-in-multiple-assets/main.js | 1 - .../short-message.js | 1 + .../src/long-message-entry.js | 3 + .../src/messages.js | 2 + .../src/short-message-entry.js | 3 + .../with-module-in-multiple-assets/stats.json | 99 +++++++++++++------ .../webpack.config.js | 21 ++++ 12 files changed, 208 insertions(+), 70 deletions(-) create mode 100644 test/stats/with-module-in-multiple-assets/README.md delete mode 100644 test/stats/with-module-in-multiple-assets/bridge.js create mode 100644 test/stats/with-module-in-multiple-assets/long-message.js delete mode 100644 test/stats/with-module-in-multiple-assets/main.js create mode 100644 test/stats/with-module-in-multiple-assets/short-message.js create mode 100644 test/stats/with-module-in-multiple-assets/src/long-message-entry.js create mode 100644 test/stats/with-module-in-multiple-assets/src/messages.js create mode 100644 test/stats/with-module-in-multiple-assets/src/short-message-entry.js create mode 100644 test/stats/with-module-in-multiple-assets/webpack.config.js diff --git a/src/analyzer.js b/src/analyzer.js index fab024c3..63954c62 100644 --- a/src/analyzer.js +++ b/src/analyzer.js @@ -417,13 +417,11 @@ function getViewerData(bundleStats, bundleDir, opts) { ); } - // A module that belongs to several assets is represented by a single stats object, and the - // module tree reads `parsedSrc` lazily, after every asset has been processed. Copying the - // modules per asset keeps attributing a parsed source to one asset from changing what the - // other assets containing that module report. - assetModules = (assetModules || []).map((statsModule) => ({ - ...statsModule, - })); + // A module that is a part of more than one asset is one stats object. Every asset that + // contains that module gets the same object. The module tree reads `parsedSrc` late, after + // all assets are processed. Copy the modules for each asset. Then a parsed source that is + // set for one asset cannot change what another asset shows. + assetModules = assetModules.map((statsModule) => ({ ...statsModule })); const asset = (result[statAsset.name] = /** @type {Asset} */ ({ size: statAsset.size, diff --git a/test/analyzer.js b/test/analyzer.js index deb43d91..f7a5e9de 100644 --- a/test/analyzer.js +++ b/test/analyzer.js @@ -3,6 +3,7 @@ const fs = require("node:fs"); const path = require("node:path"); const url = require("node:url"); const puppeteer = require("puppeteer"); +const Logger = require("../src/Logger"); const { getViewerData } = require("../src/analyzer"); const { parseBundle } = require("../src/parseUtils"); const { isZstdSupported } = require("../src/sizeUtils"); @@ -127,74 +128,96 @@ describe("Analyzer", () => { }); }); - it("should not attribute parsed sources across assets reusing module IDs", () => { + it("should not attribute parsed sources across assets that reuse module IDs", () => { const statsDir = path.resolve( __dirname, "./stats/with-worker-loader-dynamic-import", ); - // Reading the stats file instead of `require`ing it because `getViewerData` mutates it. + // `getViewerData` changes the stats it receives, so read a new copy of them. const stats = JSON.parse( fs.readFileSync(path.join(statsDir, "stats.json"), "utf8"), ); - // The root compilation and the worker child compilation both number their modules from `0`, - // so module ID `0` refers to a different module in each asset. - const duplicateModuleId = "0"; - const { modules: rootModules } = parseBundle( - path.join(statsDir, "bundle.js"), - ); - const { modules: workerModules } = parseBundle( - path.join(statsDir, "bundle.worker.js"), - ); - const rootModuleSrc = rootModules[duplicateModuleId]; + // Two compilations number their modules from `0`. Module ID `0` is a different module in + // each asset. + const moduleId = "0"; + const rootSource = parseBundle(path.join(statsDir, "bundle.js")).modules[ + moduleId + ]; + const workerSource = parseBundle(path.join(statsDir, "bundle.worker.js")) + .modules[moduleId]; - expect(rootModuleSrc).toEqual(expect.any(String)); - expect(workerModules[duplicateModuleId]).not.toBe(rootModuleSrc); + expect(rootSource).toEqual(expect.any(String)); + expect(workerSource).not.toBe(rootSource); const chartData = getViewerData(stats, statsDir); const rootAsset = chartData.find((asset) => asset.label === "bundle.js"); + // `parsedSize` is the length of the parsed source. expect(rootAsset.groups).toHaveLength(1); - // Module `parsedSize` is the length of the parsed source. - expect(rootAsset.groups[0].parsedSize).toBe(rootModuleSrc.length); + expect(rootAsset.groups[0].parsedSize).toBe(rootSource.length); }); - it("should not attribute parsed sources across assets sharing a module", () => { + it("should not attribute parsed sources across assets that share a module", () => { const statsDir = path.resolve( __dirname, "./stats/with-module-in-multiple-assets", ); - // Reading the stats file instead of `require`ing it because `getViewerData` mutates it. + // `getViewerData` changes the stats it receives, so read a new copy of them. const stats = JSON.parse( fs.readFileSync(path.join(statsDir, "stats.json"), "utf8"), ); - // `./src/shared.js` is a single stats module belonging to both assets, but each asset - // embeds its own copy of it, so both assets have a different parsed source for module ID `1`. - const sharedModuleId = "1"; - // Module `parsedSize` is the length of the parsed source. + // `messages.js` is one source module used by two bundles. Each bundle uses a different + // export, so Webpack creates a different factory for the module in each bundle. + const sharedModuleId = "906"; const expectedParsedSizes = { - "main.js": parseBundle(path.join(statsDir, "main.js")).modules[ - sharedModuleId - ].length, - "bridge.js": parseBundle(path.join(statsDir, "bridge.js")).modules[ - sharedModuleId - ].length, + "long-message.js": parseBundle(path.join(statsDir, "long-message.js")) + .modules[sharedModuleId].length, + "short-message.js": parseBundle(path.join(statsDir, "short-message.js")) + .modules[sharedModuleId].length, }; - expect(expectedParsedSizes["main.js"]).not.toBe( - expectedParsedSizes["bridge.js"], + + expect(expectedParsedSizes["long-message.js"]).not.toBe( + expectedParsedSizes["short-message.js"], ); const chartData = getViewerData(stats, statsDir); - const sharedModuleSizes = Object.fromEntries( + const parsedSizes = Object.fromEntries( chartData.map((asset) => [ asset.label, - asset.groups[0].groups.find((group) => group.label === "shared.js") + asset.groups[0].groups.find((group) => group.label === "messages.js") .parsedSize, ]), ); - expect(sharedModuleSizes).toEqual(expectedParsedSizes); + expect(parsedSizes).toEqual(expectedParsedSizes); + }); + + it("should show only stat sizes when no bundle can be parsed", () => { + const statsDir = path.resolve( + __dirname, + "./stats/with-module-in-multiple-assets", + ); + const stats = JSON.parse( + fs.readFileSync(path.join(statsDir, "stats.json"), "utf8"), + ); + const logger = new Logger("silent"); + const warn = jest.spyOn(logger, "warn"); + + // No asset file is in this directory, so every asset fails to parse. + const chartData = getViewerData(stats, path.join(statsDir, "src"), { + logger, + }); + + expect(warn).toHaveBeenCalledWith( + expect.stringContaining("No bundles were parsed"), + ); + + for (const asset of chartData) { + expect(asset.parsedSize).toBeUndefined(); + expect(asset.statSize).toBeGreaterThan(0); + } }); it("should update the treemap when a chunk is deselected", async () => { diff --git a/test/stats/with-module-in-multiple-assets/README.md b/test/stats/with-module-in-multiple-assets/README.md new file mode 100644 index 00000000..cd027cdf --- /dev/null +++ b/test/stats/with-module-in-multiple-assets/README.md @@ -0,0 +1,45 @@ +# Fixture: one module in two assets + +This fixture tests one source module that Webpack puts in two output bundles. + +## Test case + +The build has two entry files: + +- `src/long-message-entry.js` +- `src/short-message-entry.js` + +Both files import `src/messages.js`. This is the source module that the two bundles share. + +`messages.js` exports a long message and a short message. Each entry file imports only one of +these exports. Webpack creates these bundles: + +- `long-message.js` contains the long message. +- `short-message.js` contains the short message. + +The configuration disables module concatenation, split chunks, and the separate runtime chunk. +These settings keep the module visible in both bundles. + +## Stats and bundle sizes + +Webpack gives `messages.js` module ID `906`. The stats report its source size as `87` in both +chunks. + +Webpack removes the unused export from each bundle. This makes the emitted module factories +different: + +- The factory in `long-message.js` has 59 characters. +- The factory in `short-message.js` has 37 characters. + +The analyzer must report the correct factory size for each bundle. + +## Rebuild + +Run this command from the repository root: + +```sh +npx webpack --config test/stats/with-module-in-multiple-assets/webpack.config.js +``` + +The `stats.json` file is a reduced copy of the Webpack stats. It contains only the fields that the +analyzer reads. diff --git a/test/stats/with-module-in-multiple-assets/bridge.js b/test/stats/with-module-in-multiple-assets/bridge.js deleted file mode 100644 index 14401b36..00000000 --- a/test/stats/with-module-in-multiple-assets/bridge.js +++ /dev/null @@ -1 +0,0 @@ -!function(m){var i={};function r(e){if(i[e])return i[e].exports;var t=i[e]={i:e,l:!1,exports:{}};return m[e].call(t.exports,t,t.exports,r),t.l=!0,t.exports}r.m=m,r.c=i,r(r.s=3)}({1:function(e,t,r){"use strict";t.shared=function(){return"s"}},3:function(e,t,r){"use strict";r(1),console.log("bridge-entry-with-extra-code-here")}}); diff --git a/test/stats/with-module-in-multiple-assets/long-message.js b/test/stats/with-module-in-multiple-assets/long-message.js new file mode 100644 index 00000000..f8e5d515 --- /dev/null +++ b/test/stats/with-module-in-multiple-assets/long-message.js @@ -0,0 +1 @@ +(()=>{"use strict";var e={906(e,r,o){o.d(r,{U:()=>t});const t="This message is longer."}},r={};function o(t){var s=r[t];if(void 0!==s)return s.exports;var n=r[t]={exports:{}};return e[t](n,n.exports,o),n.exports}o.d=(e,r)=>{for(var t in r)o.o(r,t)&&!o.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},o.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r);var t=o(906);console.log(t.U)})(); \ No newline at end of file diff --git a/test/stats/with-module-in-multiple-assets/main.js b/test/stats/with-module-in-multiple-assets/main.js deleted file mode 100644 index a9421a9c..00000000 --- a/test/stats/with-module-in-multiple-assets/main.js +++ /dev/null @@ -1 +0,0 @@ -!function(m){var i={};function r(e){if(i[e])return i[e].exports;var t=i[e]={i:e,l:!1,exports:{}};return m[e].call(t.exports,t,t.exports,r),t.l=!0,t.exports}r.m=m,r.c=i,r(r.s=2)}({1:function(e,t,r){"use strict";t.shared=function(){return"shared-in-main"}},2:function(e,t,r){"use strict";r(1),console.log("main")}}); diff --git a/test/stats/with-module-in-multiple-assets/short-message.js b/test/stats/with-module-in-multiple-assets/short-message.js new file mode 100644 index 00000000..fbc10603 --- /dev/null +++ b/test/stats/with-module-in-multiple-assets/short-message.js @@ -0,0 +1 @@ +(()=>{"use strict";var r={906(r,e,o){o.d(e,{S:()=>t});const t="x"}},e={};function o(t){var n=e[t];if(void 0!==n)return n.exports;var s=e[t]={exports:{}};return r[t](s,s.exports,o),s.exports}o.d=(r,e)=>{for(var t in e)o.o(e,t)&&!o.o(r,t)&&Object.defineProperty(r,t,{enumerable:!0,get:e[t]})},o.o=(r,e)=>Object.prototype.hasOwnProperty.call(r,e);var t=o(906);console.log(t.S)})(); \ No newline at end of file diff --git a/test/stats/with-module-in-multiple-assets/src/long-message-entry.js b/test/stats/with-module-in-multiple-assets/src/long-message-entry.js new file mode 100644 index 00000000..12a592c7 --- /dev/null +++ b/test/stats/with-module-in-multiple-assets/src/long-message-entry.js @@ -0,0 +1,3 @@ +import { longMessage } from "./messages.js"; + +console.log(longMessage); diff --git a/test/stats/with-module-in-multiple-assets/src/messages.js b/test/stats/with-module-in-multiple-assets/src/messages.js new file mode 100644 index 00000000..5621c2a7 --- /dev/null +++ b/test/stats/with-module-in-multiple-assets/src/messages.js @@ -0,0 +1,2 @@ +export const longMessage = "This message is longer."; +export const shortMessage = "x"; diff --git a/test/stats/with-module-in-multiple-assets/src/short-message-entry.js b/test/stats/with-module-in-multiple-assets/src/short-message-entry.js new file mode 100644 index 00000000..df7ce38d --- /dev/null +++ b/test/stats/with-module-in-multiple-assets/src/short-message-entry.js @@ -0,0 +1,3 @@ +import { shortMessage } from "./messages.js"; + +console.log(shortMessage); diff --git a/test/stats/with-module-in-multiple-assets/stats.json b/test/stats/with-module-in-multiple-assets/stats.json index 66f86199..e597ccff 100644 --- a/test/stats/with-module-in-multiple-assets/stats.json +++ b/test/stats/with-module-in-multiple-assets/stats.json @@ -1,37 +1,80 @@ { "assets": [ - { "name": "main.js", "size": 300, "chunks": [0], "info": {} }, - { "name": "bridge.js", "size": 320, "chunks": [1], "info": {} } - ], - "chunks": [], - "entrypoints": { - "main": { "name": "main", "assets": [{ "name": "main.js" }] }, - "bridge": { "name": "bridge", "assets": [{ "name": "bridge.js" }] } - }, - "modules": [ { - "id": 1, - "identifier": "./src/shared.js", - "name": "./src/shared.js", - "size": 20, - "depth": 1, - "chunks": [0, 1] + "type": "asset", + "name": "long-message.js", + "size": 400, + "chunks": [379], + "info": { "javascriptModule": false } }, { - "id": 2, - "identifier": "./src/main.js", - "name": "./src/main.js", - "size": 30, - "depth": 1, - "chunks": [0] + "type": "asset", + "name": "short-message.js", + "size": 378, + "chunks": [453], + "info": { "javascriptModule": false } + } + ], + "chunks": [ + { + "id": 379, + "names": ["long-message"], + "files": ["long-message.js"], + "modules": [ + { + "id": 907, + "identifier": "./src/long-message-entry.js", + "name": "./src/long-message-entry.js", + "size": 72, + "depth": 0, + "chunks": [379], + "usedExports": [] + }, + { + "id": 906, + "identifier": "./src/messages.js", + "name": "./src/messages.js", + "size": 87, + "depth": 1, + "chunks": [379, 453], + "usedExports": ["longMessage"] + } + ] }, { - "id": 3, - "identifier": "./src/bridge.js", - "name": "./src/bridge.js", - "size": 30, - "depth": 1, - "chunks": [1] + "id": 453, + "names": ["short-message"], + "files": ["short-message.js"], + "modules": [ + { + "id": 906, + "identifier": "./src/messages.js", + "name": "./src/messages.js", + "size": 87, + "depth": 1, + "chunks": [379, 453], + "usedExports": ["shortMessage"] + }, + { + "id": 645, + "identifier": "./src/short-message-entry.js", + "name": "./src/short-message-entry.js", + "size": 74, + "depth": 0, + "chunks": [453], + "usedExports": [] + } + ] + } + ], + "entrypoints": { + "long-message": { + "name": "long-message", + "assets": [{ "name": "long-message.js" }] + }, + "short-message": { + "name": "short-message", + "assets": [{ "name": "short-message.js" }] } - ] + } } diff --git a/test/stats/with-module-in-multiple-assets/webpack.config.js b/test/stats/with-module-in-multiple-assets/webpack.config.js new file mode 100644 index 00000000..9541d2e3 --- /dev/null +++ b/test/stats/with-module-in-multiple-assets/webpack.config.js @@ -0,0 +1,21 @@ +const path = require("node:path"); + +// Build two bundles that import different exports from the same module. +module.exports = { + mode: "production", + context: __dirname, + entry: { + "long-message": "./src/long-message-entry.js", + "short-message": "./src/short-message-entry.js", + }, + output: { + path: __dirname, + filename: "[name].js", + }, + optimization: { + concatenateModules: false, + runtimeChunk: false, + splitChunks: false, + moduleIds: "deterministic", + }, +};