diff --git a/src/helpers/injectRefreshEntry.js b/src/helpers/injectRefreshEntry.js index 636a4792..c0444097 100644 --- a/src/helpers/injectRefreshEntry.js +++ b/src/helpers/injectRefreshEntry.js @@ -1,3 +1,5 @@ +const querystring = require('querystring'); + /** @typedef {string | string[] | import('webpack').Entry} StaticEntry */ /** @typedef {StaticEntry | import('webpack').EntryFunc} WebpackEntry */ @@ -7,27 +9,38 @@ * @param {import('../types').ReactRefreshPluginOptions} [options] Configuration options for this plugin. * @returns {WebpackEntry} An injected entry object. */ -const injectRefreshEntry = (originalEntry, options) => { - const sockHost = options.sockHost ? `&sockHost=${options.sockHost}` : ''; - const sockPort = options.sockPort ? `&sockPort=${options.sockPort}` : ''; - const sockPath = options.sockPath ? `&sockPath=${options.sockPath}` : ''; - const queryParams = `?options${sockHost}${sockPort}${sockPath}`; - const entryInjects = [ - // Legacy WDS SockJS integration - options.useLegacyWDSSockets && require.resolve('../runtime/LegacyWebpackDevServerSocket'), +function injectRefreshEntry(originalEntry, options) { + let resourceQuery = {}; + if (options.overlay) { + options.overlay.sockHost && (resourceQuery.sockHost = options.overlay.sockHost); + options.overlay.sockPath && (resourceQuery.sockPath = options.overlay.sockPath); + options.overlay.sockPort && (resourceQuery.sockPort = options.overlay.sockPort); + } + + // We don't need to URI encode the resourceQuery as it will be parsed by Webpack + const queryString = querystring.stringify(resourceQuery, null, null, { + encodeURIComponent: (string) => string, + }); + + const prependEntries = [ // React-refresh runtime require.resolve('../runtime/ReactRefreshEntry'), + ]; + + const appendEntries = [ + // Legacy WDS SockJS integration + options.useLegacyWDSSockets && require.resolve('../runtime/LegacyWDSSocketEntry'), // Error overlay runtime - options.overlay && options.overlay.entry + queryParams, + options.overlay && options.overlay.entry + (queryString && `?${queryString}`), ].filter(Boolean); // Single string entry point if (typeof originalEntry === 'string') { - return [...entryInjects, originalEntry]; + return [...prependEntries, originalEntry, ...appendEntries]; } // Single array entry point if (Array.isArray(originalEntry)) { - return [...entryInjects, ...originalEntry]; + return [...prependEntries, ...originalEntry, ...appendEntries]; } // Multiple entry points if (typeof originalEntry === 'object') { @@ -48,6 +61,6 @@ const injectRefreshEntry = (originalEntry, options) => { } throw new Error('Failed to parse the Webpack `entry` object!'); -}; +} module.exports = injectRefreshEntry; diff --git a/src/helpers/validateOptions.js b/src/helpers/validateOptions.js index 8ec6b6df..1b219270 100644 --- a/src/helpers/validateOptions.js +++ b/src/helpers/validateOptions.js @@ -12,6 +12,18 @@ function isBooleanOrUndefined(name, value) { } } +function isNumberOrUndefined(name, value) { + const valueType = typeof value; + if (valueType !== 'undefined' && valueType !== 'number') { + throw new Error( + [ + `The "${name}" option, if defined, must be a number.`, + `Instead received: "${valueType}".`, + ].join('\n') + ); + } +} + function isStringOrUndefined(name, value) { const valueType = typeof value; if (valueType !== 'undefined' && valueType !== 'string') { @@ -69,9 +81,12 @@ function validateOptions(options) { typeof defaultedOptions.overlay !== 'undefined' && typeof defaultedOptions.overlay !== 'boolean' ) { - const { entry, module: overlayModule } = defaultedOptions.overlay; + const { entry, module: overlayModule, sockHost, sockPath, sockPort } = defaultedOptions.overlay; isStringOrUndefined('overlay.entry', entry); isStringOrUndefined('overlay.module', overlayModule); + isStringOrUndefined('overlay.sockHost', sockHost); + isStringOrUndefined('overlay.sockPath', sockPath); + isNumberOrUndefined('overlay.sockPort', sockPort); defaultedOptions.overlay = { ...defaultedOptions.overlay, diff --git a/src/runtime/ErrorOverlayEntry.js b/src/runtime/ErrorOverlayEntry.js index 5f516ad6..72330286 100644 --- a/src/runtime/ErrorOverlayEntry.js +++ b/src/runtime/ErrorOverlayEntry.js @@ -73,7 +73,10 @@ function compileMessageHandler(message) { let overrides = {}; if (__resourceQuery) { - overrides = require('querystring').parse(__resourceQuery.slice(1)); + const searchParams = new URLSearchParams(__resourceQuery.slice(1)); + searchParams.forEach(function (value, key) { + overrides[key] = value; + }); } // Registers handlers for compile errors diff --git a/src/types.js b/src/types.js index 2a719f51..809f343d 100644 --- a/src/types.js +++ b/src/types.js @@ -1,7 +1,10 @@ /** * @typedef {Object} ErrorOverlayOptions * @property {string} [entry] Path to a JS file that sets up the error overlay integration. - * @property {string} module The error overlay module to use. + * @property {string} [module] The error overlay module to use. + * @property {string} [sockHost] The socket host to use. + * @property {string} [sockPath] The socket path to use. + * @property {number} [sockPort] The socket port to use. */ /** @@ -9,9 +12,6 @@ * @property {boolean} [disableRefreshCheck] Disables detection of react-refresh's Babel plugin. (Deprecated since v0.3.0) * @property {boolean} [forceEnable] Enables the plugin forcefully. * @property {boolean | ErrorOverlayOptions} [overlay] Modifies how the error overlay integration works in the plugin. - * @property {string} [sockHost] The socket host to use for the error overlay integration. - * @property {string} [sockPath] The socket path to use for the error overlay integration. - * @property {number} [sockPort] The socket port to use for the error overlay integration. * @property {boolean} [useLegacyWDSSockets] Uses a custom SocketJS implementation for older versions of webpack-dev-server. */