Skip to content

Commit bd6dd94

Browse files
authored
Merge pull request #44 from pmmmwh/feat/customize-overlay
feat(overlay): allow full customization of the error overlay integration
2 parents 0a6c114 + c42cd5b commit bd6dd94

File tree

8 files changed

+141
-30
lines changed

8 files changed

+141
-30
lines changed

README.md

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,71 @@ module.exports = api => {
105105
## Options
106106

107107
This plugin accepts a few options that are specifically targeted for advanced users.
108-
The allowed values are as follows:
109108

110-
| Name | Type | Default | Description |
111-
| :-----------------------: | :-------: | :-----: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
112-
| **`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. |
113-
| **`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. |
114-
| **`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. |
109+
### `options.disableRefreshCheck`
110+
111+
Type: `boolean`
112+
Default: `false`
113+
114+
Disables detection of react-refresh's Babel plugin.
115+
Useful if you do not parse JS files within `node_modules`, or if you have a Babel setup not entirely controlled by Webpack.
116+
117+
### `options.forceEnable`
118+
119+
Type: `boolean`
120+
Default: `false`
121+
122+
Enables the plugin forcefully.
123+
Useful if you want to use the plugin in production, or if you are using Webpack's `none` mode without `NODE_ENV`, for example.
124+
125+
### `options.overlay`
126+
127+
Type: `boolean | ErrorOverlayOptions`
128+
Default: `undefined`
129+
130+
Modifies how the error overlay integration works in the plugin.
131+
132+
- If `options.overlay` is not provided or is `true`, the plugin will use the bundled error overlay interation.
133+
- If `options.overlay` is `false`, it will disable the error overlay integration.
134+
- If an `ErrorOverlayOptions` object is provided:
135+
(**NOTE**: This is an advanced option that exists mostly for tools like `create-react-app` or `Next.js`)
136+
137+
- A `module` property must be defined.
138+
It should reference a JS file that exports at least two functions with footprints as follows:
139+
140+
```ts
141+
function handleRuntimeError(error: Error) {}
142+
function clearRuntimeErrors() {}
143+
```
144+
145+
- 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.
146+
If it is not defined, the bundled error overlay entry will be used.
147+
It expects the `module` file to export two more functions:
148+
149+
```ts
150+
function showCompileError(webpackErrorMessage: string) {}
151+
function clearCompileErrors() {}
152+
```
153+
154+
Note that `webpackErrorMessage` is ANSI encoded, so you will need logic to parse it.
155+
156+
- An example configuration:
157+
```js
158+
const options = {
159+
overlay: {
160+
entry: 'some-webpack-entry-file',
161+
module: 'some-error-overlay-module',
162+
},
163+
};
164+
```
165+
166+
### `options.useLegacyWDSSockets`
167+
168+
Type: `boolean`
169+
Default: `false`
170+
171+
Set this to true if you are using a `webpack-dev-server` version prior to 3.8 as it requires a custom SockJS implementation.
172+
If you use this feature, you will also need to install `sockjs-client` as a peer dependency.
115173

116174
## Related Work
117175

src/helpers/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
const createRefreshTemplate = require('./createRefreshTemplate');
22
const injectRefreshEntry = require('./injectRefreshEntry');
3+
const validateOptions = require('./validateOptions');
34

45
module.exports = {
56
createRefreshTemplate,
67
injectRefreshEntry,
8+
validateOptions,
79
};

src/helpers/injectRefreshEntry.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,17 @@
44
/**
55
* Injects an entry to the bundle for react-refresh.
66
* @param {WebpackEntry} [originalEntry] A Webpack entry object.
7-
* @param {ReactRefreshPluginOptions} [options] Configuration options for this plugin
7+
* @param {import('../types').ReactRefreshPluginOptions} [options] Configuration options for this plugin.
88
* @returns {WebpackEntry} An injected entry object.
99
*/
1010
const injectRefreshEntry = (originalEntry, options) => {
1111
const entryInjects = [
12+
// Legacy WDS SockJS integration
1213
options.useLegacyWDSSockets && require.resolve('../runtime/LegacyWebpackDevServerSocket'),
1314
// React-refresh runtime
1415
require.resolve('../runtime/ReactRefreshEntry'),
1516
// Error overlay runtime
16-
require.resolve('../runtime/ErrorOverlayEntry'),
17+
options.overlay && options.overlay.entry,
1718
// React-refresh Babel transform detection
1819
require.resolve('../runtime/BabelDetectComponent'),
1920
].filter(Boolean);

src/helpers/validateOptions.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/** @type {import('../types').ReactRefreshPluginOptions} */
2+
const defaultOptions = {
3+
disableRefreshCheck: false,
4+
forceEnable: false,
5+
useLegacyWDSSockets: false,
6+
};
7+
8+
/** @type {import('../types').ErrorOverlayOptions} */
9+
const defaultOverlayOptions = {
10+
entry: require.resolve('../runtime/ErrorOverlayEntry'),
11+
module: require.resolve('../overlay'),
12+
};
13+
14+
/**
15+
* Validates the options for the plugin.
16+
* @param {import('../types').ReactRefreshPluginOptions} options Non-validated plugin options object.
17+
* @returns {import('../types').ReactRefreshPluginOptions} Validated plugin options.
18+
*/
19+
module.exports = function validateOptions(options) {
20+
const validatedOptions = Object.assign(defaultOptions, options);
21+
22+
if (
23+
typeof validatedOptions.overlay !== 'undefined' &&
24+
typeof validatedOptions.overlay !== 'boolean'
25+
) {
26+
if (typeof validatedOptions.overlay.module !== 'string') {
27+
throw new Error(
28+
`To use the "overlay" option, a string must be provided in the "module" property. Instead, the provided value has type: "${typeof options
29+
.overlay.module}".`
30+
);
31+
}
32+
33+
validatedOptions.overlay = {
34+
entry: options.overlay.entry || defaultOverlayOptions.entry,
35+
module: options.overlay.module,
36+
};
37+
} else {
38+
validatedOptions.overlay =
39+
(typeof validatedOptions.overlay === 'undefined' || validatedOptions.overlay) &&
40+
defaultOverlayOptions;
41+
}
42+
43+
return validatedOptions;
44+
};

