forked from vercel/next.js
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce dynamicIO experiment (vercel#67571)
`nextConfig.experimental.dynamicIO: boolean` is a new experimental flag that changes the static generation bailout heuristic and PPR postpone heuristic to exclude async work that is not resolvable in the current Task's microtask queue from prerenders. functionally it means that in this mode fetches and db queries will now not be prerenderable unless you opt into it with a caching API like `unstable_cache()` or the fetch `cache: 'force-cache'` option.
- Loading branch information
Showing
63 changed files
with
3,224 additions
and
504 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,9 @@ | ||
import type { NextConfig } from '../server/config-shared' | ||
|
||
export function needsExperimentalReact(config: NextConfig) { | ||
return Boolean(config.experimental?.ppr || config.experimental?.taint) | ||
return Boolean( | ||
config.experimental?.ppr || | ||
config.experimental?.taint || | ||
config.experimental?.dynamicIO | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
179 changes: 179 additions & 0 deletions
179
packages/next/src/server/app-render/app-render-prerender-utils.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
import { InvariantError } from '../../shared/lib/invariant-error' | ||
|
||
/** | ||
* This is a utility function to make scheduling sequential tasks that run back to back easier. | ||
* We schedule on the same queue (setImmediate) at the same time to ensure no other events can sneak in between. | ||
*/ | ||
export function prerenderAndAbortInSequentialTasks<R>( | ||
prerender: () => Promise<R>, | ||
abort: () => void | ||
): Promise<R> { | ||
if (process.env.NEXT_RUNTIME === 'edge') { | ||
throw new InvariantError( | ||
'`prerenderAndAbortInSequentialTasks` should not be called in edge runtime.' | ||
) | ||
} else { | ||
return new Promise((resolve, reject) => { | ||
let pendingResult: Promise<R> | ||
setImmediate(() => { | ||
try { | ||
pendingResult = prerender() | ||
} catch (err) { | ||
reject(err) | ||
} | ||
}) | ||
setImmediate(() => { | ||
abort() | ||
resolve(pendingResult) | ||
}) | ||
}) | ||
} | ||
} | ||
|
||
// React's RSC prerender function will emit an incomplete flight stream when using `prerender`. If the connection | ||
// closes then whatever hanging chunks exist will be errored. This is because prerender (an experimental feature) | ||
// has not yet implemented a concept of resume. For now we will simulate a paused connection by wrapping the stream | ||
// in one that doesn't close even when the underlying is complete. | ||
export class ReactServerResult { | ||
private _stream: null | ReadableStream<Uint8Array> | ||
|
||
constructor(stream: ReadableStream<Uint8Array>) { | ||
this._stream = stream | ||
} | ||
|
||
tee() { | ||
if (this._stream === null) { | ||
throw new Error( | ||
'Cannot tee a ReactServerResult that has already been consumed' | ||
) | ||
} | ||
const tee = this._stream.tee() | ||
this._stream = tee[0] | ||
return tee[1] | ||
} | ||
|
||
consume() { | ||
if (this._stream === null) { | ||
throw new Error( | ||
'Cannot consume a ReactServerResult that has already been consumed' | ||
) | ||
} | ||
const stream = this._stream | ||
this._stream = null | ||
return stream | ||
} | ||
} | ||
|
||
export type ReactServerPrerenderResolveToType = { | ||
prelude: ReadableStream<Uint8Array> | ||
} | ||
|
||
export async function createReactServerPrerenderResult( | ||
underlying: Promise<ReactServerPrerenderResolveToType> | ||
): Promise<ReactServerPrerenderResult> { | ||
const chunks: Array<Uint8Array> = [] | ||
const { prelude } = await underlying | ||
const reader = prelude.getReader() | ||
while (true) { | ||
const { done, value } = await reader.read() | ||
if (done) { | ||
break | ||
} else { | ||
chunks.push(value) | ||
} | ||
} | ||
return new ReactServerPrerenderResult(chunks) | ||
} | ||
|
||
export async function createReactServerPrerenderResultFromRender( | ||
underlying: ReadableStream<Uint8Array> | ||
): Promise<ReactServerPrerenderResult> { | ||
const chunks: Array<Uint8Array> = [] | ||
const reader = underlying.getReader() | ||
while (true) { | ||
const { done, value } = await reader.read() | ||
if (done) { | ||
break | ||
} else { | ||
chunks.push(value) | ||
} | ||
} | ||
return new ReactServerPrerenderResult(chunks) | ||
} | ||
export class ReactServerPrerenderResult { | ||
private _chunks: null | Array<Uint8Array> | ||
|
||
private assertChunks(expression: string): Array<Uint8Array> { | ||
if (this._chunks === null) { | ||
throw new InvariantError( | ||
`Cannot \`${expression}\` on a ReactServerPrerenderResult that has already been consumed.` | ||
) | ||
} | ||
return this._chunks | ||
} | ||
|
||
private consumeChunks(expression: string): Array<Uint8Array> { | ||
const chunks = this.assertChunks(expression) | ||
this.consume() | ||
return chunks | ||
} | ||
|
||
consume(): void { | ||
this._chunks = null | ||
} | ||
|
||
constructor(chunks: Array<Uint8Array>) { | ||
this._chunks = chunks | ||
} | ||
|
||
asUnclosingStream(): ReadableStream<Uint8Array> { | ||
const chunks = this.assertChunks('asUnclosingStream()') | ||
return createUnclosingStream(chunks) | ||
} | ||
|
||
consumeAsUnclosingStream(): ReadableStream<Uint8Array> { | ||
const chunks = this.consumeChunks('consumeAsUnclosingStream()') | ||
return createUnclosingStream(chunks) | ||
} | ||
|
||
asStream(): ReadableStream<Uint8Array> { | ||
const chunks = this.assertChunks('asStream()') | ||
return createClosingStream(chunks) | ||
} | ||
|
||
consumeAsStream(): ReadableStream<Uint8Array> { | ||
const chunks = this.consumeChunks('consumeAsStream()') | ||
return createClosingStream(chunks) | ||
} | ||
} | ||
|
||
function createUnclosingStream( | ||
chunks: Array<Uint8Array> | ||
): ReadableStream<Uint8Array> { | ||
let i = 0 | ||
return new ReadableStream({ | ||
async pull(controller) { | ||
if (i < chunks.length) { | ||
controller.enqueue(chunks[i++]) | ||
} | ||
// we intentionally keep the stream open. The consumer will clear | ||
// out chunks once finished and the remaining memory will be GC'd | ||
// when this object goes out of scope | ||
}, | ||
}) | ||
} | ||
|
||
function createClosingStream( | ||
chunks: Array<Uint8Array> | ||
): ReadableStream<Uint8Array> { | ||
let i = 0 | ||
return new ReadableStream({ | ||
async pull(controller) { | ||
if (i < chunks.length) { | ||
controller.enqueue(chunks[i++]) | ||
} else { | ||
controller.close() | ||
} | ||
}, | ||
}) | ||
} |
Oops, something went wrong.