Skip to content

Commit 61ce02e

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 1c2fb8e commit 61ce02e

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

+29-4
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,35 @@ let __nextDevClientId = Math.round(Math.random() * 100 + Date.now())
4646
let reloading = false
4747
let startLatency: number | null = null
4848

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

55-
function onFastRefresh(
72+
function handleSuccessfulHotUpdateWebpack(
5673
dispatcher: Dispatcher,
5774
sendMessage: (message: string) => void,
5875
updatedModules: ReadonlyArray<string>
5976
) {
77+
resolvePendingHotUpdateWebpack()
6078
dispatcher.onBuildOk()
6179

6280
reportHmrLatency(sendMessage, updatedModules)
@@ -278,12 +296,16 @@ function processMessage(
278296
} else {
279297
tryApplyUpdates(
280298
function onBeforeHotUpdate(hasUpdates: boolean) {
281-
onBeforeFastRefresh(dispatcher, hasUpdates)
299+
handleBeforeHotUpdateWebpack(dispatcher, hasUpdates)
282300
},
283301
function onSuccessfulHotUpdate(webpackUpdatedModules: string[]) {
284302
// Only dismiss it when we're sure it's a hot update.
285303
// Otherwise it would flicker right before the reload.
286-
onFastRefresh(dispatcher, sendMessage, webpackUpdatedModules)
304+
handleSuccessfulHotUpdateWebpack(
305+
dispatcher,
306+
sendMessage,
307+
webpackUpdatedModules
308+
)
287309
},
288310
sendMessage,
289311
dispatcher
@@ -294,6 +316,9 @@ function processMessage(
294316
switch (obj.action) {
295317
case HMR_ACTIONS_SENT_TO_BROWSER.BUILDING: {
296318
startLatency = Date.now()
319+
if (!process.env.TURBOPACK) {
320+
setPendingHotUpdateWebpack()
321+
}
297322
console.log('[Fast Refresh] rebuilding')
298323
break
299324
}

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

+9
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)