Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ interface ReactRefreshPluginOptions {
exclude?: string | RegExp | Array<string | RegExp>;
include?: string | RegExp | Array<string | RegExp>;
library?: string;
runtimeEntry?: string | false;
esModule?: boolean | ESModuleOptions;
overlay?: boolean | ErrorOverlayOptions;
}
Expand Down Expand Up @@ -78,6 +79,17 @@ This is similar to the `output.uniqueName` in Webpack 5 or the `output.library`

It is most useful when multiple instances of React Refresh is running together simultaneously.

#### `runtimeEntry`

Type: `string | false`

Default: `'@pmmmwh/react-refresh-webpack-plugin/client/ReactRefreshEntry'`

The **PATH** to a file/module that sets up the React Refresh runtime integration.

This entry is prepended to Webpack entries by default. When set to `false`, no runtime entry will be injected automatically.
This is useful when the runtime needs to be registered manually, or when a target does not need it, such as a service worker entry.

#### `esModule`

Type: `boolean | ESModuleOptions`
Expand Down
3 changes: 3 additions & 0 deletions lib/options.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@
]
},
"library": { "type": "string" },
"runtimeEntry": {
"anyOf": [{ "const": false }, { "$ref": "#/definitions/Path" }]
},
"overlay": {
"anyOf": [{ "type": "boolean" }, { "$ref": "#/definitions/OverlayOptions" }]
}
Expand Down
3 changes: 2 additions & 1 deletion lib/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* @property {string | RegExp | Array<string | RegExp>} [include] Files to explicitly include for processing.
* @property {string} [library] Name of the library bundle.
* @property {boolean | ErrorOverlayOptions} [overlay] Modifies how the error overlay integration works in the plugin.
* @property {string | false} [runtimeEntry] Runtime entry to prepend to Webpack entries, or false to disable automatic injection.
*/

/**
Expand All @@ -25,7 +26,7 @@
*/

/**
* @typedef {import('type-fest').SetRequired<import('type-fest').Except<ReactRefreshPluginOptions, 'overlay'>, 'exclude' | 'include'> & OverlayOverrides} NormalizedPluginOptions
* @typedef {import('type-fest').SetRequired<import('type-fest').Except<ReactRefreshPluginOptions, 'overlay'>, 'exclude' | 'include' | 'runtimeEntry'> & OverlayOverrides} NormalizedPluginOptions
*/

module.exports = {};
4 changes: 2 additions & 2 deletions lib/utils/getAdditionalEntries.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
function getAdditionalEntries(options) {
const prependEntries = [
// React-refresh runtime
require.resolve('../../client/ReactRefreshEntry'),
];
options.runtimeEntry && require.resolve(options.runtimeEntry),
].filter(Boolean);

