diff --git a/index.js b/index.js index 34df47f5e..70094f49a 100644 --- a/index.js +++ b/index.js @@ -15,6 +15,7 @@ const { } = require("./lib/html-tags"); const prettyError = require("./lib/errors.js"); const chunkSorter = require("./lib/chunksorter.js"); +const validateChunkNames = require("./lib/validators"); const { AsyncSeriesWaterfallHook } = require("tapable"); /** @typedef {import("./typings").HtmlTagObject} HtmlTagObject */ @@ -1195,6 +1196,30 @@ class HtmlWebpackPlugin { return result; } + /** + * @param {(string | null | undefined)[]} allChunkNames - all compilation chunk names + * @returns {Error[]} validation errors + */ + validateOptions(allChunkNames) { + const validationErrors = []; + if (this.options.chunks !== "all") { + const chunksErrors = validateChunkNames( + allChunkNames, + this.options.chunks, + "chunks", + ); + validationErrors.push(...chunksErrors); + } + const excludeChunksErrors = validateChunkNames( + allChunkNames, + this.options.excludeChunks, + "excludeChunks", + ); + validationErrors.push(...excludeChunksErrors); + + return validationErrors; + } + /** * Replace [contenthash] in filename * @@ -1261,6 +1286,16 @@ class HtmlWebpackPlugin { ) { // Get all entry point names for this html file const entryNames = Array.from(compilation.entrypoints.keys()); + + // Get all chunk names + const allChunkNames = Array.from(compilation.chunks).map((c) => c.name); + + const validationErrors = this.validateOptions(allChunkNames); + if (validationErrors.length) { + // TODO throw error in the next major release + compilation.warnings.push(...validationErrors); + } + const filteredEntryNames = this.filterEntryChunks( entryNames, this.options.chunks, diff --git a/lib/validators.js b/lib/validators.js new file mode 100644 index 000000000..51e9f014c --- /dev/null +++ b/lib/validators.js @@ -0,0 +1,23 @@ +/** + * Validates that all expected chunks are part of the compilation result + * + * @param {(string | null | undefined)[]} allChunkNames - all compilation chunk names + * @param {string[]} expectedChunkNames - expected chunk names + * @param {string} label + * @returns {Error[]} validation errors + */ +module.exports = function (allChunkNames, expectedChunkNames, label) { + if (!Array.isArray(expectedChunkNames)) { + return []; + } + + const missingChunks = expectedChunkNames.filter( + (chunkName) => !allChunkNames.includes(chunkName), + ); + + return missingChunks.map((chunk) => { + return new Error( + `HtmlWebpackPlugin: The chunk '${chunk}' provided in the 'options.${label}' option was not found in the compilation results`, + ); + }); +}; diff --git a/spec/basic.spec.js b/spec/basic.spec.js index b9952df28..54ef8dd21 100644 --- a/spec/basic.spec.js +++ b/spec/basic.spec.js @@ -47,7 +47,7 @@ function testHtmlPlugin( } const compilationWarnings = (stats.compilation.warnings || []).join("\n"); if (expectWarnings) { - expect(compilationWarnings).not.toBe(""); + expect(compilationWarnings).toMatch(expectWarnings); } else { expect(compilationWarnings).toBe(""); } @@ -3048,6 +3048,62 @@ describe("HtmlWebpackPlugin", () => { ); }); + it("adds a warning if a specified chunk in the chunks option is not found", (done) => { + testHtmlPlugin( + { + mode: "production", + entry: { + app: path.join(__dirname, "fixtures/index.js"), + }, + output: { + path: OUTPUT_DIR, + filename: "[name]_bundle.js", + }, + optimization: { + emitOnErrors: true, + }, + plugins: [ + new HtmlWebpackPlugin({ + chunks: ["app", "non_existent_chunk"], + }), + ], + }, + [], + null, + done, + false, + /HtmlWebpackPlugin: The chunk 'non_existent_chunk' provided in the 'options.chunks' option was not found in the compilation results/, + ); + }); + + it("adds a warning if a specified chunk in the excludeChunks option is not found", (done) => { + testHtmlPlugin( + { + mode: "production", + entry: { + app: path.join(__dirname, "fixtures/index.js"), + }, + output: { + path: OUTPUT_DIR, + filename: "[name]_bundle.js", + }, + optimization: { + emitOnErrors: true, + }, + plugins: [ + new HtmlWebpackPlugin({ + excludeChunks: ["non_existent_chunk"], + }), + ], + }, + [], + null, + done, + false, + /HtmlWebpackPlugin: The chunk 'non_existent_chunk' provided in the 'options.excludeChunks' option was not found in the compilation results/, + ); + }); + it("should add the webpack compilation object as a property of the templateParam object", (done) => { testHtmlPlugin( {