Skip to content

[dev-tools] refactor: merge devtools-related states into server/client state #79544

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

Open
wants to merge 3 commits into
base: jiwon/05-23-_dev-tools_refactor_use_global_server_state
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@ import {
ACTION_BEFORE_REFRESH,
ACTION_BUILD_ERROR,
ACTION_BUILD_OK,
ACTION_DEBUG_INFO,
ACTION_DEV_INDICATOR,
ACTION_DEV_TOOLS,
ACTION_REFRESH,
ACTION_STATIC_INDICATOR,
ACTION_UNHANDLED_ERROR,
ACTION_UNHANDLED_REJECTION,
ACTION_VERSION_INFO,
REACT_REFRESH_FULL_RELOAD,
reportInvalidHmrMessage,
useErrorOverlayReducer,
Expand All @@ -31,14 +29,12 @@ import {
useWebsocketPing,
} from '../utils/use-websocket'
import { parseComponentStack } from '../utils/parse-component-stack'
import type { VersionInfo } from '../../../../server/dev/parse-version-info'
import { HMR_ACTIONS_SENT_TO_BROWSER } from '../../../../server/dev/hot-reloader-types'
import type {
HMR_ACTION_TYPES,
TurbopackMsgToBrowser,
} from '../../../../server/dev/hot-reloader-types'
import { REACT_REFRESH_FULL_RELOAD_FROM_ERROR } from '../shared'
import type { DebugInfo } from '../types'
import { useUntrackedPathname } from '../../navigation-untracked'
import { getComponentStack, getOwnerStack } from '../../errors/stitched-error'
import { handleDevBuildIndicatorHmrEvents } from '../../../dev/dev-build-indicator/internal/handle-dev-build-indicator-hmr-events'
Expand All @@ -51,12 +47,10 @@ import { NEXT_HMR_REFRESH_HASH_COOKIE } from '../../app-router-headers'
export interface Dispatcher {
onBuildOk(): void
onBuildError(message: string): void
onVersionInfo(versionInfo: VersionInfo): void
onDebugInfo(debugInfo: DebugInfo): void
onBeforeRefresh(): void
onRefresh(): void
onStaticIndicator(status: boolean): void
onDevIndicator(devIndicator: DevToolsServerState['devIndicator']): void
onDevTools(devTools: DevToolsServerState): void
}

let mostRecentCompilationHash: any = null
Expand Down Expand Up @@ -316,9 +310,9 @@ function processMessage(
const { errors, warnings } = obj

// Is undefined when it's a 'built' event
if ('versionInfo' in obj) dispatcher.onVersionInfo(obj.versionInfo)
if ('debug' in obj && obj.debug) dispatcher.onDebugInfo(obj.debug)
if ('devIndicator' in obj) dispatcher.onDevIndicator(obj.devIndicator)
if ('devTools' in obj) {
dispatcher.onDevTools(obj.devTools)
}

const hasErrors = Boolean(errors && errors.length)
// Compilation with errors (e.g. syntax error or missing modules).
Expand Down Expand Up @@ -496,20 +490,11 @@ export default function HotReload({
onRefresh() {
dispatch({ type: ACTION_REFRESH })
},
onVersionInfo(versionInfo) {
dispatch({ type: ACTION_VERSION_INFO, versionInfo })
},
onStaticIndicator(status: boolean) {
dispatch({ type: ACTION_STATIC_INDICATOR, staticIndicator: status })
},
onDebugInfo(debugInfo) {
dispatch({ type: ACTION_DEBUG_INFO, debugInfo })
},
onDevIndicator(devIndicator) {
dispatch({
type: ACTION_DEV_INDICATOR,
devIndicator,
})
onDevTools(devTools: DevToolsServerState) {
dispatch({ type: ACTION_DEV_TOOLS, devTools })
},
}
}, [dispatch])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@ import {
ACTION_BEFORE_REFRESH,
ACTION_BUILD_ERROR,
ACTION_BUILD_OK,
ACTION_DEV_INDICATOR,
ACTION_DEV_TOOLS,
ACTION_REFRESH,
ACTION_STATIC_INDICATOR,
ACTION_UNHANDLED_ERROR,
ACTION_UNHANDLED_REJECTION,
ACTION_VERSION_INFO,
} from '../shared'
import type { VersionInfo } from '../../../../server/dev/parse-version-info'
import { getComponentStack, getOwnerStack } from '../../errors/stitched-error'
import type { DevToolsServerState } from '../../../../server/dev/dev-tools-server-state'

Expand Down Expand Up @@ -118,18 +116,12 @@ export function onBeforeRefresh() {
Bus.emit({ type: ACTION_BEFORE_REFRESH })
}

export function onVersionInfo(versionInfo: VersionInfo) {
Bus.emit({ type: ACTION_VERSION_INFO, versionInfo })
}

export function onStaticIndicator(isStatic: boolean) {
Bus.emit({ type: ACTION_STATIC_INDICATOR, staticIndicator: isStatic })
}

export function onDevIndicator(
devIndicator: DevToolsServerState['devIndicator']
) {
Bus.emit({ type: ACTION_DEV_INDICATOR, devIndicator })
export function onDevTools(devTools: DevToolsServerState) {
Bus.emit({ type: ACTION_DEV_TOOLS, devTools })
}

export { getErrorByType } from '../utils/get-error-by-type'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,8 @@ import {
onBuildOk,
onBeforeRefresh,
onRefresh,
onVersionInfo,
onDevTools,
onStaticIndicator,
onDevIndicator,
} from './client'
import stripAnsi from 'next/dist/compiled/strip-ansi'
import { addMessageListener, sendMessage } from './websocket'
Expand Down Expand Up @@ -287,8 +286,9 @@ function processMessage(obj: HMR_ACTION_TYPES) {
const { errors, warnings } = obj

// Is undefined when it's a 'built' event
if ('versionInfo' in obj) onVersionInfo(obj.versionInfo)
if ('devIndicator' in obj) onDevIndicator(obj.devIndicator)
if ('devTools' in obj) {
onDevTools(obj.devTools)
}

const hasErrors = Boolean(errors && errors.length)
if (hasErrors) {
Expand Down
92 changes: 50 additions & 42 deletions packages/next/src/client/components/react-dev-overlay/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,23 @@ type FastRefreshState =
/** The refresh process has been triggered, but the new code has not been executed yet. */
| { type: 'pending'; errors: SupportedErrorEvent[] }

export type DevToolsClientState = {
versionInfo: VersionInfo
debugInfo: DebugInfo
devIndicator: {
staticIndicator: boolean
showIndicator: boolean
disableDevIndicator: boolean
}
}

export interface OverlayState {
nextId: number
buildError: string | null
errors: SupportedErrorEvent[]
refreshState: FastRefreshState
versionInfo: VersionInfo
notFound: boolean
staticIndicator: boolean
showIndicator: boolean
disableDevIndicator: boolean
debugInfo: DebugInfo
devToolsClientState: DevToolsClientState
routerType: 'pages' | 'app'
}

Expand All @@ -34,11 +40,9 @@ export const ACTION_BUILD_OK = 'build-ok'
export const ACTION_BUILD_ERROR = 'build-error'
export const ACTION_BEFORE_REFRESH = 'before-fast-refresh'
export const ACTION_REFRESH = 'fast-refresh'
export const ACTION_VERSION_INFO = 'version-info'
export const ACTION_UNHANDLED_ERROR = 'unhandled-error'
export const ACTION_UNHANDLED_REJECTION = 'unhandled-rejection'
export const ACTION_DEBUG_INFO = 'debug-info'
export const ACTION_DEV_INDICATOR = 'dev-indicator'
export const ACTION_DEV_TOOLS = 'dev-tools'

export const STORAGE_KEY_THEME = '__nextjs-dev-tools-theme'
export const STORAGE_KEY_POSITION = '__nextjs-dev-tools-position'
Expand Down Expand Up @@ -75,19 +79,9 @@ export interface UnhandledRejectionAction {
frames: StackFrame[]
}

export interface DebugInfoAction {
type: typeof ACTION_DEBUG_INFO
debugInfo: any
}

interface VersionInfoAction {
type: typeof ACTION_VERSION_INFO
versionInfo: VersionInfo
}

interface DevIndicatorAction {
type: typeof ACTION_DEV_INDICATOR
devIndicator: DevToolsServerState['devIndicator']
interface DevToolsAction {
type: typeof ACTION_DEV_TOOLS
devTools: DevToolsServerState
}

export type BusEvent =
Expand All @@ -97,10 +91,8 @@ export type BusEvent =
| FastRefreshAction
| UnhandledErrorAction
| UnhandledRejectionAction
| VersionInfoAction
| StaticIndicatorAction
| DebugInfoAction
| DevIndicatorAction
| DevToolsAction

const REACT_ERROR_STACK_BOTTOM_FRAME_REGEX =
// 1st group: v8
Expand Down Expand Up @@ -141,17 +133,21 @@ export const INITIAL_OVERLAY_STATE: Omit<OverlayState, 'routerType'> = {
buildError: null,
errors: [],
notFound: false,
staticIndicator: false,
/*
refreshState: { type: 'idle' },
devToolsClientState: {
versionInfo: { installed: '0.0.0', staleness: 'unknown' },
debugInfo: { devtoolsFrontendUrl: undefined },
devIndicator: {
staticIndicator: false,
disableDevIndicator: false,
/*
This is set to `true` when we can reliably know
whether the indicator is in disabled state or not.
Otherwise the surface would flicker because the disabled flag loads from the config.
*/
showIndicator: false,
disableDevIndicator: false,
refreshState: { type: 'idle' },
versionInfo: { installed: '0.0.0', staleness: 'unknown' },
debugInfo: { devtoolsFrontendUrl: undefined },
showIndicator: false,
},
},
}

function getInitialState(
Expand All @@ -166,11 +162,17 @@ function getInitialState(
export function useErrorOverlayReducer(routerType: 'pages' | 'app') {
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 }
return {
...state,
devToolsClientState: {
...state.devToolsClientState,
devIndicator: {
...state.devToolsClientState.devIndicator,
staticIndicator: action.staticIndicator,
},
},
}
}
case ACTION_BUILD_OK: {
return { ...state, buildError: null }
Expand Down Expand Up @@ -228,15 +230,21 @@ export function useErrorOverlayReducer(routerType: 'pages' | 'app') {
return state
}
}
case ACTION_VERSION_INFO: {
return { ...state, versionInfo: action.versionInfo }
}
case ACTION_DEV_INDICATOR: {
case ACTION_DEV_TOOLS: {
return {
...state,
showIndicator: true,
disableDevIndicator:
shouldDisableDevIndicator || !!action.devIndicator.disabledUntil,
devToolsClientState: {
...state.devToolsClientState,
versionInfo: action.devTools.versionInfo,
debugInfo: action.devTools.debugInfo,
devIndicator: {
...state.devToolsClientState.devIndicator,
showIndicator: true,
disableDevIndicator:
shouldDisableDevIndicator ||
!!action.devTools.devIndicator.disabledUntil,
Comment on lines +236 to +245
Copy link
Member Author

Choose a reason for hiding this comment

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

States are too messy here because the server state and the client state differ. It is cleaned up at #79557. I separated to another PR because there were calculations on the client based on the server value. The PR moved the calculations to the server and made the client state fully inherit the server state.

},
},
}
}
default: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,16 @@ const state: OverlayState = {
buildError: null,
errors: [],
refreshState: { type: 'idle' },
disableDevIndicator: false,
showIndicator: true,
versionInfo: mockVersionInfo,
notFound: false,
staticIndicator: true,
debugInfo: { devtoolsFrontendUrl: undefined },
devToolsClientState: {
versionInfo: mockVersionInfo,
debugInfo: { devtoolsFrontendUrl: undefined },
devIndicator: {
staticIndicator: true,
showIndicator: true,
disableDevIndicator: false,
},
},
}

export const StaticRoute: Story = {
Expand All @@ -71,7 +75,13 @@ export const DynamicRoute: Story = {
errorCount: 0,
state: {
...state,
staticIndicator: false,
devToolsClientState: {
...state.devToolsClientState,
devIndicator: {
...state.devToolsClientState.devIndicator,
staticIndicator: false,
},
},
},
setIsErrorOverlayOpen: () => {},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ export function DevToolsIndicator({
return (
<DevToolsPopover
routerType={state.routerType}
semver={state.versionInfo.installed}
semver={state.devToolsClientState.versionInfo.installed}
issueCount={errorCount}
isStaticRoute={state.staticIndicator}
isStaticRoute={state.devToolsClientState.devIndicator.staticIndicator}
hide={() => {
setIsDevToolsIndicatorVisible(false)
fetch('/__nextjs_disable_dev_indicator', {
Expand All @@ -59,7 +59,10 @@ export function DevToolsIndicator({
}}
setIsErrorOverlayOpen={setIsErrorOverlayOpen}
isTurbopack={!!process.env.TURBOPACK}
disabled={state.disableDevIndicator || !isDevToolsIndicatorVisible}
disabled={
state.devToolsClientState.devIndicator.disableDevIndicator ||
!isDevToolsIndicatorVisible
}
isBuildError={isBuildError}
{...props}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { OverlayState } from '../../../../shared'
import type { DevToolsClientState, OverlayState } from '../../../../shared'

import { Suspense } from 'react'
import { BuildError } from '../../../container/build-error'
Expand All @@ -12,7 +12,7 @@ export interface ErrorBaseProps {
rendered: boolean
transitionDurationMs: number
isTurbopack: boolean
versionInfo: OverlayState['versionInfo']
versionInfo: DevToolsClientState['versionInfo']
errorCount: number
}

Expand Down Expand Up @@ -40,7 +40,7 @@ export function ErrorOverlay({
rendered,
transitionDurationMs,
isTurbopack,
versionInfo: state.versionInfo,
versionInfo: state.devToolsClientState.versionInfo,
errorCount,
}

Expand Down Expand Up @@ -71,7 +71,7 @@ export function ErrorOverlay({
return (
<Errors
{...commonProps}
debugInfo={state.debugInfo}
debugInfo={state.devToolsClientState.debugInfo}
runtimeErrors={runtimeErrors}
onClose={() => {
setIsErrorOverlayOpen(false)
Expand Down
Loading
Loading