diff --git a/packages/next/src/client/components/errors/hydration-error-info.ts b/packages/next/src/client/components/errors/hydration-error-info.ts deleted file mode 100644 index 3854db35535d..000000000000 --- a/packages/next/src/client/components/errors/hydration-error-info.ts +++ /dev/null @@ -1,39 +0,0 @@ -type NullableText = string | null | undefined - -// https://github.com/facebook/react/blob/main/packages/react-dom/src/__tests__/ReactDOMHydrationDiff-test.js used as a reference -const htmlTagsWarnings = new Set([ - 'Warning: In HTML, %s cannot be a child of <%s>.%s\nThis will cause a hydration error.%s', - 'Warning: In HTML, %s cannot be a descendant of <%s>.\nThis will cause a hydration error.%s', - 'Warning: In HTML, text nodes cannot be a child of <%s>.\nThis will cause a hydration error.', - "Warning: In HTML, whitespace text nodes cannot be a child of <%s>. Make sure you don't have any extra whitespace between tags on each line of your source code.\nThis will cause a hydration error.", - 'Warning: Expected server HTML to contain a matching <%s> in <%s>.%s', - 'Warning: Did not expect server HTML to contain a <%s> in <%s>.%s', -]) -const textAndTagsMismatchWarnings = new Set([ - 'Warning: Expected server HTML to contain a matching text node for "%s" in <%s>.%s', - 'Warning: Did not expect server HTML to contain the text node "%s" in <%s>.%s', -]) - -export const getHydrationWarningType = ( - message: NullableText -): 'tag' | 'text' | 'text-in-tag' => { - if (typeof message !== 'string') { - // TODO: Doesn't make sense to treat no message as a hydration error message. - // We should bail out somewhere earlier. - return 'text' - } - - const normalizedMessage = message.startsWith('Warning: ') - ? message - : `Warning: ${message}` - - if (isHtmlTagsWarning(normalizedMessage)) return 'tag' - if (isTextInTagsMismatchWarning(normalizedMessage)) return 'text-in-tag' - - return 'text' -} - -const isHtmlTagsWarning = (message: string) => htmlTagsWarnings.has(message) - -const isTextInTagsMismatchWarning = (msg: string) => - textAndTagsMismatchWarnings.has(msg) diff --git a/packages/next/src/client/components/react-dev-overlay/pages/bus.ts b/packages/next/src/client/components/react-dev-overlay/pages/bus.ts deleted file mode 100644 index 47835b047b06..000000000000 --- a/packages/next/src/client/components/react-dev-overlay/pages/bus.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { BusEvent } from '../shared' - -export type BusEventHandler = (ev: BusEvent) => void - -let handlers: Set = new Set() -let queue: BusEvent[] = [] - -function drain() { - // Draining should never happen synchronously in case multiple handlers are - // registered. - setTimeout(function () { - while ( - // Until we are out of events: - Boolean(queue.length) && - // Or, if all handlers removed themselves as a result of handling the - // event(s) - Boolean(handlers.size) - ) { - const ev = queue.shift()! - handlers.forEach((handler) => handler(ev)) - } - }, 1) -} - -export function emit(ev: BusEvent): void { - queue.push(Object.freeze({ ...ev })) - drain() -} - -export function on(fn: BusEventHandler): boolean { - if (handlers.has(fn)) { - return false - } - - handlers.add(fn) - drain() - return true -} - -export function off(fn: BusEventHandler): boolean { - if (handlers.has(fn)) { - handlers.delete(fn) - return true - } - - return false -} diff --git a/packages/next/src/client/components/react-dev-overlay/shared.ts b/packages/next/src/client/components/react-dev-overlay/shared.ts index 0931ad02763a..4a1a5216ab5b 100644 --- a/packages/next/src/client/components/react-dev-overlay/shared.ts +++ b/packages/next/src/client/components/react-dev-overlay/shared.ts @@ -30,7 +30,7 @@ export interface OverlayState { routerType: 'pages' | 'app' isErrorOverlayOpen: boolean } -export type OverlayDispatch = React.Dispatch +export type OverlayDispatch = React.Dispatch export const ACTION_STATIC_INDICATOR = 'static-indicator' export const ACTION_BUILD_OK = 'build-ok' @@ -121,7 +121,7 @@ export interface RenderingIndicatorHideAction { type: typeof ACTION_RENDERING_INDICATOR_HIDE } -export type BusEvent = +export type DispatcherEvent = | BuildOkAction | BuildErrorAction | BeforeFastRefreshAction @@ -240,107 +240,110 @@ export function useErrorOverlayReducer( return events } - return useReducer((state: OverlayState, action: BusEvent): OverlayState => { - switch (action.type) { - case ACTION_DEBUG_INFO: { - return { ...state, debugInfo: action.debugInfo } - } - case ACTION_STATIC_INDICATOR: { - return { ...state, staticIndicator: action.staticIndicator } - } - case ACTION_BUILD_OK: { - return { ...state, buildError: null } - } - case ACTION_BUILD_ERROR: { - return { ...state, buildError: action.message } - } - case ACTION_BEFORE_REFRESH: { - return { ...state, refreshState: { type: 'pending', errors: [] } } - } - case ACTION_REFRESH: { - return { - ...state, - buildError: null, - errors: - // Errors can come in during updates. In this case, UNHANDLED_ERROR - // and UNHANDLED_REJECTION events might be dispatched between the - // BEFORE_REFRESH and the REFRESH event. We want to keep those errors - // around until the next refresh. Otherwise we run into a race - // condition where those errors would be cleared on refresh completion - // before they can be displayed. - state.refreshState.type === 'pending' - ? state.refreshState.errors - : [], - refreshState: { type: 'idle' }, + return useReducer( + (state: OverlayState, action: DispatcherEvent): OverlayState => { + switch (action.type) { + case ACTION_DEBUG_INFO: { + return { ...state, debugInfo: action.debugInfo } } - } - case ACTION_UNHANDLED_ERROR: - case ACTION_UNHANDLED_REJECTION: { - switch (state.refreshState.type) { - case 'idle': { - return { - ...state, - nextId: state.nextId + 1, - errors: pushErrorFilterDuplicates( - state.errors, - state.nextId, - action.reason - ), - } + case ACTION_STATIC_INDICATOR: { + return { ...state, staticIndicator: action.staticIndicator } + } + case ACTION_BUILD_OK: { + return { ...state, buildError: null } + } + case ACTION_BUILD_ERROR: { + return { ...state, buildError: action.message } + } + case ACTION_BEFORE_REFRESH: { + return { ...state, refreshState: { type: 'pending', errors: [] } } + } + case ACTION_REFRESH: { + return { + ...state, + buildError: null, + errors: + // Errors can come in during updates. In this case, UNHANDLED_ERROR + // and UNHANDLED_REJECTION events might be dispatched between the + // BEFORE_REFRESH and the REFRESH event. We want to keep those errors + // around until the next refresh. Otherwise we run into a race + // condition where those errors would be cleared on refresh completion + // before they can be displayed. + state.refreshState.type === 'pending' + ? state.refreshState.errors + : [], + refreshState: { type: 'idle' }, } - case 'pending': { - return { - ...state, - nextId: state.nextId + 1, - refreshState: { - ...state.refreshState, + } + case ACTION_UNHANDLED_ERROR: + case ACTION_UNHANDLED_REJECTION: { + switch (state.refreshState.type) { + case 'idle': { + return { + ...state, + nextId: state.nextId + 1, errors: pushErrorFilterDuplicates( state.errors, state.nextId, action.reason ), - }, + } + } + case 'pending': { + return { + ...state, + nextId: state.nextId + 1, + refreshState: { + ...state.refreshState, + errors: pushErrorFilterDuplicates( + state.errors, + state.nextId, + action.reason + ), + }, + } } + default: + return state } - default: - return state } - } - case ACTION_VERSION_INFO: { - return { ...state, versionInfo: action.versionInfo } - } - case ACTION_DEV_INDICATOR: { - return { - ...state, - showIndicator: true, - disableDevIndicator: - shouldDisableDevIndicator || !!action.devIndicator.disabledUntil, + case ACTION_VERSION_INFO: { + return { ...state, versionInfo: action.versionInfo } + } + case ACTION_DEV_INDICATOR: { + return { + ...state, + showIndicator: true, + disableDevIndicator: + shouldDisableDevIndicator || !!action.devIndicator.disabledUntil, + } + } + case ACTION_ERROR_OVERLAY_OPEN: { + return { ...state, isErrorOverlayOpen: true } + } + case ACTION_ERROR_OVERLAY_CLOSE: { + return { ...state, isErrorOverlayOpen: false } + } + case ACTION_ERROR_OVERLAY_TOGGLE: { + return { ...state, isErrorOverlayOpen: !state.isErrorOverlayOpen } + } + case ACTION_BUILDING_INDICATOR_SHOW: { + return { ...state, buildingIndicator: true } + } + case ACTION_BUILDING_INDICATOR_HIDE: { + return { ...state, buildingIndicator: false } + } + case ACTION_RENDERING_INDICATOR_SHOW: { + return { ...state, renderingIndicator: true } + } + case ACTION_RENDERING_INDICATOR_HIDE: { + return { ...state, renderingIndicator: false } + } + default: { + return state } } - case ACTION_ERROR_OVERLAY_OPEN: { - return { ...state, isErrorOverlayOpen: true } - } - case ACTION_ERROR_OVERLAY_CLOSE: { - return { ...state, isErrorOverlayOpen: false } - } - case ACTION_ERROR_OVERLAY_TOGGLE: { - return { ...state, isErrorOverlayOpen: !state.isErrorOverlayOpen } - } - case ACTION_BUILDING_INDICATOR_SHOW: { - return { ...state, buildingIndicator: true } - } - case ACTION_BUILDING_INDICATOR_HIDE: { - return { ...state, buildingIndicator: false } - } - case ACTION_RENDERING_INDICATOR_SHOW: { - return { ...state, renderingIndicator: true } - } - case ACTION_RENDERING_INDICATOR_HIDE: { - return { ...state, renderingIndicator: false } - } - default: { - return state - } - } - }, getInitialState(routerType)) + }, + getInitialState(routerType) + ) } diff --git a/packages/next/src/client/components/react-dev-overlay/ui/dev-overlay.stories.tsx b/packages/next/src/client/components/react-dev-overlay/ui/dev-overlay.stories.tsx index a99e422fe9c2..379b8282d08d 100644 --- a/packages/next/src/client/components/react-dev-overlay/ui/dev-overlay.stories.tsx +++ b/packages/next/src/client/components/react-dev-overlay/ui/dev-overlay.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react' -import type { BusEvent, OverlayState } from '../shared' +import type { DispatcherEvent, OverlayState } from '../shared' // @ts-expect-error import imgApp from './app.png' @@ -97,23 +97,26 @@ const initialState: OverlayState = { } function useOverlayReducer() { - return useReducer((state, action): OverlayState => { - switch (action.type) { - case ACTION_ERROR_OVERLAY_CLOSE: { - return { ...state, isErrorOverlayOpen: false } + return useReducer( + (state, action): OverlayState => { + switch (action.type) { + case ACTION_ERROR_OVERLAY_CLOSE: { + return { ...state, isErrorOverlayOpen: false } + } + case ACTION_ERROR_OVERLAY_OPEN: { + return { ...state, isErrorOverlayOpen: true } + } + case ACTION_ERROR_OVERLAY_TOGGLE: { + return { ...state, isErrorOverlayOpen: !state.isErrorOverlayOpen } + } + default: { + return state + } } - case ACTION_ERROR_OVERLAY_OPEN: { - return { ...state, isErrorOverlayOpen: true } - } - case ACTION_ERROR_OVERLAY_TOGGLE: { - return { ...state, isErrorOverlayOpen: !state.isErrorOverlayOpen } - } - default: { - return state - } - } - return state - }, initialState) + return state + }, + initialState + ) } function getNoSquashedHydrationErrorDetails() {