Skip to content

Commit

Permalink
feat: new root/hot for better error management. Fixes #1078, #1111
Browse files Browse the repository at this point in the history
  • Loading branch information
theKashey committed Dec 11, 2018
1 parent 7a421a1 commit 3029428
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 24 deletions.
44 changes: 40 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ npm install react-hot-loader

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

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

Expand Down Expand Up @@ -60,11 +61,17 @@ setConfig({

```js
// App.js
import React from 'react'
import { hot } from 'react-hot-loader'

import { hot } from 'react-hot-loader/root'
const App = () => <div>Hello World!</div>
export default hot(App)
```

There is also another version of `hot`, used prior version 4.5.4. Please use a new one,
as long is it much more resilient to js errors you may make during development.

```js
import { hot } from 'react-hot-loader'
const App = () => <div>Hello World!</div>
export default hot(module)(App)
```

Expand Down Expand Up @@ -241,6 +248,35 @@ module.exports = {
}
```

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).

## React-🔥-Dom

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)

It is the same React-Dom, with the same version, just with our patches already landed inside.

There is 2 ways to install it:

* Use **yarn** name resolution, so `@hot-loader/react-dom` would be installed instead of `react-dom`

```
yarn add @hot-loader/react-dom@npm:react-dom
```

* Use webpack **aliases**

```js
// webpack.conf
...
resolve: {
alias: {
'react-dom': '@hot-loader/react-dom'
}
}
...
```

### Code Splitting

If you want to use Code Splitting + React Hot Loader, the simplest solution is to pick one of our compatible library:
Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "4.5.3",
"description": "Tweak React components in real time.",
"main": "index.js",
"types": "react-hot-loader.d.ts",
"types": "index.d.ts",
"homepage": "https://github.com/gaearon/react-hot-loader",
"repository": "https://github.com/gaearon/react-hot-loader/",
"license": "MIT",
Expand Down Expand Up @@ -33,7 +33,8 @@
"babel.js",
"webpack.js",
"patch.js",
"react-hot-loader.d.ts"
"root.js",
"*.d.ts"
],
"sideEffects": false,
"keywords": [
Expand All @@ -49,6 +50,7 @@
"reload"
],
"devDependencies": {
"@types/react": "^16.7.13",
"babel-cli": "^6.7.5",
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.3",
Expand Down
24 changes: 19 additions & 5 deletions src/AppContainer.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import PropTypes from 'prop-types'
import defaultPolyfill, { polyfill } from 'react-lifecycles-compat'
import logger from './logger'
import { get as getGeneration } from './global/generation'
import configuration from './configuration'
import { logException } from './errorReporter'

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

state = {
error: null,
errorInfo: null,
// eslint-disable-next-line react/no-unused-state
generation: 0,
}
Expand All @@ -33,16 +36,27 @@ class AppContainer extends React.Component {
return true
}

componentDidCatch(error) {
componentDidCatch(error, errorInfo) {
logger.error(error)
this.setState({ error })
const { errorReporter = configuration.errorReporter } = this.props
if (!errorReporter) {
logException(error, errorInfo)
}
this.setState({
error,
errorInfo,
})
}

render() {
const { error } = this.state
const { error, errorInfo } = this.state

const {
errorReporter: ErrorReporter = configuration.errorReporter,
} = this.props

if (this.props.errorReporter && error) {
return <this.props.errorReporter error={error} />
if (ErrorReporter && error) {
return <ErrorReporter error={error} errorInfo={errorInfo} />
}

if (this.hotComponentUpdate) {
Expand Down
3 changes: 3 additions & 0 deletions src/configuration.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ const configuration = {

// flag to completely disable RHL for Components
ignoreComponents: false,

// default value for AppContainer errorOverlay
errorReporter: undefined,
}

export default configuration
2 changes: 2 additions & 0 deletions src/global/modules.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logger from '../logger'

const openedModules = {}
export let lastModuleOpened = ''

const hotModules = {}

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

export const enter = sourceModule => {
lastModuleOpened = sourceModule.id
if (sourceModule && sourceModule.id) {
openedModules[sourceModule.id] = true
} else {
Expand Down
28 changes: 25 additions & 3 deletions src/hot.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,31 @@ import hoistNonReactStatic from 'hoist-non-react-statics'
import { getComponentDisplayName } from './internal/reactUtils'
import AppContainer from './AppContainer.dev'
import reactHotLoader from './reactHotLoader'
import { isOpened as isModuleOpened, hotModule } from './global/modules'
import {
isOpened as isModuleOpened,
hotModule,
lastModuleOpened,
} from './global/modules'
import logger from './logger'
import { clearExceptions, logException } from './errorReporter'

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

const chargeFailbackTimer = id =>
setTimeout(() => {
logger.error(
`hot update failed for module "${id}". Last file processed: "${lastModuleOpened}".`,
)
logException({
toString: () => `hot update failed for module "${id}"`,
})
}, 0)

const clearFailbackTimer = timerId => clearTimeout(timerId)

const createHoc = (SourceComponent, TargetComponent) => {
hoistNonReactStatic(TargetComponent, SourceComponent)
TargetComponent.displayName = `HotExported${getComponentDisplayName(
Expand All @@ -34,15 +51,16 @@ const makeHotExport = sourceModule => {
}

if (sourceModule.hot) {
// Mark as self-accepted for Webpack
// Update instances for Parcel
// Mark as self-accepted for Webpack (callback is an Error Handler)
// Update instances for Parcel (callback is an Accept Handler)
sourceModule.hot.accept(updateInstances)

// Webpack way
if (sourceModule.hot.addStatusHandler) {
if (sourceModule.hot.status() === 'idle') {
sourceModule.hot.addStatusHandler(status => {
if (status === 'apply') {
clearExceptions()
updateInstances()
}
})
Expand All @@ -62,9 +80,13 @@ const hot = sourceModule => {
const module = hotModule(moduleId)
makeHotExport(sourceModule)

clearExceptions()
const failbackTimer = chargeFailbackTimer(sourceModule.id)

// TODO: Ensure that all exports from this file are react components.

return WrappedComponent => {
clearFailbackTimer(failbackTimer)
// register proxy for wrapped component
reactHotLoader.register(
WrappedComponent,
Expand Down
7 changes: 7 additions & 0 deletions src/reactHotLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,13 @@ const reactHotLoader = {
configuration.disableHotRendererWhenInjected

reactHotLoader.IS_REACT_MERGE_ENABLED = true
console.info(
'React-Hot-Loader: react-🔥-dom patch detected. You may use all the features.',
)
} else {
console.warn(
'React-Hot-Loader: react-🔥-dom patch is not detected. React 16.6+ features may not work.',
)
}
if (!React.createElement.isPatchedByReactHotLoader) {
const originalCreateElement = React.createElement
Expand Down
46 changes: 36 additions & 10 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,19 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.53.tgz#396b35af826fa66aad472c8cb7b8d5e277f4e6d8"
integrity sha512-54Dm6NwYeiSQmRB1BLXKr5GELi0wFapR1npi8bnZhEcu84d/yQKqnwwXQ56hZ0RUbTG6L5nqDZaN3dgByQXQRQ==

"@types/prop-types@*":
version "15.5.7"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.7.tgz#c6f1e0d0109ff358b132d98b7b4025c7a7b707c5"
integrity sha512-a6WH0fXkgPNiGIuLjjdpf0n/GnmgWZ4vLuVIJJnDwhmRDPEaiRBcy5ofQPh+EJFua0S1QWmk1745+JqZQGnJ8Q==

"@types/react@^16.7.13":
version "16.7.13"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.7.13.tgz#d2369ae78377356d42fb54275d30218e84f2247a"
integrity sha512-WhqrQLAE9z65hfcvWqZfR6qUtIazFRyb8LXqHo8440R53dAQqNkt2OlVJ3FXwqOwAXXg4nfYxt0qgBvE18o5XA==
dependencies:
"@types/prop-types" "*"
csstype "^2.2.0"

JSONStream@^1.0.4:
version "1.3.1"
resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.1.tgz#707f761e01dae9e16f1bcf93703b78c70966579a"
Expand Down Expand Up @@ -1947,6 +1960,11 @@ cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0":
dependencies:
cssom "0.3.x"

csstype@^2.2.0:
version "2.5.8"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.5.8.tgz#4ce5aa16ea0d562ef9105fa3ae2676f199586a35"
integrity sha512-r4DbsyNJ7slwBSKoGesxDubRWJ71ghG8W2+1HcsDlAo12KGca9dDLv0u98tfdFw7ldBdoA7XmCnI6Q8LpAJXaQ==

currently-unhandled@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
Expand Down Expand Up @@ -5553,15 +5571,15 @@ rc@^1.1.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"

react-dom@16:
version "16.6.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.6.0.tgz#6375b8391e019a632a89a0988bce85f0cc87a92f"
integrity sha512-Stm2D9dXEUUAQdvpvhvFj/DEXwC2PAL/RwEMhoN4dvvD2ikTlJegEXf97xryg88VIAU22ZAP7n842l+9BTz6+w==
react-dom@^16.6.3:
version "16.6.3"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.6.3.tgz#8fa7ba6883c85211b8da2d0efeffc9d3825cccc0"
integrity sha512-8ugJWRCWLGXy+7PmNh8WJz3g1TaTUt1XyoIcFN+x0Zbkoz+KKdUyx1AQLYJdbFXjuF41Nmjn5+j//rxvhFjgSQ==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
scheduler "^0.10.0"
scheduler "^0.11.2"

react-is@^16.5.2, react-is@^16.6.0:
version "16.6.0"
Expand Down Expand Up @@ -5602,15 +5620,15 @@ react-test-renderer@^16.0.0-0:
object-assign "^4.1.1"
prop-types "^15.6.0"

react@16:
version "16.6.0"
resolved "https://registry.yarnpkg.com/react/-/react-16.6.0.tgz#b34761cfaf3e30f5508bc732fb4736730b7da246"
integrity sha512-zJPnx/jKtuOEXCbQ9BKaxDMxR0001/hzxXwYxG8septeyYGfsgAei6NgfbVgOhbY1WOP2o3VPs/E9HaN+9hV3Q==
react@^16.6.3:
version "16.6.3"
resolved "https://registry.yarnpkg.com/react/-/react-16.6.3.tgz#25d77c91911d6bbdd23db41e70fb094cc1e0871c"
integrity sha512-zCvmH2vbEolgKxtqXL2wmGCUxUyNheYn/C+PD1YAjfxHC54+MhdruyhO7QieQrYsYeTxrn93PM2y0jRH1zEExw==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.2"
scheduler "^0.10.0"
scheduler "^0.11.2"

read-pkg-up@^1.0.1:
version "1.0.1"
Expand Down Expand Up @@ -6161,6 +6179,14 @@ scheduler@^0.10.0:
loose-envify "^1.1.0"
object-assign "^4.1.1"

scheduler@^0.11.2:
version "0.11.3"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.11.3.tgz#b5769b90cf8b1464f3f3cfcafe8e3cd7555a2d6b"
integrity sha512-i9X9VRRVZDd3xZw10NY5Z2cVMbdYg6gqFecfj79USv1CFN+YrJ3gIPRKf1qlY+Sxly4djoKdfx1T+m9dnRB8kQ==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"

semver-compare@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
Expand Down

0 comments on commit 3029428

Please sign in to comment.