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 full reload overlay and warn in CLI instead #37874

Merged
merged 10 commits into from
Jun 22, 2022
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 24 additions & 26 deletions packages/next/client/dev/error-overlay/hot-dev-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ import {
onBuildError,
onBuildOk,
onRefresh,
onFullRefreshNeeded,
} from 'next/dist/compiled/@next/react-dev-overlay/dist/client'
import stripAnsi from 'next/dist/compiled/strip-ansi'
import { addMessageListener, sendMessage } from './websocket'
import formatWebpackMessages from './format-webpack-messages'
import sendClientErrorToDevServer from '../send-client-error-to-dev-server'

// This alternative WebpackDevServer combines the functionality of:
// https://github.com/webpack/webpack-dev-server/blob/webpack-1/client/index.js
Expand Down Expand Up @@ -314,15 +314,23 @@ function tryApplyUpdates(onHotUpdateSuccess) {
function handleApplyUpdates(err, updatedModules) {
if (err || hadRuntimeError || !updatedModules) {
if (err) {
performFullRefresh(err)
console.warn(
'[Fast Refresh] performing full reload\n\n' +
"Fast Refresh will perform a full reload when you edit a file that's imported by modules outside of the React rendering tree.\n" +
'You might have a file which exports a React component but also exports a value that is imported by a non-React component file.\n' +
'Consider migrating the non-React component export to a separate file and importing it into both files.\n\n' +
'It is also possible the parent component of the component you edited is a class component, which disables Fast Refresh.\n' +
'Fast Refresh requires at least one parent function component in your React tree.'
)
} else if (hadRuntimeError) {
performFullRefresh()
console.warn(
'[Fast Refresh] performing full reload because your application had an unrecoverable error'
)
}
performFullRefresh(err)
return
}

clearFullRefreshStorage()

const hasUpdates = Boolean(updatedModules.length)
if (typeof onHotUpdateSuccess === 'function') {
// Maybe we want to do something.
Expand Down Expand Up @@ -356,27 +364,17 @@ function tryApplyUpdates(onHotUpdateSuccess) {
)
}

const FULL_REFRESH_STORAGE_KEY = '_has_warned_about_full_refresh'

function performFullRefresh(err) {
if (!hasAlreadyWarnedAboutFullRefresh()) {
sessionStorage.setItem(FULL_REFRESH_STORAGE_KEY, 'true')
const reason =
err &&
((err.stack && err.stack.split('\n').slice(0, 5).join('\n')) ||
err.message ||
err + '')
onFullRefreshNeeded(reason)
} else {
const stackTrace =
err &&
((err.stack && err.stack.split('\n').slice(0, 5).join('\n')) ||
err.message ||
err + '')

sendClientErrorToDevServer(
'Fast Refresh had to perform a full reload. Read more: https://nextjs.org/docs/basic-features/fast-refresh#how-it-works',
stackTrace
).finally(() => {
window.location.reload()
}
}

function hasAlreadyWarnedAboutFullRefresh() {
return sessionStorage.getItem(FULL_REFRESH_STORAGE_KEY) !== null
}

function clearFullRefreshStorage() {
if (sessionStorage.getItem(FULL_REFRESH_STORAGE_KEY) !== 'ignore')
sessionStorage.removeItem(FULL_REFRESH_STORAGE_KEY)
})
}
13 changes: 13 additions & 0 deletions packages/next/client/dev/send-client-error-to-dev-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export default function sendClientErrorToDevServer(
hanneslund marked this conversation as resolved.
Show resolved Hide resolved
message: string,
stackTrace: string
) {
return fetch('/_next/client-error', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
message,
stackTrace,
}),
}).catch(() => {})
}
4 changes: 4 additions & 0 deletions packages/next/server/base-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
protected abstract getBuildId(): string
protected abstract generatePublicRoutes(): Route[]
protected abstract generateImageRoutes(): Route[]
protected abstract generateClientErrorRoute(): Route | undefined
protected abstract generateStaticRoutes(): Route[]
protected abstract generateFsStaticRoutes(): Route[]
protected abstract generateCatchAllMiddlewareRoute(): Route | undefined
Expand Down Expand Up @@ -719,10 +720,12 @@ export default abstract class Server<ServerOptions extends Options = Options> {
useFileSystemPublicRoutes: boolean
dynamicRoutes: DynamicRoutes | undefined
nextConfig: NextConfig
clientErrorRoute: Route | undefined
} {
const publicRoutes = this.generatePublicRoutes()
const imageRoutes = this.generateImageRoutes()
const staticFilesRoutes = this.generateStaticRoutes()
const clientErrorRoute = this.generateClientErrorRoute()

const fsRoutes: Route[] = [
...this.generateFsStaticRoutes(),
Expand Down Expand Up @@ -901,6 +904,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
dynamicRoutes: this.dynamicRoutes,
pageChecker: this.hasPage.bind(this),
nextConfig: this.nextConfig,
clientErrorRoute,
}
}

