Skip to content
40 changes: 29 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ More sample projects for common Webpack development setups are available in the
> Note: If you are using TypeScript (instead of Babel) as a transpiler, you will still need to use `babel-loader` to process your source code.
> Check out this [sample project](https://github.com/pmmmwh/react-refresh-webpack-plugin/tree/master/examples/typescript-without-babel) on how to set this up.

### Polyfill for Older Browsers (WDS Only)
### Polyfill for Older Browsers

If you need to develop on IE11, you will need to polyfill the DOM URL API.
This can be done by adding the following before any of your code in the main entry (either one is fine):
Expand Down Expand Up @@ -157,15 +157,16 @@ Modifies how the error overlay integration works in the plugin.
- 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:
- An optional `module` property could be defined.
If it is not defined, the bundled error overlay will be used.
If 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.
- An optional `entry` property could 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:

Expand All @@ -186,25 +187,42 @@ Modifies how the error overlay integration works in the plugin.
};
```

### `options.sockHost`
#### `options.overlay.sockHost`

Type: `string`
Default: effectively `window.location.hostname`
Default: `window.location.hostname`

Set this if you are running webpack on a host other than `window.location.hostname`. This is used by the error overlay module.
Set this if you are running webpack on a host other than `window.location.hostname`.
This will be used by the error overlay module, and is available for `webpack-dev-server` only.

### `options.sockPort`
#### `options.overlay.sockIntegration`

Type: `wds` or `whm` or `string`
Default: `wds`

This controls how the error overlay connects to the sockets provided by several Webpack hot reload integrations.

- If you use `webpack-dev-server`, you don't need to set this as it defaults to `wds`.
- If you use `webpack-hot-middleware`, you should set this to `whm`.
- If you use anything else, you will have to provide a path to a module that will accept a message handler function and initializes the socket connection.
See the [`runtime/sockets`](https://github.com/pmmmwh/react-refresh-webpack-plugin/tree/master/src/runtime/sockets) folder for sample implementations.

#### `options.overlay.sockPort`

Type: `number`
Default: effectively `window.location.port`
Default: `window.location.port`

Set this if you are running webpack on a port other than `window.location.port`
Set this if you are running webpack on a port other than `window.location.port`.
This will be used by the error overlay module, and is available for `webpack-dev-server` only.

### `options.sockPath`
#### `options.overlay.sockPath`

Type: `string`
Default: `/sockjs-node`

Set this if you are running webpack on a custom path.
This will be used by the error overlay module, and is available for `webpack-dev-server` only.

### `options.useLegacyWDSSockets`

Type: `boolean`
Expand Down
7 changes: 6 additions & 1 deletion examples/webpack-hot-middleware/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ module.exports = {
},
plugins: [
isDevelopment && new webpack.HotModuleReplacementPlugin(),
isDevelopment && new ReactRefreshPlugin(),
isDevelopment &&
new ReactRefreshPlugin({
overlay: {
sockIntegration: 'whm',
},
}),
new HtmlWebpackPlugin({
filename: './index.html',
template: './public/index.html',
Expand Down
18 changes: 18 additions & 0 deletions src/helpers/getSocketIntegration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
function getSocketIntegration(integrationType) {
let resolvedSocketIntegration;
switch (integrationType) {
case 'wds':
resolvedSocketIntegration = require.resolve('../runtime/sockets/WDSSocket');
break;
case 'whm':
resolvedSocketIntegration = require.resolve('../runtime/sockets/WHMEventSource');
break;
default:
resolvedSocketIntegration = require.resolve(integrationType);
break;
}

return resolvedSocketIntegration;
}

module.exports = getSocketIntegration;
2 changes: 2 additions & 0 deletions src/helpers/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
const createRefreshTemplate = require('./createRefreshTemplate');
const getSocketIntegration = require('./getSocketIntegration');
const injectRefreshEntry = require('./injectRefreshEntry');
const validateOptions = require('./validateOptions');

module.exports = {
createRefreshTemplate,
getSocketIntegration,
injectRefreshEntry,
validateOptions,
};
4 changes: 3 additions & 1 deletion src/helpers/validateOptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,17 +81,19 @@ function validateOptions(options) {
typeof defaultedOptions.overlay !== 'undefined' &&
typeof defaultedOptions.overlay !== 'boolean'
) {
const { entry, module: overlayModule, sockHost, sockPath, sockPort } = defaultedOptions.overlay;
const { entry, module: overlayModule, sockHost, sockIntegration, sockPath, sockPort } = defaultedOptions.overlay;
isStringOrUndefined('overlay.entry', entry);
isStringOrUndefined('overlay.module', overlayModule);
isStringOrUndefined('overlay.sockHost', sockHost);
isStringOrUndefined('overlay.sockIntegration', sockIntegration);
isStringOrUndefined('overlay.sockPath', sockPath);
isNumberOrUndefined('overlay.sockPort', sockPort);

defaultedOptions.overlay = {
...defaultedOptions.overlay,
entry: entry || defaultOverlayOptions.entry,
module: overlayModule || defaultOverlayOptions.module,
sockIntegration: sockIntegration || defaultOverlayOptions.sockIntegration,
};
} else {
defaultedOptions.overlay =
Expand Down
12 changes: 9 additions & 3 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
const path = require('path');
const webpack = require('webpack');
const { createRefreshTemplate, injectRefreshEntry, validateOptions } = require('./helpers');
const { errorOverlay, refreshUtils } = require('./runtime/globals');
const {
createRefreshTemplate,
getSocketIntegration,
injectRefreshEntry,
validateOptions,
} = require('./helpers');
const { errorOverlay, initSocket, refreshUtils } = require('./runtime/globals');

class ReactRefreshPlugin {
/**
* @param {import('./types').ReactRefreshPluginOptions} [options] Options for react-refresh-plugin.
* @returns {void}
*/
constructor(options) {
this.options = validateOptions(options);
this.options = validateOptions(options || {});
}

/**
Expand Down Expand Up @@ -39,6 +44,7 @@ class ReactRefreshPlugin {
[refreshUtils]: require.resolve('./runtime/refreshUtils'),
...(!!this.options.overlay && {
[errorOverlay]: require.resolve(this.options.overlay.module),
[initSocket]: getSocketIntegration(this.options.overlay.sockIntegration),
}),
});
providePlugin.apply(compiler);
Expand Down
7 changes: 3 additions & 4 deletions src/runtime/ErrorOverlayEntry.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
/* global __resourceQuery, __react_refresh_error_overlay__ */
/* global __resourceQuery, __react_refresh_error_overlay__, __react_refresh_init_socket__ */

const formatWebpackErrors = require('./formatWebpackErrors');
const createSocket = require('./createSocket');
const {
error: registerErrorHandler,
unhandledRejection: registerUnhandledRejectionHandler,
} = require('./errorEventHandlers');
const formatWebpackErrors = require('./formatWebpackErrors');

// Setup error states
let isHotReload = false;
Expand Down Expand Up @@ -80,7 +79,7 @@ if (__resourceQuery) {
}

// Registers handlers for compile errors
createSocket(compileMessageHandler, overrides);
__react_refresh_init_socket__(compileMessageHandler, overrides);
// Registers handlers for runtime errors
registerErrorHandler(function handleError(error) {
hasRuntimeErrors = true;
Expand Down
22 changes: 0 additions & 22 deletions src/runtime/WHMEventSource.js

This file was deleted.

38 changes: 0 additions & 38 deletions src/runtime/createSocket.js

This file was deleted.

8 changes: 5 additions & 3 deletions src/runtime/globals.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module.exports.errorOverlay = '__react_refresh_error_overlay__';

module.exports.refreshUtils = '__react_refresh_utils__';
module.exports = {
errorOverlay: '__react_refresh_error_overlay__',
initSocket: '__react_refresh_init_socket__',
refreshUtils: '__react_refresh_utils__',
};
33 changes: 33 additions & 0 deletions src/runtime/sockets/WDSSocket.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// eslint-disable-next-line no-unused-vars
/* global __resourceQuery, __webpack_dev_server_client__ */

const url = require('native-url');

/**
* Initializes a socket server for HMR according to the user's Webpack configuration.
* @param {function(*): void} messageHandler A handler to consume Webpack compilation messages.
* @param {*} overrides Socket integration overrides to change the connection URL.
* @returns {void}
*/
function initWDSSocket(messageHandler, overrides) {
if (typeof __webpack_dev_server_client__ !== 'undefined') {
const SocketClient = __webpack_dev_server_client__;
// TODO: Support usage of custom sockets after WDS 4.0 is released
// Ref: https://github.com/webpack/webpack-dev-server/pull/2055
const connection = new SocketClient(
url.format({
protocol: window.location.protocol,
hostname: overrides.sockHost || window.location.hostname,
port: overrides.sockPort || window.location.port,
pathname: overrides.sockPath || '/sockjs-node',
})
);

connection.onMessage(function onSocketMessage(data) {
const message = JSON.parse(data);
messageHandler(message);
});
}
}

module.exports = initWDSSocket;
37 changes: 37 additions & 0 deletions src/runtime/sockets/WHMEventSource.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* If the consumers' setup is to use webpack-hot-middleware with a custom express server,
* we want to bind onto the EventSource for error tracking.
*/

/**
* The hard-coded singleton key for webpack-hot-middleware's client instance.
*
* [Ref](https://github.com/webpack-contrib/webpack-hot-middleware/blob/cb29abb9dde435a1ac8e9b19f82d7d36b1093198/client.js#L152)
*/
const singletonKey = '__webpack_hot_middleware_reporter__';

/**
* Creates a socket server for HMR according to the user's Webpack configuration.
* @param {function(*): void} messageHandler A handler to consume Webpack compilation messages.
* @param {*} overrides Socket integration overrides to change the connection URL.
* @returns {void}
*/
function initWHMEventSource(messageHandler, overrides) {
const client = window[singletonKey] || require('webpack-hot-middleware/client');

client.useCustomOverlay({
showProblems(type, data) {
const error = {
type,
data,
};

messageHandler(error);
},
clear() {
messageHandler({ type: 'ok' });
},
});
}

module.exports = initWHMEventSource;
1 change: 1 addition & 0 deletions src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* @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} [sockHost] The socket host to use.
* @property {'wds' | 'whm' | string} [sockIntegration] Path to a JS file that sets up the Webpack socket integration.
* @property {string} [sockPath] The socket path to use.
* @property {number} [sockPort] The socket port to use.
*/
Expand Down