Skip to content

Commit cc63bde

Browse files
fix: keep the shebang, report a throwing banner helper, and key the cache on it
An end banner now keeps the shebang it was split from, a formatBanner or getBannerPosition that throws becomes an error of that asset rather than rejecting processAssets, and a declared helper's source joins the cache and chunk-hash identity.
1 parent 6419638 commit cc63bde

2 files changed

Lines changed: 113 additions & 16 deletions

File tree

‎src/index.js‎

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,20 +1188,32 @@ class MinimizerPlugin {
11881188
const positioned = helpers.find(
11891189
(fn) => typeof fn.getBannerPosition === "function",
11901190
);
1191-
const comment = formatter
1192-
? formatter(String(banner))
1193-
: `/*! ${banner} */`;
1194-
1195-
output.source =
1196-
positioned &&
1197-
/** @type {() => "start" | "end" | undefined} */
1198-
(positioned.getBannerPosition)() === "end"
1199-
? new ConcatSource(output.source, comment)
1200-
: new ConcatSource(
1201-
shebang ? `${shebang}\n` : "",
1202-
`${comment}\n`,
1203-
output.source,
1204-
);
1191+
1192+
// The helpers are the minimizer's code, so one that throws is
1193+
// reported against this asset like a failed minify.
1194+
try {
1195+
const comment = formatter
1196+
? formatter(String(banner))
1197+
: `/*! ${banner} */`;
1198+
const head = shebang ? `${shebang}\n` : "";
1199+
1200+
output.source =
1201+
positioned &&
1202+
/** @type {() => "start" | "end" | undefined} */
1203+
(positioned.getBannerPosition)() === "end"
1204+
? new ConcatSource(head, output.source, comment)
1205+
: new ConcatSource(head, `${comment}\n`, output.source);
1206+
} catch (error) {
1207+
compilation.errors.push(
1208+
MinimizerPlugin.buildError(
1209+
/** @type {Error | ErrorObject | string} */
1210+
(error),
1211+
name,
1212+
),
1213+
);
1214+
1215+
return;
1216+
}
12051217
}
12061218
}
12071219

@@ -2482,8 +2494,17 @@ class MinimizerPlugin {
24822494
? fn.getMinimizerVersion() || "0.0.0"
24832495
: "0.0.0";
24842496

2497+
// A banner helper changes the bytes written, so its source joins the
2498+
// identity; only where one is declared, so no other key moves.
2499+
const banner =
2500+
fn &&
2501+
(typeof fn.formatBanner === "function" ||
2502+
typeof fn.getBannerPosition === "function")
2503+
? `|${String(fn.formatBanner)}|${String(fn.getBannerPosition)}`
2504+
: "";
2505+
24852506
if (!ref) {
2486-
return version;
2507+
return `${version}${banner}`;
24872508
}
24882509

24892510
// Which module it is, read against the build rather than the disk: two
@@ -2496,7 +2517,7 @@ class MinimizerPlugin {
24962517
)
24972518
.replace(/\\/g, "/");
24982519

2499-
return `${version}|${where}|${ref.export || ""}`;
2520+
return `${version}|${where}|${ref.export || ""}${banner}`;
25002521
};
25012522
const data = getSerializeJavascript()({
25022523
minimizer: Array.isArray(this.options.minimizer.implementation)

‎test/minify-option.test.js‎

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,82 @@ describe("minify option", () => {
280280
expect(getWarnings(stats)).toMatchSnapshot("warnings");
281281
});
282282

283+
it("should keep the shebang first when the banner goes at the end", async () => {
284+
const compiler = getCompiler({
285+
entry: path.resolve(__dirname, "./fixtures/minify/es6.js"),
286+
});
287+
288+
const minify = async () => ({
289+
code: "#!/usr/bin/env node\nrun()",
290+
extractedComments: ["/*! @license MIT */"],
291+
});
292+
293+
minify.formatBanner = (banner) => `/* ${banner} */`;
294+
minify.getBannerPosition = () => "end";
295+
296+
new MinimizerPlugin({ parallel: false, minify }).apply(compiler);
297+
298+
const stats = await compile(compiler);
299+
300+
expect(readAsset("main.js", compiler, stats)).toBe(
301+
"#!/usr/bin/env node\nrun()/* For license information please see main.js.LICENSE.txt */",
302+
);
303+
});
304+
305+
it("should report a banner helper that throws as an error of the asset", async () => {
306+
const compiler = getCompiler({
307+
entry: path.resolve(__dirname, "./fixtures/minify/es6.js"),
308+
bail: false,
309+
});
310+
311+
const minify = async () => ({
312+
code: "run()",
313+
extractedComments: ["/*! @license MIT */"],
314+
});
315+
316+
minify.formatBanner = () => {
317+
throw new Error("no banner");
318+
};
319+
320+
new MinimizerPlugin({ parallel: false, minify }).apply(compiler);
321+
322+
const stats = await compile(compiler);
323+
324+
expect(stats.compilation.errors).toHaveLength(1);
325+
expect(stats.compilation.errors[0].message).toMatch(
326+
/main\.js from minimizer-webpack-plugin[\s\S]*no banner/,
327+
);
328+
});
329+
330+
it("should name a chunk apart when only its minimizer's banner helper differs", async () => {
331+
/**
332+
* @param {(banner: string) => string} formatBanner the helper
333+
* @returns {Promise<string[]>} the emitted script names
334+
*/
335+
const build = async (formatBanner) => {
336+
const compiler = getCompiler({
337+
entry: path.resolve(__dirname, "./fixtures/minify/es6.js"),
338+
output: { filename: "[name].[chunkhash].js" },
339+
});
340+
341+
const minify = async () => ({ code: "run()" });
342+
343+
minify.formatBanner = formatBanner;
344+
345+
new MinimizerPlugin({ parallel: false, minify }).apply(compiler);
346+
347+
const stats = await compile(compiler);
348+
349+
return Object.keys(stats.compilation.assets).filter((name) =>
350+
name.endsWith(".js"),
351+
);
352+
};
353+
354+
expect(await build((banner) => `/* ${banner} */`)).not.toEqual(
355+
await build((banner) => `<!-- ${banner} -->`),
356+
);
357+
});
358+
283359
it("should work with source maps", async () => {
284360
const compiler = getCompiler({
285361
devtool: "source-map",

0 commit comments

Comments
 (0)