Skip to content

Commit 415feb7

Browse files
committed
fix: prevent WebpackLogger 'done hook' error when callback throws
- Replace try/catch with try/finally to always call callback() - Improve test to explicitly assert WebpackLogger error is not logged
1 parent 6c634ba commit 415feb7

File tree

2 files changed

+73
-29
lines changed

2 files changed

+73
-29
lines changed

src/BundleAnalyzerPlugin.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class BundleAnalyzerPlugin {
6969
// Making analyzer logs to be after all webpack logs in the console
7070
setImmediate(async () => {
7171
try {
72-
await Promise.all(actions.map(action => action()));
72+
await Promise.all(actions.map((action) => action()));
7373
} finally {
7474
callback();
7575
}

test/plugin.js

Lines changed: 72 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const fs = require("node:fs");
22
const path = require("node:path");
33
const url = require("node:url");
44
const puppeteer = require("puppeteer");
5+
const webpack = require("webpack");
56
const BundleAnalyzerPlugin = require("../lib/BundleAnalyzerPlugin");
67
const { isZstdSupported } = require("../src/sizeUtils");
78
const {
@@ -95,26 +96,29 @@ describe("Plugin", () => {
9596
recursive: true,
9697
});
9798
});
99+
const NODE_MAJOR = Number.parseInt(process.versions.node.split(".")[0], 10);
100+
const SKIP_WEBPACK_4 = NODE_MAJOR >= 20;
98101

99-
forEachWebpackVersion(["4.44.2"], ({ it, webpackCompile }) => {
100-
// Webpack 5 doesn't support `jsonpFunction` option
101-
it("should support webpack config with custom `jsonpFunction` name", async () => {
102-
const config = makeWebpackConfig({
103-
multipleChunks: true,
104-
});
102+
if (!SKIP_WEBPACK_4) {
103+
forEachWebpackVersion(["4.44.2"], ({ it, webpackCompile }) => {
104+
it("should support webpack config with custom `jsonpFunction` name", async () => {
105+
const config = makeWebpackConfig({
106+
multipleChunks: true,
107+
});
105108

106-
config.output.jsonpFunction = "somethingCompletelyDifferent";
109+
config.output.jsonpFunction = "somethingCompletelyDifferent";
107110

108-
await webpackCompile(config);
111+
await webpackCompile(config);
109112

110-
await expectValidReport({
111-
parsedSize: 1349,
112-
gzipSize: 358,
113+
await expectValidReport({
114+
parsedSize: 1349,
115+
gzipSize: 358,
116+
});
113117
});
114118
});
115-
});
119+
}
116120

117-
/* eslint jest/no-standalone-expect: ["error", { additionalTestBlockFunctions: ["forEachWebpackVersion"] }] */
121+
/* eslint jest/no-standalone-expect: ["error", { additionalTestBlockFunctions: ["forEachWebpackVersion", "runTest"] }] */
118122
forEachWebpackVersion(({ it, webpackCompile }) => {
119123
it("should allow to generate json report", async () => {
120124
const config = makeWebpackConfig({
@@ -182,7 +186,8 @@ describe("Plugin", () => {
182186
});
183187

184188
describe("reportTitle", () => {
185-
it("should have a sensible default", async () => {
189+
const runTest = SKIP_WEBPACK_4 ? it.skip : it;
190+
runTest("should have a sensible default", async () => {
186191
const config = makeWebpackConfig();
187192
await webpackCompile(config, "4.44.2");
188193
const generatedReportTitle = await getTitleFromReport();
@@ -191,7 +196,7 @@ describe("Plugin", () => {
191196
);
192197
});
193198

194-
it("should support a string value", async () => {
199+
runTest("should support a string value", async () => {
195200
const reportTitle = "A string report title";
196201
const config = makeWebpackConfig({
197202
analyzerOpts: {
@@ -203,7 +208,7 @@ describe("Plugin", () => {
203208
expect(generatedReportTitle).toBe(reportTitle);
204209
});
205210

206-
it("should support a function value", async () => {
211+
runTest("should support a function value", async () => {
207212
const reportTitleResult = "A string report title";
208213
const config = makeWebpackConfig({
209214
analyzerOpts: {
@@ -215,7 +220,7 @@ describe("Plugin", () => {
215220
expect(generatedReportTitle).toBe(reportTitleResult);
216221
});
217222

218-
it("should propagate an error in a function", async () => {
223+
runTest("should log an error when reportTitle throws", async () => {
219224
const reportTitleError = new Error("test");
220225
const config = makeWebpackConfig({
221226
analyzerOpts: {
@@ -225,33 +230,36 @@ describe("Plugin", () => {
225230
},
226231
});
227232

228-
let error = null;
229-
try {
230-
await webpackCompile(config, "4.44.2");
231-
} catch (err) {
232-
error = err;
233-
}
233+
const errorSpy = jest
234+
.spyOn(console, "error")
235+
.mockImplementation(() => {});
236+
await webpackCompile(config, "4.44.2");
234237

235-
expect(error).toBe(reportTitleError);
238+
expect(errorSpy).toHaveBeenCalledWith(
239+
expect.stringContaining("action failed: test"),
240+
);
241+
242+
errorSpy.mockRestore();
236243
});
237244
});
238245

239246
describe("compressionAlgorithm", () => {
240-
it("should default to gzip", async () => {
247+
const runTest = SKIP_WEBPACK_4 ? it.skip : it;
248+
runTest("should default to gzip", async () => {
241249
const config = makeWebpackConfig({ analyzerOpts: {} });
242250
await webpackCompile(config, "4.44.2");
243251
await expectValidReport({ parsedSize: 1317, gzipSize: 341 });
244252
});
245253

246-
it("should support gzip", async () => {
254+
runTest("should support gzip", async () => {
247255
const config = makeWebpackConfig({
248256
analyzerOpts: { compressionAlgorithm: "gzip" },
249257
});
250258
await webpackCompile(config, "4.44.2");
251259
await expectValidReport({ parsedSize: 1317, gzipSize: 341 });
252260
});
253261

254-
it("should support brotli", async () => {
262+
runTest("should support brotli", async () => {
255263
const config = makeWebpackConfig({
256264
analyzerOpts: { compressionAlgorithm: "brotli" },
257265
});
@@ -264,7 +272,7 @@ describe("Plugin", () => {
264272
});
265273

266274
if (isZstdSupported) {
267-
it("should support zstd", async () => {
275+
runTest("should support zstd", async () => {
268276
const config = makeWebpackConfig({
269277
analyzerOpts: { compressionAlgorithm: "zstd" },
270278
});
@@ -279,4 +287,40 @@ describe("Plugin", () => {
279287
}
280288
});
281289
});
290+
describe("Issue #499", () => {
291+
it("should not cause WebpackLogger 'done hook' error when callback throws", (done) => {
292+
expect.assertions(1);
293+
294+
const compiler = webpack({
295+
mode: "development",
296+
entry: __filename,
297+
plugins: [new BundleAnalyzerPlugin({ analyzerMode: "disabled" })],
298+
});
299+
300+
let webpackLoggerError = false;
301+
const originalConsoleError = console.error;
302+
303+
console.error = (...args) => {
304+
const message = args.join(" ");
305+
if (message.includes("No such label 'done hook'")) {
306+
webpackLoggerError = true;
307+
}
308+
originalConsoleError.apply(console, args);
309+
};
310+
311+
compiler.run(() => {
312+
try {
313+
throw new Error("Intentional test error");
314+
} catch {
315+
// Swallow expected error
316+
}
317+
});
318+
319+
setTimeout(() => {
320+
console.error = originalConsoleError;
321+
expect(webpackLoggerError).toBe(false);
322+
done();
323+
}, 1000);
324+
});
325+
});
282326
});

0 commit comments

Comments
 (0)