Skip to content

Commit

Permalink
Wait for pending Webpack Hot Updates before evaluating JS from RSC re…
Browse files Browse the repository at this point in the history
…sponses

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.
  • Loading branch information
eps1lon committed Jul 12, 2024
1 parent 7408b8b commit 734e292
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,35 @@ let __nextDevClientId = Math.round(Math.random() * 100 + Date.now())
let reloading = false
let startLatency: number | null = null

function onBeforeFastRefresh(dispatcher: Dispatcher, hasUpdates: boolean) {
let pendingHotUpdateWebpack = Promise.resolve()
let resolvePendingHotUpdateWebpack: () => void = () => {}
function setPendingHotUpdateWebpack() {
pendingHotUpdateWebpack = new Promise((resolve) => {
resolvePendingHotUpdateWebpack = () => {
resolve()
}
})
}

export function waitForWebpackRuntimeHotUpdate() {
return pendingHotUpdateWebpack
}

function handleBeforeHotUpdateWebpack(
dispatcher: Dispatcher,
hasUpdates: boolean
) {
if (hasUpdates) {
dispatcher.onBeforeRefresh()
}
}

function onFastRefresh(
function handleSuccessfulHotUpdateWebpack(
dispatcher: Dispatcher,
sendMessage: (message: string) => void,
updatedModules: ReadonlyArray<string>
) {
resolvePendingHotUpdateWebpack()
dispatcher.onBuildOk()

reportHmrLatency(sendMessage, updatedModules)
Expand Down Expand Up @@ -281,12 +299,16 @@ function processMessage(
} else {
tryApplyUpdates(
function onBeforeHotUpdate(hasUpdates: boolean) {
onBeforeFastRefresh(dispatcher, hasUpdates)
handleBeforeHotUpdateWebpack(dispatcher, hasUpdates)
},
function onSuccessfulHotUpdate(webpackUpdatedModules: string[]) {
// Only dismiss it when we're sure it's a hot update.
// Otherwise it would flicker right before the reload.
onFastRefresh(dispatcher, sendMessage, webpackUpdatedModules)
handleSuccessfulHotUpdateWebpack(
dispatcher,
sendMessage,
webpackUpdatedModules
)
},
sendMessage,
dispatcher
Expand Down Expand Up @@ -320,6 +342,9 @@ function processMessage(
}
case HMR_ACTIONS_SENT_TO_BROWSER.BUILDING: {
startLatency = Date.now()
if (!process.env.TURBOPACK) {
setPendingHotUpdateWebpack()
}
console.log('[Fast Refresh] rebuilding')
break
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { urlToUrlWithoutFlightMarker } from '../app-router'
import { callServer } from '../../app-call-server'
import { PrefetchKind } from './router-reducer-types'
import { hexHash } from '../../../shared/lib/hash'
import { waitForWebpackRuntimeHotUpdate } from '../react-dev-overlay/app/hot-reloader-client'

export type FetchServerResponseResult = [
flightData: FlightData,
Expand Down Expand Up @@ -152,6 +153,14 @@ export async function fetchServerResponse(
return doMpaNavigation(responseUrl.toString())
}

// We may navigate to a page that requires a different Webpack runtime.
// In prod, every page will have the same Webpack runtime.
// In dev, the Webpack runtime is minimal for each page.
// We need to ensure the Webpack runtime is updated before executing client-side JS of the new page.
if (process.env.NODE_ENV !== 'production') {
await waitForWebpackRuntimeHotUpdate()
}

// Handle the `fetch` readable stream that can be unwrapped by `React.use`.
const [buildId, flightData]: NextFlightResponse = await createFromFetch(
Promise.resolve(res),
Expand Down

0 comments on commit 734e292

Please sign in to comment.