From 268ab536fca0d85047ca6bc170ad8df6fc837ff5 Mon Sep 17 00:00:00 2001 From: Utkarsh Tiwari Date: Mon, 27 Jul 2026 02:28:36 +0530 Subject: [PATCH 1/2] fix: preserve compiler paths for shared plugin instances --- .changeset/calm-compilers-report.md | 5 ++ src/BundleAnalyzerPlugin.js | 73 ++++++++++++++++++++--------- src/viewer.js | 11 +++-- test/plugin.js | 25 ++++++++++ 4 files changed, 89 insertions(+), 25 deletions(-) create mode 100644 .changeset/calm-compilers-report.md diff --git a/.changeset/calm-compilers-report.md b/.changeset/calm-compilers-report.md new file mode 100644 index 00000000..4fad95d3 --- /dev/null +++ b/.changeset/calm-compilers-report.md @@ -0,0 +1,5 @@ +--- +"webpack-bundle-analyzer": patch +--- + +Resolve bundle assets and report files against the correct compiler output path when a plugin instance is shared by multiple compilers. diff --git a/src/BundleAnalyzerPlugin.js b/src/BundleAnalyzerPlugin.js index 3dada6fb..22c9e8bd 100644 --- a/src/BundleAnalyzerPlugin.js +++ b/src/BundleAnalyzerPlugin.js @@ -127,7 +127,10 @@ class BundleAnalyzerPlugin { if (this.opts.generateStatsFile) { actions.push(() => - this.generateStatsFile(stats.toJson(this.opts.statsOptions)), + this.generateStatsFile( + stats.toJson(this.opts.statsOptions), + compiler, + ), ); } @@ -138,15 +141,21 @@ class BundleAnalyzerPlugin { if (this.opts.analyzerMode === "server") { actions.push(() => - this.startAnalyzerServer(stats.toJson(analyzerStatsOptions)), + this.startAnalyzerServer( + stats.toJson(analyzerStatsOptions), + compiler, + ), ); } else if (this.opts.analyzerMode === "static") { actions.push(() => - this.generateStaticReport(stats.toJson(analyzerStatsOptions)), + this.generateStaticReport( + stats.toJson(analyzerStatsOptions), + compiler, + ), ); } else if (this.opts.analyzerMode === "json") { actions.push(() => - this.generateJSONReport(stats.toJson(analyzerStatsOptions)), + this.generateJSONReport(stats.toJson(analyzerStatsOptions), compiler), ); } @@ -175,12 +184,15 @@ class BundleAnalyzerPlugin { /** * @param {StatsCompilation} stats stats + * @param {Compiler=} compiler compiler * @returns {Promise} */ - async generateStatsFile(stats) { + async generateStatsFile( + stats, + compiler = /** @type {Compiler} */ (this.compiler), + ) { const statsFilepath = path.resolve( - /** @type {Compiler} */ - (this.compiler).outputPath, + compiler.outputPath, this.opts.statsFilename, ); await fs.promises.mkdir(path.dirname(statsFilepath), { recursive: true }); @@ -200,11 +212,18 @@ class BundleAnalyzerPlugin { /** * @param {StatsCompilation} stats stats + * @param {Compiler=} compiler compiler * @returns {Promise} */ - async startAnalyzerServer(stats) { + async startAnalyzerServer( + stats, + compiler = /** @type {Compiler} */ (this.compiler), + ) { if (this.server) { - (await this.server).updateChartData(stats); + (await this.server).updateChartData( + stats, + this.getBundleDirFromCompiler(compiler), + ); } else { this.server = viewer.startServer(stats, { openBrowser: this.opts.openAnalyzer, @@ -212,7 +231,7 @@ class BundleAnalyzerPlugin { port: this.opts.analyzerPort, reportTitle: this.opts.reportTitle, compressionAlgorithm: this.opts.compressionAlgorithm, - bundleDir: this.getBundleDirFromCompiler(), + bundleDir: this.getBundleDirFromCompiler(compiler), logger: this.logger, defaultSizes: this.opts.defaultSizes, excludeAssets: this.opts.excludeAssets, @@ -223,17 +242,20 @@ class BundleAnalyzerPlugin { /** * @param {StatsCompilation} stats stats + * @param {Compiler=} compiler compiler * @returns {Promise} */ - async generateJSONReport(stats) { + async generateJSONReport( + stats, + compiler = /** @type {Compiler} */ (this.compiler), + ) { await viewer.generateJSONReport(stats, { reportFilename: path.resolve( - /** @type {Compiler} */ - (this.compiler).outputPath, + compiler.outputPath, this.opts.reportFilename || "report.json", ), compressionAlgorithm: this.opts.compressionAlgorithm, - bundleDir: this.getBundleDirFromCompiler(), + bundleDir: this.getBundleDirFromCompiler(compiler), logger: this.logger, excludeAssets: this.opts.excludeAssets, }); @@ -241,32 +263,39 @@ class BundleAnalyzerPlugin { /** * @param {StatsCompilation} stats stats + * @param {Compiler=} compiler compiler * @returns {Promise} */ - async generateStaticReport(stats) { + async generateStaticReport( + stats, + compiler = /** @type {Compiler} */ (this.compiler), + ) { await viewer.generateReport(stats, { openBrowser: this.opts.openAnalyzer, reportFilename: path.resolve( - /** @type {Compiler} */ - (this.compiler).outputPath, + compiler.outputPath, this.opts.reportFilename || "report.html", ), reportTitle: this.opts.reportTitle, compressionAlgorithm: this.opts.compressionAlgorithm, - bundleDir: this.getBundleDirFromCompiler(), + bundleDir: this.getBundleDirFromCompiler(compiler), logger: this.logger, defaultSizes: this.opts.defaultSizes, excludeAssets: this.opts.excludeAssets, }); } - getBundleDirFromCompiler() { + /** + * @param {Compiler=} compiler compiler + * @returns {string | null} bundle directory + */ + getBundleDirFromCompiler(compiler = /** @type {Compiler} */ (this.compiler)) { const outputFileSystemConstructor = /** @type {OutputFileSystem} */ - (/** @type {Compiler} */ (this.compiler).outputFileSystem).constructor; + (compiler.outputFileSystem).constructor; if (typeof outputFileSystemConstructor === "undefined") { - return /** @type {Compiler} */ (this.compiler).outputPath; + return compiler.outputPath; } switch (outputFileSystemConstructor.name) { case "MemoryFileSystem": @@ -276,7 +305,7 @@ class BundleAnalyzerPlugin { case "AsyncMFS": return null; default: - return /** @type {Compiler} */ (this.compiler).outputPath; + return compiler.outputPath; } } } diff --git a/src/viewer.js b/src/viewer.js index 2b372516..4484b145 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -115,7 +115,7 @@ function getChartData(analyzerOpts, bundleStats, bundleDir) { * @property {AnalyzerUrl} analyzerUrl analyzer url */ -/** @typedef {{ ws: WebSocketServer, http: Server, updateChartData: (bundleStats: StatsCompilation) => void }} ViewerServerObj */ +/** @typedef {{ ws: WebSocketServer, http: Server, updateChartData: (bundleStats: StatsCompilation, bundleDir?: string | null) => void }} ViewerServerObj */ /** * @param {StatsCompilation} bundleStats bundle stats @@ -207,9 +207,14 @@ async function startServer(bundleStats, opts) { /** * @param {StatsCompilation} bundleStats bundle stats + * @param {string | null=} updatedBundleDir bundle directory */ - function updateChartData(bundleStats) { - const newChartData = getChartData(analyzerOpts, bundleStats, bundleDir); + function updateChartData(bundleStats, updatedBundleDir = bundleDir) { + const newChartData = getChartData( + analyzerOpts, + bundleStats, + updatedBundleDir, + ); if (!newChartData) return; diff --git a/test/plugin.js b/test/plugin.js index 39b56baa..1a7547ef 100644 --- a/test/plugin.js +++ b/test/plugin.js @@ -129,6 +129,31 @@ describe("Plugin", () => { expect(chartData).toBeDefined(); }); + it("should use each compiler output path when a plugin instance is reused", async () => { + const plugin = new BundleAnalyzerPlugin({ + analyzerMode: "json", + logLevel: "error", + }); + const firstConfig = makeWebpackConfig(); + const secondConfig = makeWebpackConfig(); + + firstConfig.output.path = path.resolve(__dirname, "./output/first"); + firstConfig.plugins = [plugin]; + secondConfig.output.path = path.resolve(__dirname, "./output/second"); + secondConfig.plugins = [plugin]; + + await webpackCompile([firstConfig, secondConfig]); + + for (const output of ["first", "second"]) { + const reportPath = path.resolve( + __dirname, + `./output/${output}/report.json`, + ); + expect(fs.existsSync(reportPath)).toBe(true); + expect(JSON.parse(fs.readFileSync(reportPath, "utf8"))).not.toEqual([]); + } + }); + it("should support webpack config with `multi` module", async () => { const config = makeWebpackConfig(); From 8fabe1a81477034f12d57d6b23a984a4c4ba7d4c Mon Sep 17 00:00:00 2001 From: Utkarsh Tiwari Date: Tue, 28 Jul 2026 15:02:04 +0530 Subject: [PATCH 2/2] refactor: require explicit compiler argument in report methods --- src/BundleAnalyzerPlugin.js | 32 ++++++++++---------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/src/BundleAnalyzerPlugin.js b/src/BundleAnalyzerPlugin.js index 22c9e8bd..00229a11 100644 --- a/src/BundleAnalyzerPlugin.js +++ b/src/BundleAnalyzerPlugin.js @@ -184,13 +184,10 @@ class BundleAnalyzerPlugin { /** * @param {StatsCompilation} stats stats - * @param {Compiler=} compiler compiler + * @param {Compiler} compiler compiler * @returns {Promise} */ - async generateStatsFile( - stats, - compiler = /** @type {Compiler} */ (this.compiler), - ) { + async generateStatsFile(stats, compiler) { const statsFilepath = path.resolve( compiler.outputPath, this.opts.statsFilename, @@ -212,13 +209,10 @@ class BundleAnalyzerPlugin { /** * @param {StatsCompilation} stats stats - * @param {Compiler=} compiler compiler + * @param {Compiler} compiler compiler * @returns {Promise} */ - async startAnalyzerServer( - stats, - compiler = /** @type {Compiler} */ (this.compiler), - ) { + async startAnalyzerServer(stats, compiler) { if (this.server) { (await this.server).updateChartData( stats, @@ -242,13 +236,10 @@ class BundleAnalyzerPlugin { /** * @param {StatsCompilation} stats stats - * @param {Compiler=} compiler compiler + * @param {Compiler} compiler compiler * @returns {Promise} */ - async generateJSONReport( - stats, - compiler = /** @type {Compiler} */ (this.compiler), - ) { + async generateJSONReport(stats, compiler) { await viewer.generateJSONReport(stats, { reportFilename: path.resolve( compiler.outputPath, @@ -263,13 +254,10 @@ class BundleAnalyzerPlugin { /** * @param {StatsCompilation} stats stats - * @param {Compiler=} compiler compiler + * @param {Compiler} compiler compiler * @returns {Promise} */ - async generateStaticReport( - stats, - compiler = /** @type {Compiler} */ (this.compiler), - ) { + async generateStaticReport(stats, compiler) { await viewer.generateReport(stats, { openBrowser: this.opts.openAnalyzer, reportFilename: path.resolve( @@ -286,10 +274,10 @@ class BundleAnalyzerPlugin { } /** - * @param {Compiler=} compiler compiler + * @param {Compiler} compiler compiler * @returns {string | null} bundle directory */ - getBundleDirFromCompiler(compiler = /** @type {Compiler} */ (this.compiler)) { + getBundleDirFromCompiler(compiler) { const outputFileSystemConstructor = /** @type {OutputFileSystem} */ (compiler.outputFileSystem).constructor;