Skip to content

Commit 098749c

Browse files
committed
Re-implement HMR refresh reducer
Follow up to vercel#84426. This re-implements the HMR refresh reducer like we did for the normal refresh reducer. The main difference is that an HMR is triggered by a code change, so it needs to purge the entire prefetch cache, whereas a normal refresh is more targeted.
1 parent 594ead4 commit 098749c

File tree

1 file changed

+19
-105
lines changed

1 file changed

+19
-105
lines changed

packages/next/src/client/components/router-reducer/reducers/hmr-refresh-reducer.ts

Lines changed: 19 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,120 +1,34 @@
1-
import {
2-
fetchServerResponse,
3-
type FetchServerResponseResult,
4-
} from '../fetch-server-response'
5-
import { createHrefFromUrl } from '../create-href-from-url'
6-
import { applyRouterStatePatchToTree } from '../apply-router-state-patch-to-tree'
7-
import { isNavigatingToNewRootLayout } from '../is-navigating-to-new-root-layout'
81
import type {
2+
Mutable,
93
ReadonlyReducerState,
104
ReducerState,
115
HmrRefreshAction,
12-
Mutable,
136
} from '../router-reducer-types'
14-
import { handleExternalUrl } from './navigate-reducer'
15-
import { handleMutable } from '../handle-mutable'
16-
import { applyFlightData } from '../apply-flight-data'
17-
import type { CacheNode } from '../../../../shared/lib/app-router-types'
18-
import { createEmptyCacheNode } from '../../app-router'
19-
import { handleSegmentMismatch } from '../handle-segment-mismatch'
20-
import { hasInterceptionRouteInCurrentTree } from './has-interception-route-in-current-tree'
7+
import { handleNavigationResult } from './navigate-reducer'
8+
import { refresh as refreshUsingSegmentCache } from '../../segment-cache/navigation'
9+
import { revalidateEntireCache } from '../../segment-cache/cache'
2110

22-
// A version of refresh reducer that keeps the cache around instead of wiping all of it.
23-
function hmrRefreshReducerImpl(
11+
export function hmrRefreshReducerImpl(
2412
state: ReadonlyReducerState,
2513
action: HmrRefreshAction
2614
): ReducerState {
27-
const { origin } = action
28-
const mutable: Mutable = {}
29-
const href = state.canonicalUrl
15+
// There was a code change. Purge the entire prefetch cache. This is the main
16+
// difference between an HMR refresh and a normal refresh.
17+
revalidateEntireCache(state.nextUrl, state.tree)
18+
19+
const currentUrl = new URL(state.canonicalUrl, action.origin)
20+
const result = refreshUsingSegmentCache(
21+
currentUrl,
22+
state.tree,
23+
state.nextUrl,
24+
state.renderedSearch,
25+
state.canonicalUrl
26+
)
3027

28+
const mutable: Mutable = {}
3129
mutable.preserveCustomHistoryState = false
3230

33-
const cache: CacheNode = createEmptyCacheNode()
34-
// If the current tree was intercepted, the nextUrl should be included in the request.
35-
// This is to ensure that the refresh request doesn't get intercepted, accidentally triggering the interception route.
36-
const includeNextUrl = hasInterceptionRouteInCurrentTree(state.tree)
37-
38-
// TODO-APP: verify that `href` is not an external url.
39-
// Fetch data from the root of the tree.
40-
const navigatedAt = Date.now()
41-
cache.lazyData = fetchServerResponse(new URL(href, origin), {
42-
flightRouterState: [state.tree[0], state.tree[1], state.tree[2], 'refetch'],
43-
nextUrl: includeNextUrl ? state.nextUrl : null,
44-
isHmrRefresh: true,
45-
})
46-
47-
return cache.lazyData.then(
48-
(result: FetchServerResponseResult) => {
49-
// Handle case when navigating to page in `pages` from `app`
50-
if (typeof result === 'string') {
51-
return handleExternalUrl(
52-
state,
53-
mutable,
54-
result,
55-
state.pushRef.pendingPush
56-
)
57-
}
58-
59-
const { flightData, canonicalUrl, renderedSearch } = result
60-
61-
// Remove cache.lazyData as it has been resolved at this point.
62-
cache.lazyData = null
63-
64-
let currentTree = state.tree
65-
let currentCache = state.cache
66-
67-
for (const normalizedFlightData of flightData) {
68-
const { tree: treePatch, isRootRender } = normalizedFlightData
69-
if (!isRootRender) {
70-
// TODO-APP: handle this case better
71-
console.log('REFRESH FAILED')
72-
return state
73-
}
74-
75-
const newTree = applyRouterStatePatchToTree(
76-
// TODO-APP: remove ''
77-
[''],
78-
currentTree,
79-
treePatch,
80-
state.canonicalUrl
81-
)
82-
83-
if (newTree === null) {
84-
return handleSegmentMismatch(state, action, treePatch)
85-
}
86-
87-
if (isNavigatingToNewRootLayout(currentTree, newTree)) {
88-
return handleExternalUrl(
89-
state,
90-
mutable,
91-
href,
92-
state.pushRef.pendingPush
93-
)
94-
}
95-
96-
const applied = applyFlightData(
97-
navigatedAt,
98-
currentCache,
99-
cache,
100-
normalizedFlightData
101-
)
102-
103-
if (applied) {
104-
mutable.cache = cache
105-
currentCache = cache
106-
}
107-
108-
mutable.patchedTree = newTree
109-
mutable.renderedSearch = renderedSearch
110-
mutable.canonicalUrl = createHrefFromUrl(canonicalUrl)
111-
112-
currentTree = newTree
113-
}
114-
return handleMutable(state, mutable)
115-
},
116-
() => state
117-
)
31+
return handleNavigationResult(currentUrl, state, mutable, false, result)
11832
}
11933

12034
function hmrRefreshReducerNoop(

0 commit comments

Comments
 (0)