Skip to content

Commit 0196e56

Browse files
committed
Implement exotically async dynamic APIs
1 parent 54fbece commit 0196e56

File tree

31 files changed

+1681
-46
lines changed

31 files changed

+1681
-46
lines changed

packages/next/headers.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from './dist/client/components/headers'
2+
export * from './dist/server/request/cookies'

packages/next/headers.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
module.exports = require('./dist/client/components/headers')
2+
module.exports.cookies = require('./dist/server/request/cookies').cookies

packages/next/src/api/headers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from '../client/components/headers'
2+
export * from '../server/request/cookies'

packages/next/src/client/components/headers.ts

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
1-
import {
2-
type ReadonlyRequestCookies,
3-
RequestCookiesAdapter,
4-
} from '../../server/web/spec-extension/adapters/request-cookies'
51
import { HeadersAdapter } from '../../server/web/spec-extension/adapters/headers'
6-
import { RequestCookies } from '../../server/web/spec-extension/cookies'
7-
import { actionAsyncStorage } from './action-async-storage.external'
82
import { DraftMode } from './draft-mode'
93
import { trackDynamicDataAccessed } from '../../server/app-render/dynamic-rendering'
104
import { staticGenerationAsyncStorage } from './static-generation-async-storage.external'
@@ -36,32 +30,6 @@ export function headers() {
3630
return getExpectedRequestStore(callingExpression).headers
3731
}
3832

