From 002a514898b4ab9edf07826ea260b22b7c3b6e93 Mon Sep 17 00:00:00 2001 From: Michael Mok Date: Sun, 1 Mar 2020 11:32:23 +0800 Subject: [PATCH 1/7] refactor: extract types into common file --- src/types.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/types.js diff --git a/src/types.js b/src/types.js new file mode 100644 index 00000000..8d977bd7 --- /dev/null +++ b/src/types.js @@ -0,0 +1,13 @@ +/** + * @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. + */ + +/** + * @typedef {Object} ReactRefreshPluginOptions + * @property {boolean} [disableRefreshCheck] Disables detection of react-refresh's Babel plugin. + * @property {boolean} [forceEnable] Enables the plugin forcefully. + * @property {boolean | ErrorOverlayOptions} [overlay] Modifies how the error overlay integration works in the plugin. + * @property {boolean} [useLegacyWDSSockets] Uses a custom SocketJS implementation for older versions of webpack-dev-server. + */ From c27403f82105ad067f819ef7cbd839313ad46b4a Mon Sep 17 00:00:00 2001 From: Michael Mok Date: Sun, 1 Mar 2020 11:33:15 +0800 Subject: [PATCH 2/7] refactor: update type references within the project --- src/helpers/injectRefreshEntry.js | 3 ++- src/index.js | 20 +++----------------- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/helpers/injectRefreshEntry.js b/src/helpers/injectRefreshEntry.js index d4a42320..70eb7f75 100644 --- a/src/helpers/injectRefreshEntry.js +++ b/src/helpers/injectRefreshEntry.js @@ -4,11 +4,12 @@ /** * Injects an entry to the bundle for react-refresh. * @param {WebpackEntry} [originalEntry] A Webpack entry object. - * @param {ReactRefreshPluginOptions} [options] Configuration options for this plugin + * @param {import('../types').ReactRefreshPluginOptions} [options] Configuration options for this plugin. * @returns {WebpackEntry} An injected entry object. */ const injectRefreshEntry = (originalEntry, options) => { const entryInjects = [ + // Legacy WDS SockJS integration options.useLegacyWDSSockets && require.resolve('../runtime/LegacyWebpackDevServerSocket'), // React-refresh runtime require.resolve('../runtime/ReactRefreshEntry'), diff --git a/src/index.js b/src/index.js index afa0f48f..a3d54888 100644 --- a/src/index.js +++ b/src/index.js @@ -1,25 +1,11 @@ const path = require('path'); const webpack = require('webpack'); -const { createRefreshTemplate, injectRefreshEntry } = require('./helpers'); +const { createRefreshTemplate, injectRefreshEntry, validateOptions } = require('./helpers'); const { refreshUtils } = require('./runtime/globals'); -/** - * @typedef {Object} ReactRefreshPluginOptions - * @property {boolean} [disableRefreshCheck] Disables detection of react-refresh's Babel plugin. - * @property {boolean} [forceEnable] Enables the plugin forcefully. - * @property {boolean} [useLegacyWDSSockets] Uses a custom SocketJS implementation for older versions of webpack-dev-server - */ - -/** @type {ReactRefreshPluginOptions} */ -const defaultOptions = { - disableRefreshCheck: false, - forceEnable: false, - useLegacyWDSSockets: false, -}; - class ReactRefreshPlugin { /** - * @param {ReactRefreshPluginOptions} [options] Options for react-refresh-plugin. + * @param {import('./types').ReactRefreshPluginOptions} [options] Options for react-refresh-plugin. * @returns {void} */ constructor(options) { @@ -27,7 +13,7 @@ class ReactRefreshPlugin { } /** - * Applies the plugin + * Applies the plugin. * @param {import('webpack').Compiler} compiler A webpack compiler object. * @returns {void} */ From a0de3b5d68b78b321b54d651d9922e9cced6ea08 Mon Sep 17 00:00:00 2001 From: Michael Mok Date: Sun, 1 Mar 2020 11:34:06 +0800 Subject: [PATCH 3/7] feat: add options validator --- src/helpers/index.js | 2 ++ src/helpers/validateOptions.js | 41 ++++++++++++++++++++++++++++++++++ src/index.js | 2 +- 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 src/helpers/validateOptions.js diff --git a/src/helpers/index.js b/src/helpers/index.js index 2660ee08..f2b7b665 100644 --- a/src/helpers/index.js +++ b/src/helpers/index.js @@ -1,7 +1,9 @@ const createRefreshTemplate = require('./createRefreshTemplate'); const injectRefreshEntry = require('./injectRefreshEntry'); +const validateOptions = require('./validateOptions'); module.exports = { createRefreshTemplate, injectRefreshEntry, + validateOptions, }; diff --git a/src/helpers/validateOptions.js b/src/helpers/validateOptions.js new file mode 100644 index 00000000..ad471b78 --- /dev/null +++ b/src/helpers/validateOptions.js @@ -0,0 +1,41 @@ +/** @type {import('../types').ReactRefreshPluginOptions} */ +const defaultOptions = { + disableRefreshCheck: false, + forceEnable: false, + useLegacyWDSSockets: false, +}; + +/** @type {import('../types').ErrorOverlayOptions} */ +const defaultOverlayOptions = { + entry: require.resolve('../runtime/ErrorOverlayEntry'), + overlay: require.resolve('../overlay'), +}; + +/** + * Validates the options for the plugin. + * @param {import('../types').ReactRefreshPluginOptions} options Non-validated plugin options object. + * @returns {import('../types').ReactRefreshPluginOptions} Validated plugin options. + */ +module.exports = function validateOptions(options) { + const validatedOptions = Object.assign(defaultOptions, options); + + if (typeof validatedOptions.overlay !== 'boolean') { + if (typeof validatedOptions.overlay.module !== 'string') { + throw new Error( + `To use the "overlay" option, a string must be provided in the "module" property. Instead, the provided value has type: "${typeof options + .overlay.module}".` + ); + } + + validatedOptions.overlay = { + entry: options.overlay.entry || defaultOverlayOptions.entry, + module: options.overlay.module, + }; + } else { + validatedOptions.overlay = + (typeof validatedOptions.overlay === 'undefined' || validatedOptions.overlay) && + defaultOverlayOptions; + } + + return validatedOptions; +}; diff --git a/src/index.js b/src/index.js index a3d54888..776beb0c 100644 --- a/src/index.js +++ b/src/index.js @@ -9,7 +9,7 @@ class ReactRefreshPlugin { * @returns {void} */ constructor(options) { - this.options = Object.assign(defaultOptions, options); + this.options = validateOptions(options); } /** From 3f0e2c5f91ee9d1c0e51739bd489373fe70b9e8c Mon Sep 17 00:00:00 2001 From: Michael Mok Date: Sun, 1 Mar 2020 11:34:54 +0800 Subject: [PATCH 4/7] feat(overlay): only inject overlay entry when user uses the integration --- src/helpers/injectRefreshEntry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/injectRefreshEntry.js b/src/helpers/injectRefreshEntry.js index 70eb7f75..9945b450 100644 --- a/src/helpers/injectRefreshEntry.js +++ b/src/helpers/injectRefreshEntry.js @@ -14,7 +14,7 @@ const injectRefreshEntry = (originalEntry, options) => { // React-refresh runtime require.resolve('../runtime/ReactRefreshEntry'), // Error overlay runtime - require.resolve('../runtime/ErrorOverlayEntry'), + options.overlay && options.overlay.entry, // React-refresh Babel transform detection require.resolve('../runtime/BabelDetectComponent'), ].filter(Boolean); From dc4c6609ff06ae3733ce76f83156840cf3f99ae9 Mon Sep 17 00:00:00 2001 From: Michael Mok Date: Sun, 1 Mar 2020 11:48:26 +0800 Subject: [PATCH 5/7] feat(overlay): fix typos in options validator to correctly inject defaults --- src/helpers/validateOptions.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/helpers/validateOptions.js b/src/helpers/validateOptions.js index ad471b78..1730c5c8 100644 --- a/src/helpers/validateOptions.js +++ b/src/helpers/validateOptions.js @@ -8,7 +8,7 @@ const defaultOptions = { /** @type {import('../types').ErrorOverlayOptions} */ const defaultOverlayOptions = { entry: require.resolve('../runtime/ErrorOverlayEntry'), - overlay: require.resolve('../overlay'), + module: require.resolve('../overlay'), }; /** @@ -19,7 +19,10 @@ const defaultOverlayOptions = { module.exports = function validateOptions(options) { const validatedOptions = Object.assign(defaultOptions, options); - if (typeof validatedOptions.overlay !== 'boolean') { + if ( + typeof validatedOptions.overlay !== 'undefined' && + typeof validatedOptions.overlay !== 'boolean' + ) { if (typeof validatedOptions.overlay.module !== 'string') { throw new Error( `To use the "overlay" option, a string must be provided in the "module" property. Instead, the provided value has type: "${typeof options From 3ceeae21a46b022d9d7ac16b15a25c072fd1286d Mon Sep 17 00:00:00 2001 From: Michael Mok Date: Sun, 1 Mar 2020 11:49:16 +0800 Subject: [PATCH 6/7] feat(overlay): hook up runtime utils to use customized error overlay --- src/index.js | 3 ++- src/runtime/globals.js | 2 ++ src/runtime/utils.js | 10 +++++++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/index.js b/src/index.js index 776beb0c..6d1c4698 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,7 @@ const path = require('path'); const webpack = require('webpack'); const { createRefreshTemplate, injectRefreshEntry, validateOptions } = require('./helpers'); -const { refreshUtils } = require('./runtime/globals'); +const { errorOverlay, refreshUtils } = require('./runtime/globals'); class ReactRefreshPlugin { /** @@ -36,6 +36,7 @@ class ReactRefreshPlugin { // Inject refresh utilities to Webpack's global scope const providePlugin = new webpack.ProvidePlugin({ + [errorOverlay]: this.options.overlay && require.resolve(this.options.overlay.module), [refreshUtils]: require.resolve('./runtime/utils'), }); providePlugin.apply(compiler); diff --git a/src/runtime/globals.js b/src/runtime/globals.js index c4c26af7..47621a5b 100644 --- a/src/runtime/globals.js +++ b/src/runtime/globals.js @@ -1 +1,3 @@ +module.exports.errorOverlay = '__react_refresh_error_overlay__'; + module.exports.refreshUtils = '__react_refresh_utils__'; diff --git a/src/runtime/utils.js b/src/runtime/utils.js index 0b6a6860..f66090f3 100644 --- a/src/runtime/utils.js +++ b/src/runtime/utils.js @@ -1,5 +1,5 @@ +/* global __react_refresh_error_overlay__ */ const Refresh = require('react-refresh/runtime'); -const ErrorOverlay = require('../overlay'); /** * Extracts exports from a webpack module object. @@ -73,7 +73,9 @@ function createHotErrorHandler(moduleId) { * @returns {void} */ function hotErrorHandler(error) { - ErrorOverlay.handleRuntimeError(error); + if (__react_refresh_error_overlay__) { + __react_refresh_error_overlay__.handleRuntimeError(error); + } } /** @@ -109,7 +111,9 @@ function createDebounceUpdate() { refreshTimeout = setTimeout(function() { refreshTimeout = undefined; Refresh.performReactRefresh(); - ErrorOverlay.clearRuntimeErrors(); + if (__react_refresh_error_overlay__) { + __react_refresh_error_overlay__.clearRuntimeErrors(); + } }, 30); } } From c42cd5b326ed9ede3308b5757d602d406267453c Mon Sep 17 00:00:00 2001 From: Michael Mok Date: Sun, 1 Mar 2020 12:18:52 +0800 Subject: [PATCH 7/7] docs(overlay): refactor docs and document overlay options usage --- README.md | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e40fd887..240c7df0 100644 --- a/README.md +++ b/README.md @@ -105,13 +105,71 @@ module.exports = api => { ## Options This plugin accepts a few options that are specifically targeted for advanced users. -The allowed values are as follows: -| Name | Type | Default | Description | -| :-----------------------: | :-------: | :-----: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`disableRefreshCheck`** | `boolean` | `false` | Disables detection of react-refresh's Babel plugin. Useful if you do not parse JS files within `node_modules`, or if you have a Babel setup not entirely controlled by Webpack. | -| **`forceEnable`** | `boolean` | `false` | Enables the plugin forcefully. Useful if you want to use the plugin in production, or if you are using Webpack's `none` mode without `NODE_ENV`, for example. | -| **`useLegacyWDSSockets`** | `boolean` | `false` | Set this to true if you are using a webpack-dev-server version prior to 3.8 as it requires a custom SocketJS implementation. If you use this, you will also need to install `sockjs-client` as a peer depencency. | +### `options.disableRefreshCheck` + +Type: `boolean` +Default: `false` + +Disables detection of react-refresh's Babel plugin. +Useful if you do not parse JS files within `node_modules`, or if you have a Babel setup not entirely controlled by Webpack. + +### `options.forceEnable` + +Type: `boolean` +Default: `false` + +Enables the plugin forcefully. +Useful if you want to use the plugin in production, or if you are using Webpack's `none` mode without `NODE_ENV`, for example. + +### `options.overlay` + +Type: `boolean | ErrorOverlayOptions` +Default: `undefined` + +Modifies how the error overlay integration works in the plugin. + +- If `options.overlay` is not provided or is `true`, the plugin will use the bundled error overlay interation. +- If `options.overlay` is `false`, it will disable the error overlay integration. +- If an `ErrorOverlayOptions` object is provided: + (**NOTE**: This is an advanced option that exists mostly for tools like `create-react-app` or `Next.js`) + + - A `module` property must be defined. + It should reference a JS file that exports at least two functions with footprints as follows: + + ```ts + function handleRuntimeError(error: Error) {} + function clearRuntimeErrors() {} + ``` + + - An optional `entry` property could also be defined, which should also reference a JS file that contains code needed to set up your custom error overlay integration. + If it is not defined, the bundled error overlay entry will be used. + It expects the `module` file to export two more functions: + + ```ts + function showCompileError(webpackErrorMessage: string) {} + function clearCompileErrors() {} + ``` + + Note that `webpackErrorMessage` is ANSI encoded, so you will need logic to parse it. + + - An example configuration: + ```js + const options = { + overlay: { + entry: 'some-webpack-entry-file', + module: 'some-error-overlay-module', + }, + }; + ``` + +### `options.useLegacyWDSSockets` + +Type: `boolean` +Default: `false` + +Set this to true if you are using a `webpack-dev-server` version prior to 3.8 as it requires a custom SockJS implementation. +If you use this feature, you will also need to install `sockjs-client` as a peer dependency. ## Related Work