Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove react-hot-loader #4500

Merged
merged 12 commits into from May 31, 2018
3 changes: 2 additions & 1 deletion .flowconfig
@@ -1,2 +1,3 @@
[ignore]
<PROJECT_ROOT>/examples/.*
<PROJECT_ROOT>/examples/.*
<PROJECT_ROOT>/.*.json
2 changes: 1 addition & 1 deletion appveyor.yml
Expand Up @@ -7,7 +7,7 @@ install:
# Install Google Chrome for e2e testing
- choco install --ignore-checksums googlechrome
# Get the latest stable version of Node.js or io.js
- ps: Install-Product node $env:nodejs_version
- ps: Install-Product node $env:nodejs_version x64
# install modules
- npm install

Expand Down
41 changes: 41 additions & 0 deletions client/dev-error-overlay.js
@@ -0,0 +1,41 @@
// @flow
import React from 'react'
import {applySourcemaps} from './source-map-support'
import ErrorDebug, {styles} from '../lib/error-debug'
import type {RuntimeError, ErrorReporterProps} from './error-boundary'

type State = {|
mappedError: null | RuntimeError
|}

// This component is only used in development, sourcemaps are applied on the fly because componentDidCatch is not async
class DevErrorOverlay extends React.Component<ErrorReporterProps, State> {
state = {
mappedError: null
}

componentDidMount () {
const {error} = this.props

// Since componentDidMount doesn't handle errors we use then/catch here
applySourcemaps(error).then(() => {
this.setState({mappedError: error})
}).catch((caughtError) => {
this.setState({mappedError: caughtError})
})
}

render () {
const {mappedError} = this.state
const {info} = this.props
if (mappedError === null) {
return <div style={styles.errorDebug}>
<h1 style={styles.heading}>Loading stacktrace...</h1>
</div>
}

return <ErrorDebug error={mappedError} info={info} />
}
}

export default DevErrorOverlay
66 changes: 66 additions & 0 deletions client/error-boundary.js
@@ -0,0 +1,66 @@
// @flow
import * as React from 'react'
import {polyfill} from 'react-lifecycles-compat'

type ComponentDidCatchInfo = {
componentStack: string
}

export type Info = null | ComponentDidCatchInfo

export type RuntimeError = Error & {|
module: ?{|
rawRequest: string
|}
|}

export type ErrorReporterProps = {|error: RuntimeError, info: Info|}
type ErrorReporterComponent = React.ComponentType<ErrorReporterProps>

type Props = {|
ErrorReporter: null | ErrorReporterComponent,
onError: (error: RuntimeError, info: ComponentDidCatchInfo) => void,
children: React.ComponentType<*>
|}

type State = {|
error: null | RuntimeError,
info: Info
|}

class ErrorBoundary extends React.Component<Props, State> {
state = {
error: null,
info: null
}
static getDerivedStateFromProps () {
return {
error: null,
info: null
}
}
componentDidCatch (error: RuntimeError, info: ComponentDidCatchInfo) {
const {onError} = this.props

// onError is provided in production
if (onError) {
onError(error, info)
} else {
this.setState({ error, info })
}
}
render () {
const {ErrorReporter, children} = this.props
const {error, info} = this.state
if (ErrorReporter && error) {
return <ErrorReporter error={error} info={info} />
}

return React.Children.only(children)
}
}

// Makes sure we can use React 16.3 lifecycles and still support older versions of React.
polyfill(ErrorBoundary)

export default ErrorBoundary
48 changes: 28 additions & 20 deletions client/index.js
Expand Up @@ -7,6 +7,7 @@ import { loadGetInitialProps, getURL } from '../lib/utils'
import PageLoader from '../lib/page-loader'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

possible to // @flow this or should we do that in a later PR (i.e. PR just to flowtype more things)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I didn't on purpose, carefully chose which files to start with 👍

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

import * as asset from '../lib/asset'
import * as envConfig from '../lib/runtime-config'
import ErrorBoundary from './error-boundary'

// Polyfill Promise globally
// This is needed because Webpack2's dynamic loading(common chunks) code
Expand Down Expand Up @@ -66,8 +67,7 @@ const errorContainer = document.getElementById('__next-error')
let lastAppProps
export let router
export let ErrorComponent
let HotAppContainer
let ErrorDebugComponent
let DevErrorOverlay
let Component
let App
let stripAnsi = (s) => s
Expand All @@ -76,8 +76,7 @@ let applySourcemaps = (e) => e
export const emitter = new EventEmitter()