Expand Down
26 changes: 25 additions & 1 deletion packages/next/server/dev/next-dev-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import Server, { WrappedBuildError } from '../next-server'
import { getRouteMatcher } from '../../shared/lib/router/utils/route-matcher'
import { normalizePagePath } from '../../shared/lib/page-path/normalize-page-path'
import { absolutePathToPage } from '../../shared/lib/page-path/absolute-path-to-page'
import Router from '../router'
import Router, { Route } from '../router'
import { getPathMatch } from '../../shared/lib/router/utils/path-match'
import { pathHasPrefix } from '../../shared/lib/router/utils/path-has-prefix'
import { removePathPrefix } from '../../shared/lib/router/utils/remove-path-prefix'
Expand Down Expand Up @@ -967,6 +967,30 @@ export default class DevServer extends Server {
return { fsRoutes, ...otherRoutes }
}

protected generateClientErrorRoute(): Route | undefined {
return {
match: getPathMatch('/_next/client-error'),
type: 'route',
name: 'next client error',
fn: async (req, res) => {
if (req.method === 'POST') {
const { message, stackTrace } = await req.parseBody('1mb')
Log.warn(message)
if (stackTrace) {
console.warn(stackTrace)
}
res.statusCode = 200
} else {
res.statusCode = 405
}
res.send()
return {
finished: true,
}
},
}
}

// In development public files are not added to the router but handled as a fallback instead
protected generatePublicRoutes(): never[] {
return []
Expand Down
4 changes: 4 additions & 0 deletions packages/next/server/next-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,10 @@ export default class NextNodeServer extends BaseServer {
]
}

protected generateClientErrorRoute(): Route | undefined {
return undefined
}

