Skip to content

Commit d8d09d2

Browse files
authored
feat: error integration (#3)
feat: error integration
2 parents c6e66e9 + 90c6320 commit d8d09d2

29 files changed

+2417
-595
lines changed

.prettierrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"endOfLine": "lf",
3+
"printWidth": 100,
34
"singleQuote": true,
45
"trailingComma": "es5"
56
}

example/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"babel-loader": "^8.0.6",
2020
"cross-env": "^6.0.3",
2121
"html-webpack-plugin": "^3.2.0",
22-
"react-refresh": "^0.6.0",
22+
"react-refresh": "^0.7.0",
2323
"react-refresh-webpack-plugin": "https://github.com/pmmmwh/react-refresh-webpack-plugin#master",
2424
"webpack": "^4.41.2",
2525
"webpack-cli": "^3.3.9",

example/webpack.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const HtmlWebpackPlugin = require('html-webpack-plugin');
2-
const ReactRefreshPlugin = require('react-refresh-webpack-plugin');
2+
const ReactRefreshPlugin = require('../src');
33

44
module.exports = {
55
entry: './src/index.js',

example/yarn.lock

Lines changed: 517 additions & 504 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,23 @@
88
"lint": "prettier --check \"**/*.{js,jsx}\"",
99
"lint:fix": "prettier --write \"**/*.{js,jsx}\""
1010
},
11+
"dependencies": {
12+
"ansi-html": "^0.0.7",
13+
"error-stack-parser": "^2.0.4",
14+
"html-entities": "^1.2.1",
15+
"lodash.debounce": "^4.0.8",
16+
"react-dev-utils": "^9.1.0",
17+
"sockjs-client": "^1.4.0"
18+
},
1119
"devDependencies": {
1220
"prettier": "^1.18.2",
13-
"react-refresh": "^0.6.0",
21+
"react-refresh": "^0.7.0",
1422
"webpack": "^4.41.2"
1523
},
1624
"peerDependencies": {
17-
"react-refresh": "*"
25+
"react-refresh": ">= 0.7"
1826
},
1927
"engines": {
20-
"node": "8.x || 9.x || 10.x || 11.x || 12.x || 13.x"
28+
"node": ">= 8.x"
2129
}
2230
}