const overlayEntries = [
// Error overlay runtime
Expand Down
1 change: 1 addition & 0 deletions lib/utils/normalizeOptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const normalizeOptions = (options) => {
d(options, 'include', /\.([cm]js|[jt]sx?|flow)$/i);
d(options, 'forceEnable');
d(options, 'library');
d(options, 'runtimeEntry', require.resolve('../../client/ReactRefreshEntry'));

n(options, 'overlay', (overlay) => {
/** @type {import('../types').NormalizedErrorOverlayOptions} */
Expand Down
21 changes: 19 additions & 2 deletions test/unit/getAdditionalEntries.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,33 @@ const ReactRefreshEntry = require.resolve('../../client/ReactRefreshEntry');

describe('getAdditionalEntries', () => {
it('should work with default settings', () => {
expect(getAdditionalEntries({ overlay: { entry: ErrorOverlayEntry } })).toStrictEqual({
expect(
getAdditionalEntries({
overlay: { entry: ErrorOverlayEntry },
runtimeEntry: ReactRefreshEntry,
})
).toStrictEqual({
overlayEntries: [ErrorOverlayEntry],
prependEntries: [ReactRefreshEntry],
});
});

it('should skip overlay entries when overlay is false in options', () => {
expect(getAdditionalEntries({ overlay: false })).toStrictEqual({
expect(
getAdditionalEntries({
overlay: false,
runtimeEntry: ReactRefreshEntry,
})
).toStrictEqual({
overlayEntries: [],
prependEntries: [ReactRefreshEntry],
});
});

it('should skip prepend entries when runtimeEntry is false in options', () => {
expect(getAdditionalEntries({ overlay: false, runtimeEntry: false })).toStrictEqual({
overlayEntries: [],
prependEntries: [],
});
});
});
12 changes: 12 additions & 0 deletions test/unit/normalizeOptions.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
const normalizeOptions = require('../../lib/utils/normalizeOptions');

const ReactRefreshEntry = require.resolve('../../client/ReactRefreshEntry');

/** @type {Partial<import('../../types/types').ReactRefreshPluginOptions>} */
const DEFAULT_OPTIONS = {
exclude: /node_modules/i,
include: /\.([cm]js|[jt]sx?|flow)$/i,
runtimeEntry: ReactRefreshEntry,
overlay: {
entry: require.resolve('../../client/ErrorOverlayEntry'),
module: require.resolve('../../overlay'),
Expand All @@ -23,6 +26,7 @@ describe('normalizeOptions', () => {
forceEnable: true,
include: 'include',
library: 'library',
runtimeEntry: 'runtimeEntry',
overlay: {
entry: 'entry',
module: 'overlay',
Expand All @@ -34,6 +38,7 @@ describe('normalizeOptions', () => {
forceEnable: true,
include: 'include',
library: 'library',
runtimeEntry: 'runtimeEntry',
overlay: {
entry: 'entry',
module: 'overlay',
Expand All @@ -53,6 +58,13 @@ describe('normalizeOptions', () => {
});
});

it('should keep "runtimeEntry" when it is false', () => {
expect(normalizeOptions({ runtimeEntry: false })).toStrictEqual({
...DEFAULT_OPTIONS,
runtimeEntry: false,
});
});

it('should keep "overlay.entry" when it is false', () => {
const options = { ...DEFAULT_OPTIONS };
options.overlay.entry = false;
Expand Down
33 changes: 32 additions & 1 deletion test/unit/validateOptions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,37 @@ describe('validateOptions', () => {
`);
});

it('should accept "runtimeEntry" when it is an absolute path string', () => {
expect(() => {
new ReactRefreshPlugin({ runtimeEntry: '/test' });
}).not.toThrow();
});

it('should accept "runtimeEntry" when it is a string', () => {
expect(() => {
new ReactRefreshPlugin({ runtimeEntry: 'test' });
}).not.toThrow();
});

it('should accept "runtimeEntry" when it is false', () => {
expect(() => {
new ReactRefreshPlugin({ runtimeEntry: false });
}).not.toThrow();
});

it('should reject "runtimeEntry" when it is not a string nor false', () => {
expect(() => {
new ReactRefreshPlugin({ runtimeEntry: true });
}).toThrowErrorMatchingInlineSnapshot(`
"Invalid options object. React Refresh Plugin has been initialized using an options object that does not match the API schema.
- options.runtimeEntry should be one of these:
false | string
Details:
* options.runtimeEntry should be equal to constant false.
* options.runtimeEntry should be a string."
`);
});

it('should accept "overlay" when it is true', () => {
expect(() => {
new ReactRefreshPlugin({ overlay: true });
Expand Down Expand Up @@ -311,7 +342,7 @@ describe('validateOptions', () => {
}).toThrowErrorMatchingInlineSnapshot(`
"Invalid options object. React Refresh Plugin has been initialized using an options object that does not match the API schema.
- options has an unknown property 'unknown'. These properties are valid:
object { esModule?, exclude?, forceEnable?, include?, library?, overlay? }"
object { esModule?, exclude?, forceEnable?, include?, library?, runtimeEntry?, overlay? }"
`);
});
});
6 changes: 5 additions & 1 deletion types/lib/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ export type ReactRefreshPluginOptions = {
* Modifies how the error overlay integration works in the plugin.
*/
overlay?: boolean | ErrorOverlayOptions | undefined;
/**
* Runtime entry to prepend to Webpack entries, or false to disable automatic injection.
*/
runtimeEntry?: string | false | undefined;
};
export type OverlayOverrides = {
/**
Expand All @@ -52,6 +56,6 @@ export type OverlayOverrides = {
};
export type NormalizedPluginOptions = import('type-fest').SetRequired<
import('type-fest').Except<ReactRefreshPluginOptions, 'overlay'>,
'exclude' | 'include'
'exclude' | 'include' | 'runtimeEntry'
> &
OverlayOverrides;