Skip to content

Commit

Permalink
feat: context-aware runtime (#337)
Browse files Browse the repository at this point in the history
  • Loading branch information
pmmmwh authored Apr 14, 2021
1 parent baf0103 commit b1d6959
Show file tree
Hide file tree
Showing 82 changed files with 2,652 additions and 1,186 deletions.
7 changes: 7 additions & 0 deletions .babelrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"env": {
"test": {
"plugins": ["@babel/plugin-transform-modules-commonjs"]
}
}
}
10 changes: 5 additions & 5 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ version: 2.1

anchors:
- &node-version-enum
- '10.23'
- '12.19'
- '14.15'
- '15.2'
- '10.24'
- '12.22'
- '14.16'
- '15.11'
- &webpack-version-enum
- '4'
- '5'
- &node-version-param
node-version:
default: '12.19'
default: '14.16'
enum: *node-version-enum
type: enum
- &webpack-version-param
Expand Down
16 changes: 7 additions & 9 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,10 @@
},
"overrides": [
{
"files": [
"client/**/*.js",
"overlay/**/*.js",
"lib/runtime/**/*.js",
"loader/*.runtime.js",
"sockets/**/*.js"
],
"files": ["client/**/*.js", "overlay/**/*.js", "lib/runtime/**/*.js", "sockets/**/*.js"],
"parserOptions": {
"ecmaVersion": 2015
"ecmaVersion": 2015,
"sourceType": "module"
},
"env": {
"browser": true,
Expand All @@ -39,6 +34,9 @@
"__DEBUG__": true,
"WEBPACK_VERSION": true,
"browser": true
},
"parserOptions": {
"sourceType": "module"
}
},
{
Expand All @@ -48,7 +46,7 @@
}
},
{
"files": ["test/**/fixtures/*.esm.js"],
"files": ["test/**/fixtures/esm/*.js"],
"parserOptions": {
"ecmaVersion": 2015,
"sourceType": "module"
Expand Down
16 changes: 8 additions & 8 deletions client/ErrorOverlayEntry.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/* global __react_refresh_error_overlay__, __react_refresh_init_socket__, __resourceQuery */
/* global __react_refresh_error_overlay__, __react_refresh_socket__, __resourceQuery */

const errorEventHandlers = require('./utils/errorEventHandlers');
const formatWebpackErrors = require('./utils/formatWebpackErrors');
const runWithPatchedUrl = require('./utils/patchUrl');
import { handleError, handleUnhandledRejection } from './utils/errorEventHandlers.js';
import formatWebpackErrors from './utils/formatWebpackErrors.js';
import runWithPatchedUrl from './utils/patchUrl.js';

// Setup error states
let isHotReload = false;
Expand Down Expand Up @@ -34,7 +34,7 @@ function handleCompileSuccess() {

/**
* A function called after a compile errored signal is received from Webpack.
* @param {string} errors
* @param {string[]} errors
* @returns {void}
*/
function handleCompileErrors(errors) {
Expand Down Expand Up @@ -76,13 +76,13 @@ if (process.env.NODE_ENV !== 'production' && typeof window !== 'undefined') {
// Only register if no other overlay have been registered
if (!window.__reactRefreshOverlayInjected) {
// Registers handlers for compile errors
__react_refresh_init_socket__(compileMessageHandler, __resourceQuery);
__react_refresh_socket__.init(compileMessageHandler, __resourceQuery);
// Registers handlers for runtime errors
errorEventHandlers.error(function handleError(error) {
handleError(function handleError(error) {
hasRuntimeErrors = true;
__react_refresh_error_overlay__.handleRuntimeError(error);
});
errorEventHandlers.unhandledRejection(function handleUnhandledPromiseRejection(error) {
handleUnhandledRejection(function handleUnhandledPromiseRejection(error) {
hasRuntimeErrors = true;
__react_refresh_error_overlay__.handleRuntimeError(error);
});
Expand Down
2 changes: 1 addition & 1 deletion client/LegacyWDSSocketEntry.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const SockJS = require('sockjs-client/dist/sockjs');
import * as SockJS from 'sockjs-client/dist/sockjs.js';

/**
* A SockJS client adapted for use with webpack-dev-server.
Expand Down
6 changes: 3 additions & 3 deletions client/ReactRefreshEntry.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
/* global __react_refresh_library__ */

const safeThis = require('./utils/safeThis');
import safeThis from 'core-js-pure/features/global-this';
import * as RefreshRuntime from 'react-refresh/runtime';

if (process.env.NODE_ENV !== 'production' && typeof safeThis !== 'undefined') {
let $RefreshInjected$ = '__reactRefreshInjected';
var $RefreshInjected$ = '__reactRefreshInjected';
// Namespace the injected flag (if necessary) for monorepo compatibility
if (typeof __react_refresh_library__ !== 'undefined' && __react_refresh_library__) {
$RefreshInjected$ += '_' + __react_refresh_library__;
}

// Only inject the runtime if it hasn't been injected
if (!safeThis[$RefreshInjected$]) {
const RefreshRuntime = require('react-refresh/runtime');
// Inject refresh runtime into global scope
RefreshRuntime.injectIntoGlobalHook(safeThis);

Expand Down
9 changes: 5 additions & 4 deletions client/utils/errorEventHandlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ function createWindowEventHandler(eventType, createHandler) {
return register;
}

module.exports = {
error: createWindowEventHandler('error', createErrorHandler),
unhandledRejection: createWindowEventHandler('unhandledrejection', createRejectionHandler),
};
export const handleError = createWindowEventHandler('error', createErrorHandler);
export const handleUnhandledRejection = createWindowEventHandler(
'unhandledrejection',
createRejectionHandler
);
3 changes: 2 additions & 1 deletion client/utils/formatWebpackErrors.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,12 @@ function formatWebpackErrors(errors) {
// Webpack 4 compilation errors are strings
return formatMessage(errorObjOrMessage);
});

if (formattedErrors.some(isLikelyASyntaxError)) {
// If there are any syntax errors, show just them.
formattedErrors = formattedErrors.filter(isLikelyASyntaxError);
}
return formattedErrors;
}

module.exports = formatWebpackErrors;
export default formatWebpackErrors;
10 changes: 4 additions & 6 deletions client/utils/patchUrl.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/* global __react_refresh_polyfill_url__ */
import SafeURL from 'core-js-pure/web/url';
import SafeURLSearchParams from 'core-js-pure/web/url-search-params';

/**
* @typedef {Object} UrlAPIs
Expand All @@ -11,20 +13,16 @@
* @param {function(UrlAPIs): void} callback The code to run with patched URL globals.
* @returns {void}
*/
function runWithURLPatch(callback) {
function runWithPatchedUrl(callback) {
let __originalURL;
let __originalURLSearchParams;

// Polyfill the DOM URL and URLSearchParams constructors
if (__react_refresh_polyfill_url__ || !window.URL) {
const SafeURL = require('core-js-pure/web/url');

__originalURL = window.URL;
window.URL = SafeURL;
}
if (__react_refresh_polyfill_url__ || !window.URLSearchParams) {
const SafeURLSearchParams = require('core-js-pure/web/url-search-params');

__originalURLSearchParams = window.URLSearchParams;
window.URLSearchParams = SafeURLSearchParams;
}
Expand All @@ -41,4 +39,4 @@ function runWithURLPatch(callback) {
}
}

module.exports = runWithURLPatch;
export default runWithPatchedUrl;
19 changes: 0 additions & 19 deletions client/utils/safeThis.js

This file was deleted.

43 changes: 43 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface ReactRefreshPluginOptions {
exclude?: string | RegExp | Array<string | RegExp>;
include?: string | RegExp | Array<string | RegExp>;
library?: string;
esModule?: boolean | ESModuleOptions;
overlay?: boolean | ErrorOverlayOptions;
}
```
Expand Down Expand Up @@ -61,6 +62,21 @@ 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.

### `esModule`

Type: `boolean | ESModuleOptions`

Default: `undefined` (auto-detection)

Enables strict ES Modules compatible runtime.
By default, the plugin will try to infer the module system same as Webpack 5,
either via the `type` property in `package.json` (`commonjs` and `module`),
or via the file extension (`.cjs` and `.mjs`).

It is most useful when you want to enforce output of native ESM code.

See the [`ESModuleOptions`](#esmoduleoptions) section below for more details on the object API.

### `overlay`

Type: `boolean | ErrorOverlayOptions`
Expand All @@ -84,6 +100,33 @@ Modifies behaviour of the plugin's error overlay integration:

See the [`ErrorOverlayOptions`](#erroroverlayoptions) section below for more details on the object API.

## `ESModuleOptions`

```ts
interface ESModuleOptions {
exclude?: string | RegExp | Array<string | RegExp>;
include?: string | RegExp | Array<string | RegExp>;
}
```

### `exclude`

Type: `string | RegExp | Array<string | RegExp>`

Default: `/node_modules/`

Exclude files from being processed as ESM.
This is similar to the `module.rules` option in Webpack.

### `include`

Type: `string | RegExp | Array<string | RegExp>`

Default: `/\.([jt]sx?|flow)$/i`

Include files to be processed as ESM.
This is similar to the `module.rules` option in Webpack.

## `ErrorOverlayOptions`

```ts
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const RuntimeGlobals = require('webpack/lib/RuntimeGlobals');
const RuntimeModule = require('webpack/lib/RuntimeModule');
const Template = require('webpack/lib/Template');
const { refreshGlobal } = require('../globals');
const getRefreshGlobal = require('../utils/getRefreshGlobal');
const { refreshGlobal } = require('./globals');
const { getRefreshGlobal } = require('./utils');

class ReactRefreshRuntimeModule extends RuntimeModule {
constructor() {
Expand Down
31 changes: 15 additions & 16 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,6 @@ const schema = require('./options.json');

// Mapping of react-refresh globals to Webpack runtime globals
const REPLACEMENTS = {
$RefreshRuntime$: {
expr: `${refreshGlobal}.runtime`,
req: [webpackRequire, `${refreshGlobal}.runtime`],
type: 'object',
},
$RefreshCleanup$: {
expr: `${refreshGlobal}.cleanup`,
req: [webpackRequire, `${refreshGlobal}.cleanup`],
type: 'function',
},
$RefreshReg$: {
expr: `${refreshGlobal}.register`,
req: [webpackRequire, `${refreshGlobal}.register`],
Expand Down Expand Up @@ -105,8 +95,8 @@ class ReactRefreshPlugin {
if (this.options.overlay === false) {
// Stub errorOverlay module so their calls can be erased
definedModules.__react_refresh_error_overlay__ = false;
definedModules.__react_refresh_init_socket__ = false;
definedModules.__react_refresh_polyfill_url__ = false;
definedModules.__react_refresh_socket__ = false;
} else {
definedModules.__react_refresh_polyfill_url__ = this.options.overlay.useURLPolyfill || false;

Expand All @@ -116,7 +106,7 @@ class ReactRefreshPlugin {
);
}
if (this.options.overlay.sockIntegration) {
providedModules.__react_refresh_init_socket__ = getSocketIntegration(
providedModules.__react_refresh_socket__ = getSocketIntegration(
this.options.overlay.sockIntegration
);
}
Expand All @@ -127,7 +117,7 @@ class ReactRefreshPlugin {
const providePlugin = new ProvidePlugin(providedModules);
providePlugin.apply(compiler);

const matchObject = ModuleFilenameHelpers.matchObject.bind(undefined, this.options);
const match = ModuleFilenameHelpers.matchObject.bind(undefined, this.options);
const { evaluateToString, toConstantDependency } = getParserHelpers();
compiler.hooks.compilation.tap(
this.constructor.name,
Expand Down Expand Up @@ -217,7 +207,10 @@ class ReactRefreshPlugin {
this.constructor.name,
// Add react-refresh loader to process files that matches specified criteria
(data) => {
return injectRefreshLoader(data, matchObject);
return injectRefreshLoader(data, {
match,
options: { const: false, esModule: false },
});
}
);

Expand All @@ -243,7 +236,7 @@ class ReactRefreshPlugin {
case 5: {
const NormalModule = require('webpack/lib/NormalModule');
const RuntimeGlobals = require('webpack/lib/RuntimeGlobals');
const ReactRefreshRuntimeModule = require('./runtime/RefreshRuntimeModule');
const ReactRefreshRuntimeModule = require('./RefreshRuntimeModule');

compilation.hooks.additionalTreeRuntimeRequirements.tap(
this.constructor.name,
Expand All @@ -258,7 +251,13 @@ class ReactRefreshPlugin {
this.constructor.name,
// Add react-refresh loader to process files that matches specified criteria
(resolveData) => {
injectRefreshLoader(resolveData.createData, matchObject);
injectRefreshLoader(resolveData.createData, {
match,
options: {
const: compilation.runtimeTemplate.supportsConst(),
esModule: this.options.esModule,
},
});
}
);

Expand Down
Loading

0 comments on commit b1d6959

Please sign in to comment.