src/helpers/createRefreshTemplate.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const { Template } = require('webpack');
88
* [Ref](https://github.com/webpack/webpack/blob/master/lib/MainTemplate.js#L233)
99
*/
1010
const beforeModule = `
11-
var cleanup = function NoOp() {};
11+
let cleanup = function NoOp() {};
1212
1313
if (window && window.$RefreshSetup$) {
1414
cleanup = window.$RefreshSetup$(module.i);
@@ -33,10 +33,7 @@ const afterModule = `
3333
function createRefreshTemplate(source, chunk) {
3434
// If a chunk is injected with the plugin,
3535
// our custom entry for react-refresh musts be injected
36-
if (
37-
!chunk.entryModule ||
38-
!/ReactRefreshEntry/.test(chunk.entryModule._identifier || '')
39-
) {
36+
if (!chunk.entryModule || !/ReactRefreshEntry/.test(chunk.entryModule._identifier || '')) {
4037
return source;
4138
}
4239

src/helpers/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
const createRefreshTemplate = require('./createRefreshTemplate');
22
const injectRefreshEntry = require('./injectRefreshEntry');
33

4-
module.exports = { createRefreshTemplate, injectRefreshEntry };
4+
module.exports = {
5+
createRefreshTemplate,
6+
injectRefreshEntry,
7+
};

src/helpers/injectRefreshEntry.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,22 @@
77
* @returns {WebpackEntry} An injected entry object.
88
*/
99
const injectRefreshEntry = originalEntry => {
10-
const ReactRefreshEntry = require.resolve('../runtime/ReactRefreshEntry');
10+
const entryInjects = [
11+
// React-refresh runtime
12+
require.resolve('../runtime/ReactRefreshEntry'),
13+
// Error overlay runtime
14+
require.resolve('../runtime/ErrorOverlayEntry'),
15+
// React-refresh Babel transform detection
16+
require.resolve('../runtime/BabelDetectComponent'),
17+
];
1118

1219
// Single string entry point
1320
if (typeof originalEntry === 'string') {
14-
return [ReactRefreshEntry, originalEntry];
21+
return [...entryInjects, originalEntry];
1522
}
1623
// Single array entry point
1724
if (Array.isArray(originalEntry)) {
18-
return [ReactRefreshEntry, ...originalEntry];
25+
return [...entryInjects, ...originalEntry];
1926
}
2027
// Multiple entry points
2128
if (typeof originalEntry === 'object') {

src/index.js

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,25 @@ const webpack = require('webpack');
33
const { createRefreshTemplate, injectRefreshEntry } = require('./helpers');
44
const { refreshUtils } = require('./runtime/globals');
55

6+
/**
7+
* @typedef {Object} ReactRefreshPluginOptions
8+
* @property {boolean} [disableRefreshCheck] A flag to disable detection of the react-refresh Babel plugin.
9+
* @property {boolean} [forceEnable] A flag to enable the plugin forcefully.
10+
*/
11+
12+
/** @type {ReactRefreshPluginOptions} */
13+
const defaultOptions = {
14+
disableRefreshCheck: false,
15+
forceEnable: false,
16+
};
17+
618
class ReactRefreshPlugin {
719
/**
8-
* @param {*} [options] Options for react-refresh-plugin.
9-
* @param {boolean} [options.forceEnable] A flag to enable the plugin forcefully.
20+
* @param {ReactRefreshPluginOptions} [options] Options for react-refresh-plugin.
1021
* @returns {void}
1122
*/
1223
constructor(options) {
13-
this.options = options || {};
24+
this.options = Object.assign(defaultOptions, options);
1425
}
1526

1627
/**
@@ -59,11 +70,14 @@ class ReactRefreshPlugin {
5970
/\.([jt]sx?|flow)$/.test(data.resource) &&
6071
// Skip all files from node_modules
6172
!/node_modules/.test(data.resource) &&
62-
// Skip runtime refresh utilities (to prevent self-referencing)
73+
// Skip files related to refresh runtime (to prevent self-referencing)
6374
// This is useful when using the plugin as a direct dependency
64-
data.resource !== path.join(__dirname, './runtime/utils.js')
75+
!data.resource.includes(path.join(__dirname, './runtime'))
6576
) {
66-
data.loaders.unshift(require.resolve('./loader'));
77+
data.loaders.unshift({
78+
loader: require.resolve('./loader'),
79+
options: undefined,
80+
});
6781
}
6882

6983
return data;
@@ -76,6 +90,33 @@ class ReactRefreshPlugin {
7690
// Constructs the correct module template for react-refresh
7791
createRefreshTemplate
7892
);
93+
94+
compilation.hooks.finishModules.tap(this.constructor.name, modules => {
95+
if (!this.options.disableRefreshCheck) {
96+
const refreshPluginInjection = /\$RefreshReg\$/;
97+
const RefreshDetectionModule = modules.find(
98+
module => module.resource === require.resolve('./runtime/BabelDetectComponent.js')
99+
);
100+
101+
// In most cases, if we cannot find the injected detection module,
102+
// there are other compilation instances injected by other plugins.
103+
// We will have to bail out in those cases.
104+
if (!RefreshDetectionModule) {
105+
return;
106+
}
107+
108+
// Check for the function transform by the Babel plugin.
109+
if (!refreshPluginInjection.test(RefreshDetectionModule._source.source())) {
110+
throw new Error(
111+
[
112+
'The plugin is unable to detect transformed code from react-refresh.',
113+
'Did you forget to include "react-refresh/babel" in your list of Babel plugins?',
114+
'Note: you can disable this check by setting "disableRefreshCheck: true".',
115+
].join(' ')
116+
);
117+
}
118+
}
119+
});
79120
});
80121
}
81122
}

src/loader.js

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const path = require('path');
12
const { Template } = require('webpack');
23
const { refreshUtils } = require('./runtime/globals');
34

@@ -6,17 +7,32 @@ const reactModule = /['"]react['"]/;
67

78
/**
89
* A simple Webpack loader to inject react-refresh HMR code into modules.
10+
*
11+
* [Reference for Loader API](https://webpack.js.org/api/loaders/)
912
* @param {string} source The original module source code.
13+
* @param {*} [inputSourceMap] The source map of the module.
14+
* @property {function(string): void} addDependency Adds a dependency for Webpack to watch.
15+
* @property {function(Error | null, string | Buffer, *?, *?): void} callback Sends loader results to Webpack.
1016
* @returns {string} The injected module source code.
1117
*/
12-
function RefreshHotLoader(source) {
13-
// Only apply transform if the source code contains a React import
14-
return reactModule.test(source)
15-
? source +
16-
Template.getFunctionContent(require('./runtime/RefreshModuleRuntime'))
17-
.trim()
18-
.replace(/\$RefreshUtils\$/g, refreshUtils)
19-
: source;
18+
function RefreshHotLoader(source, inputSourceMap) {
19+
// Add dependency to allow caching and invalidations
20+
this.addDependency(path.resolve('./runtime/RefreshModuleRuntime'));
21+
22+
// Use callback to allow source maps to pass through
23+
this.callback(
24+
null,
25+
// Only apply transform if the source code contains a React import
26+
reactModule.test(source)
27+
? source +
28+
'\n\n' +
29+
Template.getFunctionContent(require('./runtime/RefreshModuleRuntime'))
30+
.trim()
31+
.replace(/^ {2}/gm, '')
32+
.replace(/\$RefreshUtils\$/g, refreshUtils)
33+
: source,
34+
inputSourceMap
35+
);
2036
}
2137

2238
module.exports = RefreshHotLoader;

0 commit comments

Comments
 (0)