Skip to content

Commit 3029428

Browse files
committed
feat: new root/hot for better error management. Fixes #1078, #1111
1 parent 7a421a1 commit 3029428

8 files changed

+136
-24
lines changed

Diff for: README.md

+40-4
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@ npm install react-hot-loader
3030

3131
Latest (4.5.0+, beta) version of React-Hot-Loader could be quite 🔥!
3232

33-
> RHL will patch React, React-DOM and work with fiber directly
33+
> RHL will patch React, replace React-DOM by React-🔥-DOM and work with fiber directly
3434
3535
* (required) [use webpack plugin](https://github.com/gaearon/react-hot-loader#webpack-plugin) to let RHL patch React-DOM for you.
36+
* (alternative) [use react-🔥-dom](https://github.com/gaearon/react-hot-loader#react-hot-dom) to use already patched React-DOM.
3637
* (optional) [set configuration](https://github.com/gaearon/react-hot-loader#setconfigconfig) to `ignoreSFC:true` (this will fix `hook`)
3738
* (optional) [set configuration](https://github.com/gaearon/react-hot-loader#setconfigconfig) to `pureRender:true` (this will remove side effect from Classes)
3839

@@ -60,11 +61,17 @@ setConfig({
6061

6162
```js
6263
// App.js
63-
import React from 'react'
64-
import { hot } from 'react-hot-loader'
65-
64+
import { hot } from 'react-hot-loader/root'
6665
const App = () => <div>Hello World!</div>
66+
export default hot(App)
67+
```
68+
69+
There is also another version of `hot`, used prior version 4.5.4. Please use a new one,
70+
as long is it much more resilient to js errors you may make during development.
6771

72+
```js
73+
import { hot } from 'react-hot-loader'
74+
const App = () => <div>Hello World!</div>
6875
export default hot(module)(App)
6976
```
7077

@@ -241,6 +248,35 @@ module.exports = {
241248
}
242249
```
243250

251+
Webpack plugin will also land a "hot" patch to react-dom, making React-Hot-Loader more compliant to [the principles](https://github.com/gaearon/react-hot-loader/issues/1118).
252+
253+
## React-🔥-Dom
254+
255+
Another way to make RHL more compliant is to use _our_ version of React-Dom - [hot-loader/react-dom](https://github.com/hot-loader/react-dom)
256+
257+
It is the same React-Dom, with the same version, just with our patches already landed inside.
258+
259+
There is 2 ways to install it:
260+
261+
* Use **yarn** name resolution, so `@hot-loader/react-dom` would be installed instead of `react-dom`
262+
263+
```
264+
yarn add @hot-loader/react-dom@npm:react-dom
265+
```
266+
267+
* Use webpack **aliases**
268+
269+
```js
270+
// webpack.conf
271+
...
272+
resolve: {
273+
alias: {
274+
'react-dom': '@hot-loader/react-dom'
275+
}
276+
}
277+
...
278+
```
279+
244280
### Code Splitting
245281

246282
If you want to use Code Splitting + React Hot Loader, the simplest solution is to pick one of our compatible library:

Diff for: package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": "4.5.3",
44
"description": "Tweak React components in real time.",
55
"main": "index.js",
6-
"types": "react-hot-loader.d.ts",
6+
"types": "index.d.ts",
77
"homepage": "https://github.com/gaearon/react-hot-loader",
88
"repository": "https://github.com/gaearon/react-hot-loader/",
99
"license": "MIT",
@@ -33,7 +33,8 @@
3333
"babel.js",
3434
"webpack.js",
3535
"patch.js",
36-
"react-hot-loader.d.ts"
36+
"root.js",
37+
"*.d.ts"
3738
],
3839
"sideEffects": false,
3940
"keywords": [
@@ -49,6 +50,7 @@
4950
"reload"
5051
],
5152
"devDependencies": {
53+
"@types/react": "^16.7.13",
5254
"babel-cli": "^6.7.5",
5355
"babel-core": "^6.26.3",
5456
"babel-eslint": "^8.2.3",

Diff for: src/AppContainer.dev.js

+19-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import PropTypes from 'prop-types'
33
import defaultPolyfill, { polyfill } from 'react-lifecycles-compat'
44
import logger from './logger'
55
import { get as getGeneration } from './global/generation'
6+
import configuration from './configuration'
7+
import { logException } from './errorReporter'
68

79
class AppContainer extends React.Component {
810
static getDerivedStateFromProps(nextProps, prevState) {
@@ -18,6 +20,7 @@ class AppContainer extends React.Component {
1820

1921
state = {
2022
error: null,
23+
errorInfo: null,
2124
// eslint-disable-next-line react/no-unused-state
2225
generation: 0,
2326
}
@@ -33,16 +36,27 @@ class AppContainer extends React.Component {
3336
return true
3437
}
3538

36-
componentDidCatch(error) {
39+
componentDidCatch(error, errorInfo) {
3740
logger.error(error)
38-
this.setState({ error })
41+
const { errorReporter = configuration.errorReporter } = this.props
42+
if (!errorReporter) {
43+
logException(error, errorInfo)
44+
}
45+
this.setState({
46+
error,
47+
errorInfo,
48+
})
3949
}
4050

4151
render() {
42-
const { error } = this.state
52+
const { error, errorInfo } = this.state
53+
54+
const {
55+
errorReporter: ErrorReporter = configuration.errorReporter,
56+
} = this.props
4357

44-
if (this.props.errorReporter && error) {
45-
return <this.props.errorReporter error={error} />
58+
if (ErrorReporter && error) {
59+
return <ErrorReporter error={error} errorInfo={errorInfo} />
4660
}
4761

4862
if (this.hotComponentUpdate) {

Diff for: src/configuration.js

+3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ const configuration = {
2828

2929
// flag to completely disable RHL for Components
3030
ignoreComponents: false,
31+
32+
// default value for AppContainer errorOverlay
33+
errorReporter: undefined,
3134
}
3235

3336
export default configuration

Diff for: src/global/modules.js

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logger from '../logger'
22

33
const openedModules = {}
4+
export let lastModuleOpened = ''
45

56
const hotModules = {}
67

@@ -17,6 +18,7 @@ export const isOpened = sourceModule =>
1718
sourceModule && !!openedModules[sourceModule.id]
1819

1920
export const enter = sourceModule => {
21+
lastModuleOpened = sourceModule.id
2022
if (sourceModule && sourceModule.id) {
2123
openedModules[sourceModule.id] = true
2224
} else {

Diff for: src/hot.dev.js

+25-3
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,31 @@ import hoistNonReactStatic from 'hoist-non-react-statics'
33
import { getComponentDisplayName } from './internal/reactUtils'
44
import AppContainer from './AppContainer.dev'
55
import reactHotLoader from './reactHotLoader'
6-
import { isOpened as isModuleOpened, hotModule } from './global/modules'
6+
import {
7+
isOpened as isModuleOpened,
8+
hotModule,
9+
lastModuleOpened,
10+
} from './global/modules'
711
import logger from './logger'
12+
import { clearExceptions, logException } from './errorReporter'
813

914
/* eslint-disable camelcase, no-undef */
1015
const requireIndirect =
1116
typeof __webpack_require__ !== 'undefined' ? __webpack_require__ : require
1217
/* eslint-enable */
1318

19+
const chargeFailbackTimer = id =>
20+
setTimeout(() => {
21+
logger.error(
22+
`hot update failed for module "${id}". Last file processed: "${lastModuleOpened}".`,
23+
)
24+
logException({
25+
toString: () => `hot update failed for module "${id}"`,
26+
})
27+
}, 0)
28+
29+
const clearFailbackTimer = timerId => clearTimeout(timerId)
30+
1431
const createHoc = (SourceComponent, TargetComponent) => {
1532
hoistNonReactStatic(TargetComponent, SourceComponent)
1633
TargetComponent.displayName = `HotExported${getComponentDisplayName(
@@ -34,15 +51,16 @@ const makeHotExport = sourceModule => {
3451
}
3552

3653
if (sourceModule.hot) {
37-
// Mark as self-accepted for Webpack
38-
// Update instances for Parcel
54+
// Mark as self-accepted for Webpack (callback is an Error Handler)
55+
// Update instances for Parcel (callback is an Accept Handler)
3956
sourceModule.hot.accept(updateInstances)
4057

4158
// Webpack way
4259
if (sourceModule.hot.addStatusHandler) {
4360
if (sourceModule.hot.status() === 'idle') {
4461
sourceModule.hot.addStatusHandler(status => {
4562
if (status === 'apply') {
63+
clearExceptions()
4664
updateInstances()
4765
}
4866
})
@@ -62,9 +80,13 @@ const hot = sourceModule => {
6280
const module = hotModule(moduleId)
6381
makeHotExport(sourceModule)
6482

83+
clearExceptions()
84+
const failbackTimer = chargeFailbackTimer(sourceModule.id)
85+
6586
// TODO: Ensure that all exports from this file are react components.
6687

6788
return WrappedComponent => {
89+
clearFailbackTimer(failbackTimer)
6890
// register proxy for wrapped component
6991
reactHotLoader.register(
7092
WrappedComponent,

Diff for: src/reactHotLoader.js

+7
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,13 @@ const reactHotLoader = {
233233
configuration.disableHotRendererWhenInjected
234234

235235
reactHotLoader.IS_REACT_MERGE_ENABLED = true
236+
console.info(
237+
'React-Hot-Loader: react-🔥-dom patch detected. You may use all the features.',
238+
)
239+
} else {
240+
console.warn(
241+
'React-Hot-Loader: react-🔥-dom patch is not detected. React 16.6+ features may not work.',
242+
)
236243
}
237244
if (!React.createElement.isPatchedByReactHotLoader) {
238245
const originalCreateElement = React.createElement

Diff for: yarn.lock

+36-10
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,19 @@
106106
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.53.tgz#396b35af826fa66aad472c8cb7b8d5e277f4e6d8"
107107
integrity sha512-54Dm6NwYeiSQmRB1BLXKr5GELi0wFapR1npi8bnZhEcu84d/yQKqnwwXQ56hZ0RUbTG6L5nqDZaN3dgByQXQRQ==
108108

109+
"@types/prop-types@*":
110+
version "15.5.7"
111+
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.7.tgz#c6f1e0d0109ff358b132d98b7b4025c7a7b707c5"
112+
integrity sha512-a6WH0fXkgPNiGIuLjjdpf0n/GnmgWZ4vLuVIJJnDwhmRDPEaiRBcy5ofQPh+EJFua0S1QWmk1745+JqZQGnJ8Q==
113+
114+
"@types/react@^16.7.13":
115+
version "16.7.13"
116+
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.7.13.tgz#d2369ae78377356d42fb54275d30218e84f2247a"
117+
integrity sha512-WhqrQLAE9z65hfcvWqZfR6qUtIazFRyb8LXqHo8440R53dAQqNkt2OlVJ3FXwqOwAXXg4nfYxt0qgBvE18o5XA==
118+
dependencies:
119+
"@types/prop-types" "*"
120+
csstype "^2.2.0"
121+
109122
JSONStream@^1.0.4:
110123
version "1.3.1"
111124
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.1.tgz#707f761e01dae9e16f1bcf93703b78c70966579a"
@@ -1947,6 +1960,11 @@ [email protected], "cssom@>= 0.3.2 < 0.4.0":
19471960
dependencies:
19481961
cssom "0.3.x"
19491962

1963+
csstype@^2.2.0:
1964+
version "2.5.8"
1965+
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.5.8.tgz#4ce5aa16ea0d562ef9105fa3ae2676f199586a35"
1966+
integrity sha512-r4DbsyNJ7slwBSKoGesxDubRWJ71ghG8W2+1HcsDlAo12KGca9dDLv0u98tfdFw7ldBdoA7XmCnI6Q8LpAJXaQ==
1967+
19501968
currently-unhandled@^0.4.1:
19511969
version "0.4.1"
19521970
resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
@@ -5553,15 +5571,15 @@ rc@^1.1.7:
55535571
minimist "^1.2.0"
55545572
strip-json-comments "~2.0.1"
55555573

5556-
react-dom@16:
5557-
version "16.6.0"
5558-
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.6.0.tgz#6375b8391e019a632a89a0988bce85f0cc87a92f"
5559-
integrity sha512-Stm2D9dXEUUAQdvpvhvFj/DEXwC2PAL/RwEMhoN4dvvD2ikTlJegEXf97xryg88VIAU22ZAP7n842l+9BTz6+w==
5574+
react-dom@^16.6.3:
5575+
version "16.6.3"
5576+
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.6.3.tgz#8fa7ba6883c85211b8da2d0efeffc9d3825cccc0"
5577+
integrity sha512-8ugJWRCWLGXy+7PmNh8WJz3g1TaTUt1XyoIcFN+x0Zbkoz+KKdUyx1AQLYJdbFXjuF41Nmjn5+j//rxvhFjgSQ==
55605578
dependencies:
55615579
loose-envify "^1.1.0"
55625580
object-assign "^4.1.1"
55635581
prop-types "^15.6.2"
5564-
scheduler "^0.10.0"
5582+
scheduler "^0.11.2"
55655583

55665584
react-is@^16.5.2, react-is@^16.6.0:
55675585
version "16.6.0"
@@ -5602,15 +5620,15 @@ react-test-renderer@^16.0.0-0:
56025620
object-assign "^4.1.1"
56035621
prop-types "^15.6.0"
56045622

5605-
react@16:
5606-
version "16.6.0"
5607-
resolved "https://registry.yarnpkg.com/react/-/react-16.6.0.tgz#b34761cfaf3e30f5508bc732fb4736730b7da246"
5608-
integrity sha512-zJPnx/jKtuOEXCbQ9BKaxDMxR0001/hzxXwYxG8septeyYGfsgAei6NgfbVgOhbY1WOP2o3VPs/E9HaN+9hV3Q==
5623+
react@^16.6.3:
5624+
version "16.6.3"
5625+
resolved "https://registry.yarnpkg.com/react/-/react-16.6.3.tgz#25d77c91911d6bbdd23db41e70fb094cc1e0871c"
5626+
integrity sha512-zCvmH2vbEolgKxtqXL2wmGCUxUyNheYn/C+PD1YAjfxHC54+MhdruyhO7QieQrYsYeTxrn93PM2y0jRH1zEExw==
56095627
dependencies:
56105628
loose-envify "^1.1.0"
56115629
object-assign "^4.1.1"
56125630
prop-types "^15.6.2"
5613-
scheduler "^0.10.0"
5631+
scheduler "^0.11.2"
56145632

56155633
read-pkg-up@^1.0.1:
56165634
version "1.0.1"
@@ -6161,6 +6179,14 @@ scheduler@^0.10.0:
61616179
loose-envify "^1.1.0"
61626180
object-assign "^4.1.1"
61636181

6182+
scheduler@^0.11.2:
6183+
version "0.11.3"
6184+
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.11.3.tgz#b5769b90cf8b1464f3f3cfcafe8e3cd7555a2d6b"
6185+
integrity sha512-i9X9VRRVZDd3xZw10NY5Z2cVMbdYg6gqFecfj79USv1CFN+YrJ3gIPRKf1qlY+Sxly4djoKdfx1T+m9dnRB8kQ==
6186+
dependencies:
6187+
loose-envify "^1.1.0"
6188+
object-assign "^4.1.1"
6189+
61646190
semver-compare@^1.0.0:
61656191
version "1.0.0"
61666192
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"

0 commit comments

Comments
 (0)