protected generateStaticRoutes(): Route[] {
return this.hasStaticDir
? [
Expand Down
5 changes: 5 additions & 0 deletions packages/next/server/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export default class Router {
useFileSystemPublicRoutes: boolean
seenRequests: Set<any>
nextConfig: NextConfig
clientErrorRoute: Route | undefined

constructor({
headers = [],
Expand All @@ -79,6 +80,7 @@ export default class Router {
pageChecker,
useFileSystemPublicRoutes,
nextConfig,
clientErrorRoute,
}: {
headers: Route[]
fsRoutes: Route[]
Expand All @@ -94,6 +96,7 @@ export default class Router {
pageChecker: PageChecker
useFileSystemPublicRoutes: boolean
nextConfig: NextConfig
clientErrorRoute: Route | undefined
}) {
this.nextConfig = nextConfig
this.headers = headers
Expand All @@ -106,6 +109,7 @@ export default class Router {
this.dynamicRoutes = dynamicRoutes
this.useFileSystemPublicRoutes = useFileSystemPublicRoutes
this.seenRequests = new Set()
this.clientErrorRoute = clientErrorRoute
}

get locales() {
Expand Down Expand Up @@ -219,6 +223,7 @@ export default class Router {
*/

const allRoutes = [
...(this.clientErrorRoute ? [this.clientErrorRoute] : []),
...(this.catchAllMiddleware
? this.fsRoutes.filter((r) => r.name === '_next/data catchall')
: []),
Expand Down
4 changes: 4 additions & 0 deletions packages/next/server/web-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { Params } from '../shared/lib/router/utils/route-matcher'
import type { PayloadOptions } from './send-payload'
import type { LoadComponentsReturnType } from './load-components'
import type { Options } from './base-server'
import type { Route } from './router'

import BaseServer from './base-server'
import { renderToHTML } from './render'
Expand Down Expand Up @@ -74,6 +75,9 @@ export default class NextWebServer extends BaseServer<WebServerOptions> {
protected generateImageRoutes() {
return []
}
protected generateClientErrorRoute(): Route | undefined {
return undefined
}
protected generateStaticRoutes() {
return []
}
Expand Down
13 changes: 1 addition & 12 deletions packages/react-dev-overlay/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,22 +85,11 @@ function onBuildError(message: string) {
Bus.emit({ type: Bus.TYPE_BUILD_ERROR, message })
}

function onFullRefreshNeeded(reason?: string) {
Bus.emit({ type: Bus.TYPE_FULL_REFRESH_NEEDED, reason: reason ?? null })
}

function onRefresh() {
Bus.emit({ type: Bus.TYPE_REFRESH })
}

export { getErrorByType } from './internal/helpers/getErrorByType'
export { getServerError } from './internal/helpers/nodeStackFrames'
export { default as ReactDevOverlay } from './internal/ReactDevOverlay'
export {
onBuildOk,
onBuildError,
onFullRefreshNeeded,
register,
unregister,
onRefresh,
}
export { onBuildOk, onBuildError, register, unregister, onRefresh }
35 changes: 4 additions & 31 deletions packages/react-dev-overlay/src/internal/ReactDevOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import * as Bus from './bus'
import { ShadowPortal } from './components/ShadowPortal'
import { BuildError } from './container/BuildError'
import { Errors, SupportedErrorEvent } from './container/Errors'
import { FullRefreshWarning } from './container/FullRefreshWarning'
import { ErrorBoundary } from './ErrorBoundary'
import { Base } from './styles/Base'
import { ComponentStyles } from './styles/ComponentStyles'
Expand All @@ -14,8 +13,6 @@ type OverlayState = {
nextId: number
buildError: string | null
errors: SupportedErrorEvent[]
fullRefreshReason: string | null
isAboutToFullRefresh: boolean
}

function reducer(state: OverlayState, ev: Bus.BusEvent): OverlayState {
Expand All @@ -26,19 +23,6 @@ function reducer(state: OverlayState, ev: Bus.BusEvent): OverlayState {
case Bus.TYPE_BUILD_ERROR: {
return { ...state, buildError: ev.message }
}
case Bus.TYPE_FULL_REFRESH_NEEDED: {
const aboutToRefreshNewState: OverlayState = {
...state,
fullRefreshReason: null,
isAboutToFullRefresh: true,
}

if (ev.reason === null) {
return aboutToRefreshNewState
}

return { ...aboutToRefreshNewState, fullRefreshReason: ev.reason }
}
case Bus.TYPE_REFRESH: {
return { ...state, buildError: null, errors: [] }
}
Expand All @@ -58,7 +42,7 @@ function reducer(state: OverlayState, ev: Bus.BusEvent): OverlayState {
}
}

type ErrorType = 'runtime' | 'build' | 'full-refresh'
type ErrorType = 'runtime' | 'build'

const ReactDevOverlay: React.FunctionComponent = function ReactDevOverlay({
children,
Expand All @@ -73,8 +57,6 @@ const ReactDevOverlay: React.FunctionComponent = function ReactDevOverlay({
nextId: 1,
buildError: null,
errors: [],
fullRefreshReason: null,
isAboutToFullRefresh: false,
})

React.useEffect(() => {
Expand All @@ -93,9 +75,8 @@ const ReactDevOverlay: React.FunctionComponent = function ReactDevOverlay({

const hasBuildError = state.buildError != null
const hasRuntimeErrors = Boolean(state.errors.length)
const isAboutToFullRefresh = state.isAboutToFullRefresh

const isMounted = hasBuildError || hasRuntimeErrors || isAboutToFullRefresh
const isMounted = hasBuildError || hasRuntimeErrors

return (
<React.Fragment>
Expand All @@ -109,17 +90,9 @@ const ReactDevOverlay: React.FunctionComponent = function ReactDevOverlay({
<ComponentStyles />

{shouldPreventDisplay(
hasBuildError
? 'build'
: hasRuntimeErrors
? 'runtime'
: isAboutToFullRefresh
? 'full-refresh'
: null,
hasBuildError ? 'build' : hasRuntimeErrors ? 'runtime' : null,
preventDisplay
) ? null : isAboutToFullRefresh ? (
<FullRefreshWarning reason={state.fullRefreshReason} />
) : hasBuildError ? (
) ? null : hasBuildError ? (
<BuildError message={state.buildError!} />
) : hasRuntimeErrors ? (
<Errors errors={state.errors} />
Expand Down
6 changes: 0 additions & 6 deletions packages/react-dev-overlay/src/internal/bus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { StackFrame } from 'stacktrace-parser'

export const TYPE_BUILD_OK = 'build-ok'
export const TYPE_BUILD_ERROR = 'build-error'
export const TYPE_FULL_REFRESH_NEEDED = 'full-refresh-needed'
export const TYPE_REFRESH = 'fast-refresh'
export const TYPE_UNHANDLED_ERROR = 'unhandled-error'
export const TYPE_UNHANDLED_REJECTION = 'unhandled-rejection'
Expand All @@ -13,10 +12,6 @@ export type BuildError = {
message: string
}
export type FastRefresh = { type: typeof TYPE_REFRESH }
export type FullRefreshNeeded = {
type: typeof TYPE_FULL_REFRESH_NEEDED
reason: string | null
}
export type UnhandledError = {
type: typeof TYPE_UNHANDLED_ERROR
reason: Error
Expand All @@ -31,7 +26,6 @@ export type BusEvent =
| BuildOk
| BuildError
| FastRefresh
| FullRefreshNeeded
| UnhandledError
| UnhandledRejection

Expand Down
Loading