Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c37a39b

Browse files
authored
chore(HMR clients): Clean up and share code between app and pages router (#76960)
# Do not review this PR in Graphite, review it one commit at a time: https://github.com/vercel/next.js/pull/76960/commits This is a series of very small changes to the hot reloader clients in both app and pages router to share more code and unify some of the logic or behavior between the two. This code is very fragile, so lots of small commits have been useful in bisecting test failures. These changes are too small to be practical as separate stacked graphite PRs.
1 parent 8bc202b commit c37a39b

File tree

8 files changed

+102
-119
lines changed

8 files changed

+102
-119
lines changed

packages/next/src/client/components/react-dev-overlay/app/hot-reloader-client.tsx

Lines changed: 17 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import {
2121
ACTION_UNHANDLED_ERROR,
2222
ACTION_UNHANDLED_REJECTION,
2323
ACTION_VERSION_INFO,
24+
REACT_REFRESH_FULL_RELOAD,
25+
reportInvalidHmrMessage,
2426
useErrorOverlayReducer,
2527
} from '../shared'
2628
import { parseStack } from '../utils/parse-stack'
@@ -50,6 +52,7 @@ import { shouldRenderRootLevelErrorOverlay } from '../../../lib/is-error-thrown-
5052
import { handleDevBuildIndicatorHmrEvents } from '../../../dev/dev-build-indicator/internal/handle-dev-build-indicator-hmr-events'
5153
import type { GlobalErrorComponent } from '../../error-boundary'
5254
import type { DevIndicatorServerState } from '../../../../server/dev/dev-indicator-server-state'
55+
import reportHmrLatency from '../utils/report-hmr-latency'
5356

5457
export interface Dispatcher {
5558
onBuildOk(): void
@@ -83,52 +86,18 @@ export function waitForWebpackRuntimeHotUpdate() {
8386
return pendingHotUpdateWebpack
8487
}
8588

86-
function handleBeforeHotUpdateWebpack(
87-
dispatcher: Dispatcher,
88-
hasUpdates: boolean
89-
) {
90-
if (hasUpdates) {
91-
dispatcher.onBeforeRefresh()
92-
}
93-
}
94-
9589
function handleSuccessfulHotUpdateWebpack(
9690
dispatcher: Dispatcher,
9791
sendMessage: (message: string) => void,
9892
updatedModules: ReadonlyArray<string>
9993
) {
10094
resolvePendingHotUpdateWebpack()
10195
dispatcher.onBuildOk()
102-
reportHmrLatency(sendMessage, updatedModules)
96+
reportHmrLatency(sendMessage, updatedModules, startLatency!, Date.now())
10397

10498
dispatcher.onRefresh()
10599
}
106100

107-
function reportHmrLatency(
108-
sendMessage: (message: string) => void,
109-
updatedModules: ReadonlyArray<string>
110-
) {
111-
if (!startLatency) return
112-
// turbopack has a debounce for the "built" event which we don't want to
113-
// incorrectly show in this number, use the last TURBOPACK_MESSAGE time
114-
let endLatency = turbopackLastUpdateLatency ?? Date.now()
115-
const latency = endLatency - startLatency
116-
console.log(`[Fast Refresh] done in ${latency}ms`)
117-
sendMessage(
118-
JSON.stringify({
119-
event: 'client-hmr-latency',
120-
id: window.__nextDevClientId,
121-
startTime: startLatency,
122-
endTime: endLatency,
123-
page: window.location.pathname,
124-
updatedModules,
125-
// Whether the page (tab) was hidden at the time the event occurred.
126-
// This can impact the accuracy of the event's timing.
127-
isPageHidden: document.visibilityState === 'hidden',
128-
})
129-
)
130-
}
131-
132101
// There is a newer version of the code available.
133102
function handleAvailableHash(hash: string) {
134103
// Update last known compilation hash.
@@ -203,21 +172,14 @@ function tryApplyUpdates(
203172
if (!isUpdateAvailable() || !canApplyUpdates()) {
204173
resolvePendingHotUpdateWebpack()
205174
dispatcher.onBuildOk()
206-
reportHmrLatency(sendMessage, [])
175+
reportHmrLatency(sendMessage, [], startLatency!, Date.now())
207176
return
208177
}
209178

210179
function handleApplyUpdates(err: any, updatedModules: string[] | null) {
211180
if (err || RuntimeErrorHandler.hadRuntimeError || !updatedModules) {
212181
if (err) {
213-
console.warn(
214-
'[Fast Refresh] performing full reload\n\n' +
215-
"Fast Refresh will perform a full reload when you edit a file that's imported by modules outside of the React rendering tree.\n" +
216-
'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' +
217-
'Consider migrating the non-React component export to a separate file and importing it into both files.\n\n' +
218-
'It is also possible the parent component of the component you edited is a class component, which disables Fast Refresh.\n' +
219-
'Fast Refresh requires at least one parent function component in your React tree.'
220-
)
182+
console.warn(REACT_REFRESH_FULL_RELOAD)
221183
} else if (RuntimeErrorHandler.hadRuntimeError) {
222184
console.warn(REACT_REFRESH_FULL_RELOAD_FROM_ERROR)
223185
}
@@ -321,11 +283,18 @@ function processMessage(
321283
function handleHotUpdate() {
322284
if (process.env.TURBOPACK) {
323285
dispatcher.onBuildOk()
324-
reportHmrLatency(sendMessage, [...turbopackUpdatedModules])
286+
reportHmrLatency(
287+
sendMessage,
288+
[...turbopackUpdatedModules],
289+
startLatency!,
290+
turbopackLastUpdateLatency ?? Date.now()
291+
)
325292
} else {
326293
tryApplyUpdates(
327294
function onBeforeHotUpdate(hasUpdates: boolean) {
328-
handleBeforeHotUpdateWebpack(dispatcher, hasUpdates)
295+
if (hasUpdates) {
296+
dispatcher.onBeforeRefresh()
297+
}
329298
},
330299
function onSuccessfulHotUpdate(webpackUpdatedModules: string[]) {
331300
// Only dismiss it when we're sure it's a hot update.
@@ -437,7 +406,6 @@ function processMessage(
437406
)
438407

439408
if (obj.action === HMR_ACTIONS_SENT_TO_BROWSER.BUILT) {
440-
// Handle hot updates
441409
handleHotUpdate()
442410
}
443411
return
@@ -691,13 +659,8 @@ export default function HotReload({
691659
appIsrManifestRef,
692660
pathnameRef
693661
)
694-
} catch (err: any) {
695-
console.warn(
696-
'[HMR] Invalid message: ' +
697-
JSON.stringify(event.data) +
698-
'\n' +
699-
(err?.stack ?? '')
700-
)
662+
} catch (err: unknown) {
663+
reportInvalidHmrMessage(event, err)
701664
}
702665
}
703666

packages/next/src/client/components/react-dev-overlay/pages/hot-reloader-client.ts

Lines changed: 26 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,13 @@ import type {
4747
TurbopackMsgToBrowser,
4848
} from '../../../../server/dev/hot-reloader-types'
4949
import { extractModulesFromTurbopackMessage } from '../../../../server/dev/extract-modules-from-turbopack-message'
50-
import { REACT_REFRESH_FULL_RELOAD_FROM_ERROR } from '../shared'
50+
import {
51+
REACT_REFRESH_FULL_RELOAD,
52+
REACT_REFRESH_FULL_RELOAD_FROM_ERROR,
53+
reportInvalidHmrMessage,
54+
} from '../shared'
5155
import { RuntimeErrorHandler } from '../../errors/runtime-error-handler'
56+
import reportHmrLatency from '../utils/report-hmr-latency'
5257
// This alternative WebpackDevServer combines the functionality of:
5358
// https://github.com/webpack/webpack-dev-server/blob/webpack-1/client/index.js
5459
// https://github.com/webpack/webpack/blob/webpack-1/hot/dev-server.js
@@ -62,17 +67,14 @@ declare global {
6267
const __webpack_hash__: string
6368
interface Window {
6469
__nextDevClientId: number
65-
__NEXT_HMR_LATENCY_CB: any
6670
}
6771
}
6872

6973
window.__nextDevClientId = Math.round(Math.random() * 100 + Date.now())
7074

7175
let customHmrEventHandler: any
7276
let turbopackMessageListeners: ((msg: TurbopackMsgToBrowser) => void)[] = []
73-
let MODE: 'webpack' | 'turbopack' = 'webpack'
74-
export default function connect(mode: 'webpack' | 'turbopack') {
75-
MODE = mode
77+
export default function connect() {
7678
register()
7779

7880
addMessageListener((payload) => {
@@ -82,13 +84,8 @@ export default function connect(mode: 'webpack' | 'turbopack') {
8284

8385
try {
8486
processMessage(payload)
85-
} catch (err: any) {
86-
console.warn(
87-
'[HMR] Invalid message: ' +
88-
JSON.stringify(payload) +
89-
'\n' +
90-
(err?.stack ?? '')
91-
)
87+
} catch (err: unknown) {
88+
reportInvalidHmrMessage(payload, err)
9289
}
9390
})
9491

@@ -129,7 +126,15 @@ function clearOutdatedErrors() {
129126
function handleSuccess() {
130127
clearOutdatedErrors()
131128

132-
if (MODE === 'webpack') {
129+
if (process.env.TURBOPACK) {
130+
reportHmrLatency(
131+
sendMessage,
132+
[...turbopackUpdatedModules],
133+
startLatency!,
134+
turbopackLastUpdateLatency ?? Date.now()
135+
)
136+
onBuildOk()
137+
} else {
133138
const isHotUpdate =
134139
!isFirstCompilation ||
135140
(window.__NEXT_DATA__.page !== '/_error' && isUpdateAvailable())
@@ -140,9 +145,6 @@ function handleSuccess() {
140145
if (isHotUpdate) {
141146
tryApplyUpdates(onBeforeFastRefresh, onFastRefresh)
142147
}
143-
} else {
144-
reportHmrLatency([...turbopackUpdatedModules])
145-
onBuildOk()
146148
}
147149
}
148150

@@ -238,32 +240,12 @@ function onFastRefresh(updatedModules: ReadonlyArray<string> = []) {
238240

239241
onRefresh()
240242

241-
reportHmrLatency()
242-
}
243-
244-
function reportHmrLatency(updatedModules: ReadonlyArray<string> = []) {
245-
if (!startLatency) return
246-
// turbopack has a debounce for the BUILT event which we don't want to
247-
// incorrectly show in this number, use the last TURBOPACK_MESSAGE time
248-
let endLatency = turbopackLastUpdateLatency ?? Date.now()
249-
const latency = endLatency - startLatency
250-
console.log(`[Fast Refresh] done in ${latency}ms`)
251-
sendMessage(
252-
JSON.stringify({
253-
event: 'client-hmr-latency',
254-
id: window.__nextDevClientId,
255-
startTime: startLatency,
256-
endTime: endLatency,
257-
page: window.location.pathname,
258-
updatedModules,
259-
// Whether the page (tab) was hidden at the time the event occurred.
260-
// This can impact the accuracy of the event's timing.
261-
isPageHidden: document.visibilityState === 'hidden',
262-
})
243+
reportHmrLatency(
244+
sendMessage,
245+
updatedModules,
246+
startLatency!,
247+
turbopackLastUpdateLatency ?? Date.now()
263248
)
264-
if (self.__NEXT_HMR_LATENCY_CB) {
265-
self.__NEXT_HMR_LATENCY_CB(latency)
266-
}
267249
}
268250

269251
// There is a newer version of the code available.
@@ -278,7 +260,7 @@ export function handleStaticIndicator() {
278260
const pageComponent = routeInfo?.Component
279261
const appComponent = window.next.router.components['/_app']?.Component
280262
const isDynamicPage =
281-
Boolean(pageComponent?.getInitialProps) || Boolean(routeInfo.__N_SSP)
263+
Boolean(pageComponent?.getInitialProps) || Boolean(routeInfo?.__N_SSP)
282264
const hasAppGetInitialProps =
283265
Boolean(appComponent?.getInitialProps) &&
284266
appComponent?.getInitialProps !== appComponent?.origGetInitialProps
@@ -297,7 +279,6 @@ function processMessage(obj: HMR_ACTION_TYPES) {
297279
return
298280
}
299281

300-
// Use turbopack message for analytics, (still need built for webpack)
301282
switch (obj.action) {
302283
case HMR_ACTIONS_SENT_TO_BROWSER.ISR_MANIFEST: {
303284
isrManifest = obj.data
@@ -458,18 +439,9 @@ function tryApplyUpdates(
458439
function handleApplyUpdates(err: any, updatedModules: string[] | null) {
459440
if (err || RuntimeErrorHandler.hadRuntimeError || !updatedModules) {
460441
if (err) {
461-
console.warn(
462-
'[Fast Refresh] performing full reload\n\n' +
463-
"Fast Refresh will perform a full reload when you edit a file that's imported by modules outside of the React rendering tree.\n" +
464-
'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' +
465-
'Consider migrating the non-React component export to a separate file and importing it into both files.\n\n' +
466-
'It is also possible the parent component of the component you edited is a class component, which disables Fast Refresh.\n' +
467-
'Fast Refresh requires at least one parent function component in your React tree.'
468-
)
442+
console.warn(REACT_REFRESH_FULL_RELOAD)
469443
} else if (RuntimeErrorHandler.hadRuntimeError) {
470-
console.warn(
471-
'[Fast Refresh] performing full reload because your application had an unrecoverable error'
472-
)
444+
console.warn(REACT_REFRESH_FULL_RELOAD_FROM_ERROR)
473445
}
474446
performFullReload(err)
475447
return

packages/next/src/client/components/react-dev-overlay/shared.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { SupportedErrorEvent } from './ui/container/runtime-error/render-er
66
import type { ComponentStackFrame } from './utils/parse-component-stack'
77
import type { DebugInfo } from './types'
88
import type { DevIndicatorServerState } from '../../../server/dev/dev-indicator-server-state'
9+
import type { HMR_ACTION_TYPES } from '../../../server/dev/hot-reloader-types'
910

1011
type FastRefreshState =
1112
/** No refresh in progress. */
@@ -224,5 +225,25 @@ export function useErrorOverlayReducer(routerType: 'pages' | 'app') {
224225
}, getInitialState(routerType))
225226
}
226227

228+
export const REACT_REFRESH_FULL_RELOAD =
229+
'[Fast Refresh] performing full reload\n\n' +
230+
"Fast Refresh will perform a full reload when you edit a file that's imported by modules outside of the React rendering tree.\n" +
231+
'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' +
232+
'Consider migrating the non-React component export to a separate file and importing it into both files.\n\n' +
233+
'It is also possible the parent component of the component you edited is a class component, which disables Fast Refresh.\n' +
234+
'Fast Refresh requires at least one parent function component in your React tree.'
235+
227236
export const REACT_REFRESH_FULL_RELOAD_FROM_ERROR =
228237
'[Fast Refresh] performing full reload because your application had an unrecoverable error'
238+
239+
export function reportInvalidHmrMessage(
240+
message: HMR_ACTION_TYPES | MessageEvent<unknown>,
241+
err: unknown
242+
) {
243+
console.warn(
244+
'[HMR] Invalid message: ' +
245+
JSON.stringify(message) +
246+
'\n' +
247+
((err instanceof Error && err?.stack) || '')
248+
)
249+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
declare global {
2+
interface Window {
3+
__NEXT_HMR_LATENCY_CB: ((latencyMs: number) => void) | undefined
4+
}
5+
}
6+
7+
export default function reportHmrLatency(
8+
sendMessage: (message: string) => void,
9+
updatedModules: ReadonlyArray<string>,
10+
startMsSinceEpoch: number,
11+
endMsSinceEpoch: number
12+
) {
13+
const latencyMs = endMsSinceEpoch - startMsSinceEpoch
14+
console.log(`[Fast Refresh] done in ${latencyMs}ms`)
15+
sendMessage(
16+
JSON.stringify({
17+
event: 'client-hmr-latency',
18+
id: window.__nextDevClientId,
19+
startTime: startMsSinceEpoch,
20+
endTime: endMsSinceEpoch,
21+
page: window.location.pathname,
22+
updatedModules,
23+
// Whether the page (tab) was hidden at the time the event occurred.
24+
// This can impact the accuracy of the event's timing.
25+
isPageHidden: document.visibilityState === 'hidden',
26+
})
27+
)
28+
if (self.__NEXT_HMR_LATENCY_CB) {
29+
self.__NEXT_HMR_LATENCY_CB(latencyMs)
30+
}
31+
}

packages/next/src/client/dev/amp-dev.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
connectHMR,
77
} from '../components/react-dev-overlay/pages/websocket'
88
import { HMR_ACTIONS_SENT_TO_BROWSER } from '../../server/dev/hot-reloader-types'
9+
import { reportInvalidHmrMessage } from '../components/react-dev-overlay/shared'
910

1011
declare global {
1112
const __webpack_runtime_id__: string
@@ -106,13 +107,8 @@ addMessageListener((message) => {
106107
} else if (message.action === HMR_ACTIONS_SENT_TO_BROWSER.RELOAD_PAGE) {
107108
window.location.reload()
108109
}
109-
} catch (err: any) {
110-
console.warn(
111-
'[HMR] Invalid message: ' +
112-
JSON.stringify(message) +
113-
'\n' +
114-
(err?.stack ?? '')
115-
)
110+
} catch (err: unknown) {
111+
reportInvalidHmrMessage(message, err)
116112
}
117113
})
118114

packages/next/src/client/dev/hot-middleware-client.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ declare const window: NextWindow
2020

2121
let reloading = false
2222

23-
export default (mode: 'webpack' | 'turbopack') => {
24-
const devClient = connect(mode)
23+
export default () => {
24+
const devClient = connect()
2525

2626
devClient.subscribeToHmrEvent((obj: any) => {
2727
if (reloading) return

0 commit comments

Comments
 (0)