39-
export function cookies() {
40-
const callingExpression = 'cookies'
41-
const staticGenerationStore = staticGenerationAsyncStorage.getStore()
42-
43-
if (staticGenerationStore) {
44-
if (staticGenerationStore.forceStatic) {
45-
// When we are forcing static we don't mark this as a Dynamic read and we return an empty cookies object
46-
return RequestCookiesAdapter.seal(new RequestCookies(new Headers({})))
47-
} else {
48-
// We will return a real headers object below so we mark this call as reading from a dynamic data source
49-
trackDynamicDataAccessed(staticGenerationStore, callingExpression)
50-
}
51-
}
52-
53-
const requestStore = getExpectedRequestStore(callingExpression)
54-
55-
const asyncActionStore = actionAsyncStorage.getStore()
56-
if (asyncActionStore?.isAction || asyncActionStore?.isAppRoute) {
57-
// We can't conditionally return different types here based on the context.
58-
// To avoid confusion, we always return the readonly type here.
59-
return requestStore.mutableCookies as unknown as ReadonlyRequestCookies
60-
}
61-
62-
return requestStore.cookies
63-
}
64-
6533
export function draftMode() {
6634
const callingExpression = 'draftMode'
6735
const requestStore = getExpectedRequestStore(callingExpression)

packages/next/src/server/app-render/dynamic-rendering.ts

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ export function markCurrentScopeAsDynamic(
107107
// current render because something dynamic is being used.
108108
// This won't throw so we still need to fall through to determine if/how we handle
109109
// this specific dynamic request.
110-
abortRSCRenderWithTracking(
110+
abortRenderWithTracking(
111111
prerenderStore.controller,
112112
store.route,
113113
expression
@@ -120,9 +120,9 @@ export function markCurrentScopeAsDynamic(
120120
errorWithTracking(prerenderStore.dynamicTracking, store.route, expression)
121121
} else {
122122
postponeWithTracking(
123-
prerenderStore.dynamicTracking,
124123
store.route,
125-
expression
124+
expression,
125+
prerenderStore.dynamicTracking
126126
)
127127
}
128128
} else {
@@ -171,7 +171,7 @@ export function trackDynamicDataAccessed(
171171
// current render because something dynamic is being used.
172172
// This won't throw so we still need to fall through to determine if/how we handle
173173
// this specific dynamic request.
174-
abortRSCRenderWithTracking(
174+
abortRenderWithTracking(
175175
prerenderStore.controller,
176176
store.route,
177177
expression
@@ -184,9 +184,9 @@ export function trackDynamicDataAccessed(
184184
errorWithTracking(prerenderStore.dynamicTracking, store.route, expression)
185185
} else {
186186
postponeWithTracking(
187-
prerenderStore.dynamicTracking,
188187
store.route,
189-
expression
188+
expression,
189+
prerenderStore.dynamicTracking
190190
)
191191
}
192192
} else {
@@ -205,6 +205,75 @@ export function trackDynamicDataAccessed(
205205
}
206206
}
207207

208+
export function interruptStaticGeneration(
209+
expression: string,
210+
store: StaticGenerationStore
211+
): never {
212+
store.revalidate = 0
213+
214+
// We aren't prerendering but we are generating a static page. We need to bail out of static generation
215+
const err = new DynamicServerError(
216+
`Route ${store.route} couldn't be rendered statically because it used \`${expression}\`. See more info here: https://nextjs.org/docs/messages/dynamic-server-error`
217+
)
218+
store.dynamicUsageDescription = expression
219+
store.dynamicUsageStack = err.stack
220+
221+
throw err
222+
}
223+
224+
export function trackDynamicDataInDynamicRender(store: StaticGenerationStore) {
225+
store.revalidate = 0
226+
}
227+
228+
export function abortAndErrorOnSynchronousDynamicDataAccess(
229+
controller: AbortController,
230+
route: string,
231+
expression: string,
232+
dynamicTracking: null | DynamicTrackingState
233+
): never {
234+
abortRenderWithTracking(controller, route, expression)
235+
errorWithTracking(dynamicTracking, route, expression)
236+
}
237+
238+
export function errorOnSynchronousDynamicDataAccess(
239+
route: string,
240+
expression: string,
241+
controller: null | AbortController,
242+
dynamicTracking: null | DynamicTrackingState
243+
): never {
244+
const reason =
245+
`Route ${route} needs to bail out of prerendering at this point because it used ${expression}. ` +
246+
`React throws this special object to indicate where. It should not be caught by ` +
247+
`your own try/catch. Learn more: https://nextjs.org/docs/messages/ppr-caught-error`
248+
249+
const error = createPrerenderInterruptedError(reason)
250+
251+
if (controller) {
252+
if (hasPostpone) {
253+
try {
254+
React.unstable_postpone(NEXT_PRERENDER_INTERRUPTED)
255+
} catch (e) {
256+
controller.abort(e)
257+
}
258+
} else {
259+
controller.abort(error)
260+
}
261+
}
262+
263+
if (dynamicTracking) {
264+
dynamicTracking.dynamicAccesses.push({
265+
// When we aren't debugging, we don't need to create another error for the
266+
// stack trace.
267+
stack: dynamicTracking.isDebugDynamicAccesses
268+
? new Error().stack
269+
: undefined,
270+
expression,
271+
})
272+
}
273+
274+
throw error
275+
}
276+
208277
/**
209278
* This component will call `React.postpone` that throws the postponed error.
210279
*/
@@ -215,7 +284,7 @@ type PostponeProps = {
215284
export function Postpone({ reason, route }: PostponeProps): never {
216285
const prerenderStore = prerenderAsyncStorage.getStore()
217286
const dynamicTracking = prerenderStore?.dynamicTracking || null
218-
postponeWithTracking(dynamicTracking, route, reason)
287+
postponeWithTracking(route, reason, dynamicTracking)
219288
}
220289

221290
function errorWithTracking(
@@ -241,10 +310,10 @@ function errorWithTracking(
241310
throw createPrerenderInterruptedError(reason)
242311
}
243312

244-
function postponeWithTracking(
245-
dynamicTracking: null | DynamicTrackingState,
313+
export function postponeWithTracking(
246314
route: string,
247-
expression: string
315+
expression: string,
316+
dynamicTracking: null | DynamicTrackingState
248317
): never {
249318
assertPostpone()
250319
if (dynamicTracking) {
@@ -281,7 +350,7 @@ export function isRenderInterruptedError(error: unknown) {
281350
)
282351
}
283352

284-
function abortRSCRenderWithTracking(
353+
function abortRenderWithTracking(
285354
controller: AbortController,
286355
route: string,
287356
expression: string

0 commit comments

Comments
 (0)