src/index.js

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,19 @@
11
const path = require('path');
22
const webpack = require('webpack');
3-
const { createRefreshTemplate, injectRefreshEntry } = require('./helpers');
4-
const { refreshUtils } = require('./runtime/globals');
5-
6-
/**
7-
* @typedef {Object} ReactRefreshPluginOptions
8-
* @property {boolean} [disableRefreshCheck] Disables detection of react-refresh's Babel plugin.
9-
* @property {boolean} [forceEnable] Enables the plugin forcefully.
10-
* @property {boolean} [useLegacyWDSSockets] Uses a custom SocketJS implementation for older versions of webpack-dev-server
11-
*/
12-
13-
/** @type {ReactRefreshPluginOptions} */
14-
const defaultOptions = {
15-
disableRefreshCheck: false,
16-
forceEnable: false,
17-
useLegacyWDSSockets: false,
18-
};
3+
const { createRefreshTemplate, injectRefreshEntry, validateOptions } = require('./helpers');
4+
const { errorOverlay, refreshUtils } = require('./runtime/globals');
195

206
class ReactRefreshPlugin {
217
/**
22-
* @param {ReactRefreshPluginOptions} [options] Options for react-refresh-plugin.
8+
* @param {import('./types').ReactRefreshPluginOptions} [options] Options for react-refresh-plugin.
239
* @returns {void}
2410
*/
2511
constructor(options) {
26-
this.options = Object.assign(defaultOptions, options);
12+
this.options = validateOptions(options);
2713
}
2814

2915
/**
30-
* Applies the plugin
16+
* Applies the plugin.
3117
* @param {import('webpack').Compiler} compiler A webpack compiler object.
3218
* @returns {void}
3319
*/
@@ -50,6 +36,7 @@ class ReactRefreshPlugin {
5036

5137
// Inject refresh utilities to Webpack's global scope
5238
const providePlugin = new webpack.ProvidePlugin({
39+
[errorOverlay]: this.options.overlay && require.resolve(this.options.overlay.module),
5340
[refreshUtils]: require.resolve('./runtime/utils'),
5441
});
5542
providePlugin.apply(compiler);

src/runtime/globals.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1+
module.exports.errorOverlay = '__react_refresh_error_overlay__';
2+
13
module.exports.refreshUtils = '__react_refresh_utils__';

src/runtime/utils.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
/* global __react_refresh_error_overlay__ */
12
const Refresh = require('react-refresh/runtime');
2-
const ErrorOverlay = require('../overlay');
33

44
/**
55
* Extracts exports from a webpack module object.
@@ -73,7 +73,9 @@ function createHotErrorHandler(moduleId) {
7373
* @returns {void}
7474
*/
7575
function hotErrorHandler(error) {
76-
ErrorOverlay.handleRuntimeError(error);
76+
if (__react_refresh_error_overlay__) {
77+
__react_refresh_error_overlay__.handleRuntimeError(error);
78+
}
7779
}
7880

7981
/**
@@ -109,7 +111,9 @@ function createDebounceUpdate() {
109111
refreshTimeout = setTimeout(function() {
110112
refreshTimeout = undefined;
111113
Refresh.performReactRefresh();
112-
ErrorOverlay.clearRuntimeErrors();
114+
if (__react_refresh_error_overlay__) {
115+
__react_refresh_error_overlay__.clearRuntimeErrors();
116+
}
113117
}, 30);
114118
}
115119
}

src/types.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* @typedef {Object} ErrorOverlayOptions
3+
* @property {string} [entry] Path to a JS file that sets up the error overlay integration.
4+
* @property {string} module The error overlay module to use.
5+
*/
6+
7+
/**
8+
* @typedef {Object} ReactRefreshPluginOptions
9+
* @property {boolean} [disableRefreshCheck] Disables detection of react-refresh's Babel plugin.
10+
* @property {boolean} [forceEnable] Enables the plugin forcefully.
11+
* @property {boolean | ErrorOverlayOptions} [overlay] Modifies how the error overlay integration works in the plugin.
12+
* @property {boolean} [useLegacyWDSSockets] Uses a custom SocketJS implementation for older versions of webpack-dev-server.
13+
*/

0 commit comments

Comments
 (0)