Skip to content

Commit 734e292

Browse files
committed
Wait for pending Webpack Hot Updates before evaluating JS from RSC responses
The Webpack runtime always tries to be minimal. Since all pages share the same chunk, the prod runtime supports all pages. However, in dev, the webpack runtime only supports the current page. If we navigate to a new page, the Webpack runtime may need more functionality. Previously, we eagerly evaluated the RSC payload before any Hot Update was applied. This could lead to runtime errors. For RSC payloads specifically, we just fell back to an MPA navigation. We could continue to rely on this fallback. It may be disorienting though since we flash the error toast. We would also need to adjust this logic since `createFromFetch` no longer throws in these cases in later React version and instead lets the nearest Error Boundary handle these cases.
1 parent 7408b8b commit 734e292

File tree

2 files changed

+38
-4
lines changed

2 files changed

+38
-4
lines changed

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

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,35 @@ let __nextDevClientId = Math.round(Math.random() * 100 + Date.now())
4747
let reloading = false
4848
let startLatency: number | null = null
4949

50-
function onBeforeFastRefresh(dispatcher: Dispatcher, hasUpdates: boolean) {
50+
let pendingHotUpdateWebpack = Promise.resolve()
51+
let resolvePendingHotUpdateWebpack: () => void = () => {}
52+
function setPendingHotUpdateWebpack() {
53+
pendingHotUpdateWebpack = new Promise((resolve) => {
54+
resolvePendingHotUpdateWebpack = () => {
55+
resolve()
56+
}
57+
})
58+
}
59+
60+
export function waitForWebpackRuntimeHotUpdate() {
61+
return pendingHotUpdateWebpack
62+
}
63+
64+
function handleBeforeHotUpdateWebpack(
65+
dispatcher: Dispatcher,
66+
hasUpdates: boolean
67+
) {
5168
if (hasUpdates) {
5269
dispatcher.onBeforeRefresh()
5370
}
5471
}
5572

56-
function onFastRefresh(
73+
function handleSuccessfulHotUpdateWebpack(
5774
dispatcher: Dispatcher,
5875
sendMessage: (message: string) => void,
5976
updatedModules: ReadonlyArray<string>
6077
) {
78+
resolvePendingHotUpdateWebpack()
6179
dispatcher.onBuildOk()
6280

6381
reportHmrLatency(sendMessage, updatedModules)
@@ -281,12 +299,16 @@ function processMessage(
281299
} else {
282300
tryApplyUpdates(
283301
function onBeforeHotUpdate(hasUpdates: boolean) {
284-
onBeforeFastRefresh(dispatcher, hasUpdates)
302+
handleBeforeHotUpdateWebpack(dispatcher, hasUpdates)
285303
},
286304
function onSuccessfulHotUpdate(webpackUpdatedModules: string[]) {
287305
// Only dismiss it when we're sure it's a hot update.
288306
// Otherwise it would flicker right before the reload.
289-
onFastRefresh(dispatcher, sendMessage, webpackUpdatedModules)
307+
handleSuccessfulHotUpdateWebpack(
308+
dispatcher,
309+
sendMessage,
310+
webpackUpdatedModules
311+
)
290312
},
291313
sendMessage,
292314
dispatcher
@@ -320,6 +342,9 @@ function processMessage(
320342
}
321343
case HMR_ACTIONS_SENT_TO_BROWSER.BUILDING: {
322344
startLatency = Date.now()
345+
if (!process.env.TURBOPACK) {
346+
setPendingHotUpdateWebpack()
347+
}
323348
console.log('[Fast Refresh] rebuilding')
324349
break
325350
}

packages/next/src/client/components/router-reducer/fetch-server-response.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { urlToUrlWithoutFlightMarker } from '../app-router'
2929
import { callServer } from '../../app-call-server'
3030
import { PrefetchKind } from './router-reducer-types'
3131
import { hexHash } from '../../../shared/lib/hash'
32+
import { waitForWebpackRuntimeHotUpdate } from '../react-dev-overlay/app/hot-reloader-client'
3233

3334
export type FetchServerResponseResult = [
3435
flightData: FlightData,
@@ -152,6 +153,14 @@ export async function fetchServerResponse(
152153
return doMpaNavigation(responseUrl.toString())
153154
}
154155

156+
// We may navigate to a page that requires a different Webpack runtime.
157+
// In prod, every page will have the same Webpack runtime.
158+
// In dev, the Webpack runtime is minimal for each page.
159+
// We need to ensure the Webpack runtime is updated before executing client-side JS of the new page.
160+
if (process.env.NODE_ENV !== 'production') {
161+
await waitForWebpackRuntimeHotUpdate()
162+
}
163+
155164
// Handle the `fetch` readable stream that can be unwrapped by `React.use`.
156165
const [buildId, flightData]: NextFlightResponse = await createFromFetch(
157166
Promise.resolve(res),

0 commit comments

Comments
 (0)