export default async ({
HotAppContainer: passedHotAppContainer,
ErrorDebugComponent: passedDebugComponent,
DevErrorOverlay: passedDevErrorOverlay,
stripAnsi: passedStripAnsi,
applySourcemaps: passedApplySourcemaps
} = {}) => {
Expand All @@ -88,8 +87,7 @@ export default async ({

stripAnsi = passedStripAnsi || stripAnsi
applySourcemaps = passedApplySourcemaps || applySourcemaps
HotAppContainer = passedHotAppContainer
ErrorDebugComponent = passedDebugComponent
DevErrorOverlay = passedDevErrorOverlay
ErrorComponent = await pageLoader.loadPage('/_error')
App = await pageLoader.loadPage('/_app')

Expand All @@ -115,12 +113,12 @@ export default async ({
err: initialErr
})

router.subscribe(({ Component, props, hash, err }) => {
render({ Component, props, err, hash, emitter })
router.subscribe(({ App, Component, props, hash, err }) => {
render({ App, Component, props, err, hash, emitter })
})

const hash = location.hash.substring(1)
render({ Component, props, hash, err: initialErr, emitter })
render({ App, Component, props, hash, err: initialErr, emitter })

return emitter
}
Expand All @@ -143,14 +141,14 @@ export async function render (props) {
// 404 and 500 errors are special kind of errors
// and they are still handle via the main render method.
export async function renderError (props) {
const {err} = props
const {err, errorInfo} = props

// In development we apply sourcemaps to the error
if (process.env.NODE_ENV !== 'production') {
await applySourcemaps(err)
}

const str = stripAnsi(`${err.message}\n${err.stack}${err.info ? `\n\n${err.info.componentStack}` : ''}`)
const str = stripAnsi(`${err.message}\n${err.stack}${errorInfo ? `\n\n${errorInfo.componentStack}` : ''}`)
console.error(str)

if (process.env.NODE_ENV !== 'production') {
Expand All @@ -159,7 +157,7 @@ export async function renderError (props) {
// Otherwise, we need to face issues when the issue is fixed and
// it's get notified via HMR
ReactDOM.unmountComponentAtNode(appContainer)
renderReactElement(<ErrorDebugComponent error={err} />, errorContainer)
renderReactElement(<DevErrorOverlay error={err} />, errorContainer)
return
}

Expand All @@ -168,7 +166,7 @@ export async function renderError (props) {
await doRender({...props, err, Component: ErrorComponent})
}

async function doRender ({ Component, props, hash, err, emitter: emitterProp = emitter }) {
async function doRender ({ App, Component, props, hash, err, emitter: emitterProp = emitter }) {
// Usual getInitialProps fetching is handled in next/router
// this is for when ErrorComponent gets replaced by Component by HMR
if (!props && Component &&
Expand All @@ -190,15 +188,25 @@ async function doRender ({ Component, props, hash, err, emitter: emitterProp = e
// We need to clear any existing runtime error messages
ReactDOM.unmountComponentAtNode(errorContainer)

// In development we render react-hot-loader's wrapper component
if (HotAppContainer) {
renderReactElement(<HotAppContainer errorReporter={ErrorDebugComponent} warnings={false}>
<App {...appProps} />
</HotAppContainer>, appContainer)
} else {
renderReactElement(<App {...appProps} />, appContainer)
let onError = null

if (process.env.NODE_ENV !== 'development') {
onError = async (error, errorInfo) => {
try {
await renderError({App, err: error, errorInfo})
} catch (err) {
console.error('Error while rendering error page: ', err)
}
}
}

// In development we render a wrapper component that catches runtime errors.
renderReactElement((
<ErrorBoundary ErrorReporter={DevErrorOverlay} onError={onError}>
<App {...appProps} />
</ErrorBoundary>
), appContainer)

emitterProp.emit('after-reactdom-render', { Component, ErrorComponent, appProps })
}

Expand Down
5 changes: 2 additions & 3 deletions client/next-dev.js
@@ -1,14 +1,13 @@
import stripAnsi from 'strip-ansi'
import initNext, * as next from './'
import {ClientDebug} from '../lib/error-debug'
import DevErrorOverlay from './dev-error-overlay'
import initOnDemandEntries from './on-demand-entries-client'
import initWebpackHMR from './webpack-hot-middleware-client'
import {AppContainer as HotAppContainer} from 'react-hot-loader'
import {applySourcemaps} from './source-map-support'

window.next = next

initNext({ HotAppContainer, ErrorDebugComponent: ClientDebug, applySourcemaps, stripAnsi })
initNext({ DevErrorOverlay, applySourcemaps, stripAnsi })
.then((emitter) => {
initOnDemandEntries()
initWebpackHMR()
Expand Down
53 changes: 53 additions & 0 deletions flow-typed/npm/react-lifecycles-compat_vx.x.x.js
@@ -0,0 +1,53 @@
// flow-typed signature: 729d832efcac0a21ab881042caf78e1e
// flow-typed version: <<STUB>>/react-lifecycles-compat_v3.0.4/flow_v0.73.0

/**
* This is an autogenerated libdef stub for:
*
* 'react-lifecycles-compat'
*
* Fill this stub out by replacing all the `any` types.
*
* Once filled out, we encourage you to share your work with the
* community by sending a pull request to:
* https://github.com/flowtype/flow-typed
*/

declare module 'react-lifecycles-compat' {
declare module.exports: any;
}

/**
* We include stubs for each file inside this npm package in case you need to
* require those files directly. Feel free to delete any files that aren't
* needed.
*/
declare module 'react-lifecycles-compat/react-lifecycles-compat.cjs' {
declare module.exports: any;
}

declare module 'react-lifecycles-compat/react-lifecycles-compat.es' {
declare module.exports: any;
}

declare module 'react-lifecycles-compat/react-lifecycles-compat' {
declare module.exports: any;
}

declare module 'react-lifecycles-compat/react-lifecycles-compat.min' {
declare module.exports: any;
}

// Filename aliases
declare module 'react-lifecycles-compat/react-lifecycles-compat.cjs.js' {
declare module.exports: $Exports<'react-lifecycles-compat/react-lifecycles-compat.cjs'>;
}
declare module 'react-lifecycles-compat/react-lifecycles-compat.es.js' {
declare module.exports: $Exports<'react-lifecycles-compat/react-lifecycles-compat.es'>;
}
declare module 'react-lifecycles-compat/react-lifecycles-compat.js' {
declare module.exports: $Exports<'react-lifecycles-compat/react-lifecycles-compat'>;
}
declare module 'react-lifecycles-compat/react-lifecycles-compat.min.js' {
declare module.exports: $Exports<'react-lifecycles-compat/react-lifecycles-compat.min'>;
}