Skip to content

Commit

Permalink
Implement exotically async dynamic APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
gnoff committed Aug 25, 2024
1 parent 54fbece commit 0196e56
Show file tree
Hide file tree
Showing 31 changed files with 1,681 additions and 46 deletions.
1 change: 1 addition & 0 deletions packages/next/headers.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './dist/client/components/headers'
export * from './dist/server/request/cookies'
1 change: 1 addition & 0 deletions packages/next/headers.js
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
module.exports = require('./dist/client/components/headers')
module.exports.cookies = require('./dist/server/request/cookies').cookies
1 change: 1 addition & 0 deletions packages/next/src/api/headers.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from '../client/components/headers'
export * from '../server/request/cookies'
32 changes: 0 additions & 32 deletions packages/next/src/client/components/headers.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import {
type ReadonlyRequestCookies,
RequestCookiesAdapter,
} from '../../server/web/spec-extension/adapters/request-cookies'
import { HeadersAdapter } from '../../server/web/spec-extension/adapters/headers'
import { RequestCookies } from '../../server/web/spec-extension/cookies'
import { actionAsyncStorage } from './action-async-storage.external'
import { DraftMode } from './draft-mode'
import { trackDynamicDataAccessed } from '../../server/app-render/dynamic-rendering'
import { staticGenerationAsyncStorage } from './static-generation-async-storage.external'
Expand Down Expand Up @@ -36,32 +30,6 @@ export function headers() {
return getExpectedRequestStore(callingExpression).headers
}

export function cookies() {
const callingExpression = 'cookies'
const staticGenerationStore = staticGenerationAsyncStorage.getStore()

if (staticGenerationStore) {
if (staticGenerationStore.forceStatic) {
// When we are forcing static we don't mark this as a Dynamic read and we return an empty cookies object
return RequestCookiesAdapter.seal(new RequestCookies(new Headers({})))
} else {
// We will return a real headers object below so we mark this call as reading from a dynamic data source
trackDynamicDataAccessed(staticGenerationStore, callingExpression)
}
}

const requestStore = getExpectedRequestStore(callingExpression)

const asyncActionStore = actionAsyncStorage.getStore()
if (asyncActionStore?.isAction || asyncActionStore?.isAppRoute) {
// We can't conditionally return different types here based on the context.
// To avoid confusion, we always return the readonly type here.
return requestStore.mutableCookies as unknown as ReadonlyRequestCookies
}

return requestStore.cookies
}

export function draftMode() {
const callingExpression = 'draftMode'
const requestStore = getExpectedRequestStore(callingExpression)
Expand Down
91 changes: 80 additions & 11 deletions packages/next/src/server/app-render/dynamic-rendering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export function markCurrentScopeAsDynamic(
// current render because something dynamic is being used.
// This won't throw so we still need to fall through to determine if/how we handle
// this specific dynamic request.
abortRSCRenderWithTracking(
abortRenderWithTracking(
prerenderStore.controller,
store.route,
expression
Expand All @@ -120,9 +120,9 @@ export function markCurrentScopeAsDynamic(
errorWithTracking(prerenderStore.dynamicTracking, store.route, expression)
} else {
postponeWithTracking(
prerenderStore.dynamicTracking,
store.route,
expression
expression,
prerenderStore.dynamicTracking
)
}
} else {
Expand Down Expand Up @@ -171,7 +171,7 @@ export function trackDynamicDataAccessed(
// current render because something dynamic is being used.
// This won't throw so we still need to fall through to determine if/how we handle
// this specific dynamic request.
abortRSCRenderWithTracking(
abortRenderWithTracking(
prerenderStore.controller,
store.route,
expression
Expand All @@ -184,9 +184,9 @@ export function trackDynamicDataAccessed(
errorWithTracking(prerenderStore.dynamicTracking, store.route, expression)
} else {
postponeWithTracking(
prerenderStore.dynamicTracking,
store.route,
expression
expression,
prerenderStore.dynamicTracking
)
}
} else {
Expand All @@ -205,6 +205,75 @@ export function trackDynamicDataAccessed(
}
}

export function interruptStaticGeneration(
expression: string,
store: StaticGenerationStore
): never {
store.revalidate = 0

// We aren't prerendering but we are generating a static page. We need to bail out of static generation
const err = new DynamicServerError(
`Route ${store.route} couldn't be rendered statically because it used \`${expression}\`. See more info here: https://nextjs.org/docs/messages/dynamic-server-error`
)
store.dynamicUsageDescription = expression
store.dynamicUsageStack = err.stack

throw err
}

export function trackDynamicDataInDynamicRender(store: StaticGenerationStore) {
store.revalidate = 0
}

export function abortAndErrorOnSynchronousDynamicDataAccess(
controller: AbortController,
route: string,
expression: string,
dynamicTracking: null | DynamicTrackingState
): never {
abortRenderWithTracking(controller, route, expression)
errorWithTracking(dynamicTracking, route, expression)
}

export function errorOnSynchronousDynamicDataAccess(
route: string,
expression: string,
controller: null | AbortController,
dynamicTracking: null | DynamicTrackingState
): never {
const reason =
`Route ${route} needs to bail out of prerendering at this point because it used ${expression}. ` +
`React throws this special object to indicate where. It should not be caught by ` +
`your own try/catch. Learn more: https://nextjs.org/docs/messages/ppr-caught-error`

const error = createPrerenderInterruptedError(reason)

if (controller) {
if (hasPostpone) {
try {
React.unstable_postpone(NEXT_PRERENDER_INTERRUPTED)
} catch (e) {
controller.abort(e)
}
} else {
controller.abort(error)
}
}

if (dynamicTracking) {
dynamicTracking.dynamicAccesses.push({
// When we aren't debugging, we don't need to create another error for the
// stack trace.
stack: dynamicTracking.isDebugDynamicAccesses
? new Error().stack
: undefined,
expression,
})
}

throw error
}

/**
* This component will call `React.postpone` that throws the postponed error.
*/
Expand All @@ -215,7 +284,7 @@ type PostponeProps = {
export function Postpone({ reason, route }: PostponeProps): never {
const prerenderStore = prerenderAsyncStorage.getStore()
const dynamicTracking = prerenderStore?.dynamicTracking || null
postponeWithTracking(dynamicTracking, route, reason)
postponeWithTracking(route, reason, dynamicTracking)
}

function errorWithTracking(
Expand All @@ -241,10 +310,10 @@ function errorWithTracking(
throw createPrerenderInterruptedError(reason)
}

function postponeWithTracking(
dynamicTracking: null | DynamicTrackingState,
export function postponeWithTracking(
route: string,
expression: string
expression: string,
dynamicTracking: null | DynamicTrackingState
): never {
assertPostpone()
if (dynamicTracking) {
Expand Down Expand Up @@ -281,7 +350,7 @@ export function isRenderInterruptedError(error: unknown) {
)
}

function abortRSCRenderWithTracking(
function abortRenderWithTracking(
controller: AbortController,
route: string,
expression: string
Expand Down
Loading

0 comments on commit 0196e56

Please sign in to comment.