From 6e0e4510d7795f0df3ab5a2a883f69f030c3148c Mon Sep 17 00:00:00 2001 From: jeromeh Date: Sun, 7 Feb 2021 12:16:18 +0100 Subject: [PATCH] feat: support of injectHot and injectClient on specific chunks --- lib/options.json | 18 ++- lib/utils/DevServerPlugin.js | 129 ++++++++++++++------- test/__snapshots__/Validation.test.js.snap | 4 +- test/options.test.js | 4 +- 4 files changed, 111 insertions(+), 44 deletions(-) diff --git a/lib/options.json b/lib/options.json index 5edd121159..781a166fa4 100644 --- a/lib/options.json +++ b/lib/options.json @@ -228,6 +228,13 @@ { "type": "boolean" }, + { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, { "instanceof": "Function" } @@ -238,6 +245,13 @@ { "type": "boolean" }, + { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, { "instanceof": "Function" } @@ -395,8 +409,8 @@ "hot": "should be {Boolean|String} (https://webpack.js.org/configuration/dev-server/#devserverhot)", "http2": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devserverhttp2)", "https": "should be {Object|Boolean} (https://webpack.js.org/configuration/dev-server/#devserverhttps)", - "injectClient": "should be {Boolean|Function} (https://webpack.js.org/configuration/dev-server/#devserverinjectclient)", - "injectHot": "should be {Boolean|Function} (https://webpack.js.org/configuration/dev-server/#devserverinjecthot)", + "injectClient": "should be {Boolean|String[]|Function} (https://webpack.js.org/configuration/dev-server/#devserverinjectclient)", + "injectHot": "should be {Boolean|String[]|Function} (https://webpack.js.org/configuration/dev-server/#devserverinjecthot)", "liveReload": "should be {Boolean} (https://webpack.js.org/configuration/dev-server/#devserverlivereload)", "onAfterSetupMiddleware": "should be {Function} (https://webpack.js.org/configuration/dev-server/#devserverafter)", "onBeforeSetupMiddleware": "should be {Function} (https://webpack.js.org/configuration/dev-server/#devserverbefore)", diff --git a/lib/utils/DevServerPlugin.js b/lib/utils/DevServerPlugin.js index 4e7c554f64..9e9339d46e 100644 --- a/lib/utils/DevServerPlugin.js +++ b/lib/utils/DevServerPlugin.js @@ -16,10 +16,17 @@ class DevServerPlugin { } /** - * An Entry, it can be of type string or string[] or Object + * A Entry, it can be of type string or string[] or Object * @typedef {(string[] | string | Object)} Entry */ + /** + * Additional entry to add to specific chunk + * @typedef {Object} AdditionalChunkEntry + * @property {Entry} entry + * @property {string[]} [chunks] + */ + /** * Apply the plugin * @param {Object} compiler the compiler instance @@ -69,7 +76,7 @@ class DevServerPlugin { /** * prependEntry Method for webpack 4 * @param {Entry} originalEntry - * @param {Entry} additionalEntries + * @param {AdditionalChunkEntry[]} additionalEntries * @returns {Entry} */ const prependEntry = (originalEntry, additionalEntries) => { @@ -86,8 +93,13 @@ class DevServerPlugin { Object.keys(originalEntry).forEach((key) => { // entry[key] should be a string here + const chunkAdditionalEntries = additionalEntries.filter( + (additionalEntry) => + !additionalEntry.chunks || additionalEntry.chunks.includes(key) + ); + const entryDescription = originalEntry[key]; - clone[key] = prependEntry(entryDescription, additionalEntries); + clone[key] = prependEntry(entryDescription, chunkAdditionalEntries); }); return clone; @@ -96,13 +108,15 @@ class DevServerPlugin { // in this case, entry is a string or an array. // make sure that we do not add duplicates. /** @type {Entry} */ - const entriesClone = additionalEntries.slice(0); + const newEntries = additionalEntries.map( + (additionalEntry) => additionalEntry.entry + ); [].concat(originalEntry).forEach((newEntry) => { - if (!entriesClone.includes(newEntry)) { - entriesClone.push(newEntry); + if (!newEntries.includes(newEntry)) { + newEntries.push(newEntry); } }); - return entriesClone; + return newEntries; }; /** @@ -115,23 +129,27 @@ class DevServerPlugin { /** * - * @param {Boolean | checkInjectOptionsParam} option - inject(Hot|Client) it is Boolean | fn => Boolean + * @param {Boolean | string[] | checkInjectOptionsParam} option - inject(Hot|Client) it is Boolean | fn => Boolean * @param {Object} _config * @param {Boolean} defaultValue - * @return {Boolean} + * @return {Boolean | string[]} */ - // eslint-disable-next-line no-shadow + // eslint-disable-next-line no-shadow const checkInject = (option, _config, defaultValue) => { - if (typeof option === 'boolean') { - return option; - } + if (typeof option === 'boolean') { + return option; + } - if (typeof option === 'function') { - return option(_config); - } + if (Array.isArray(option)) { + return option; + } - return defaultValue; - }; + if (typeof option === 'function') { + return option(_config); + } + + return defaultValue; + }; const compilerOptions = compiler.options; @@ -141,35 +159,68 @@ class DevServerPlugin { const isWebTarget = compilerOptions.externalsPresets ? compilerOptions.externalsPresets.web : [ - 'web', - 'webworker', - 'electron-renderer', - 'node-webkit', - // eslint-disable-next-line no-undefined - undefined, - null, - ].includes(compilerOptions.target); - - /** @type {Entry} */ - const additionalEntries = checkInject( + 'web', + 'webworker', + 'electron-renderer', + 'node-webkit', + // eslint-disable-next-line no-undefined + undefined, + null, + ].includes(compilerOptions.target); + + /** @type {AdditionalChunkEntry[]} */ + const additionalEntries = []; + + const checkInjectClientResult = checkInject( options.injectClient, compilerOptions, isWebTarget - ) - ? [clientEntry] - : []; + ); + if (checkInjectClientResult) { + additionalEntries.push({ + entry: clientEntry, + chunks: Array.isArray(checkInjectClientResult) + ? checkInjectClientResult + : null, + }); + } - if (hotEntry && checkInject(options.injectHot, compilerOptions, true)) { - additionalEntries.push(hotEntry); + if (hotEntry) { + const checkInjectHotResult = checkInject( + options.injectHot, + compilerOptions, + true + ); + if (checkInjectHotResult) { + additionalEntries.push({ + entry: hotEntry, + chunks: Array.isArray(checkInjectHotResult) + ? checkInjectHotResult + : null, + }); + } } // use a hook to add entries if available if (EntryPlugin) { - for (const additionalEntry of additionalEntries) { - new EntryPlugin(compiler.context, additionalEntry, { - // eslint-disable-next-line no-undefined - name: undefined, - }).apply(compiler); + for (const additionalChunkEntry of additionalEntries) { + // add entry to existing chunks + if ( + additionalChunkEntry.chunks && + Array.isArray(additionalChunkEntry.chunks) + ) { + additionalChunkEntry.chunks.forEach((chunkName) => { + new EntryPlugin(compiler.context, additionalChunkEntry.entry, { + // eslint-disable-next-line no-undefined + name: chunkName, + }).apply(compiler); + }); + } else { + new EntryPlugin(compiler.context, additionalChunkEntry.entry, { + // eslint-disable-next-line no-undefined + name: undefined, + }).apply(compiler); + } } } else { compilerOptions.entry = prependEntry( diff --git a/test/__snapshots__/Validation.test.js.snap b/test/__snapshots__/Validation.test.js.snap index f88f0c5255..e353fee5c7 100644 --- a/test/__snapshots__/Validation.test.js.snap +++ b/test/__snapshots__/Validation.test.js.snap @@ -12,9 +12,11 @@ exports[`Validation validation should fail validation for invalid \`hot\` config exports[`Validation validation should fail validation for invalid \`injectHot\` configuration 1`] = ` "Invalid configuration object. Object has been initialized using a configuration object that does not match the API schema. - configuration.injectHot should be one of these: - boolean | function + boolean | [string, ...] (should not have fewer than 1 item) | function Details: * configuration.injectHot should be a boolean. + * configuration.injectHot should be an array: + [string, ...] (should not have fewer than 1 item) * configuration.injectHot should be an instance of function." `; diff --git a/test/options.test.js b/test/options.test.js index 95dccc82e0..4b041c03bc 100644 --- a/test/options.test.js +++ b/test/options.test.js @@ -359,11 +359,11 @@ describe('options', () => { ], }, injectClient: { - success: [true, () => {}], + success: [true, ['a'], () => {}], failure: [''], }, injectHot: { - success: [true, () => {}], + success: [true, ['a'], () => {}], failure: [''], }, onListening: {