Skip to content

Commit

Permalink
feat(after): stabilize unstable_after (#73605)
Browse files Browse the repository at this point in the history
- rename `unstable_after` -> `after`
- remove `experimental.after` flag (and warn if it's present)

i separated the big find-and-replace into a separate commit so that it's
easier to see what else changed apart from that.

does not touch docs, that's in #73038
  • Loading branch information
lubieowoce authored Dec 6, 2024
1 parent 2772f8e commit 82e84e4
Show file tree
Hide file tree
Showing 101 changed files with 221 additions and 315 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub fn get_next_cjs_optimizer_rule(enable_mdx_rs: bool) -> ModuleRule {
"userAgent".into(),
"next/dist/server/web/spec-extension/user-agent".into(),
),
("unstable_after".into(), "next/dist/server/after".into()),
("after".into(), "next/dist/server/after".into()),
]),
},
)]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,7 @@ impl ReactServerComponentValidator {

invalid_client_imports: vec![JsWord::from("server-only"), JsWord::from("next/headers")],

invalid_client_lib_apis_mapping: [("next/server", vec!["unstable_after"])].into(),
invalid_client_lib_apis_mapping: [("next/server", vec!["after"])].into(),
imports: ImportMap::default(),
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/next/server.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export { userAgent } from 'next/dist/server/web/spec-extension/user-agent'
export { URLPattern } from 'next/dist/compiled/@edge-runtime/primitives/url'
export { ImageResponse } from 'next/dist/server/web/spec-extension/image-response'
export type { ImageResponseOptions } from 'next/dist/compiled/@vercel/og/types'
export { unstable_after } from 'next/dist/server/after'
export { after } from 'next/dist/server/after'
export { connection } from 'next/dist/server/request/connection'
export type { UnsafeUnwrappedSearchParams } from 'next/dist/server/request/search-params'
export type { UnsafeUnwrappedParams } from 'next/dist/server/request/params'
4 changes: 2 additions & 2 deletions packages/next/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const serverExports = {
.userAgent,
URLPattern: require('next/dist/server/web/spec-extension/url-pattern')
.URLPattern,
unstable_after: require('next/dist/server/after').unstable_after,
after: require('next/dist/server/after').after,
connection: require('next/dist/server/request/connection').connection,
}

Expand All @@ -26,5 +26,5 @@ exports.ImageResponse = serverExports.ImageResponse
exports.userAgentFromString = serverExports.userAgentFromString
exports.userAgent = serverExports.userAgent
exports.URLPattern = serverExports.URLPattern
exports.unstable_after = serverExports.unstable_after
exports.after = serverExports.after
exports.connection = serverExports.connection
3 changes: 0 additions & 3 deletions packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1271,7 +1271,6 @@ export default async function build(
)

const isAppDynamicIOEnabled = Boolean(config.experimental.dynamicIO)
const isAfterEnabled = Boolean(config.experimental.after)
const isAuthInterruptsEnabled = Boolean(
config.experimental.authInterrupts
)
Expand Down Expand Up @@ -2004,7 +2003,6 @@ export default async function build(
configFileName,
runtimeEnvConfig,
dynamicIO: isAppDynamicIOEnabled,
after: isAfterEnabled,
authInterrupts: isAuthInterruptsEnabled,
httpAgentOptions: config.httpAgentOptions,
locales: config.i18n?.locales,
Expand Down Expand Up @@ -2229,7 +2227,6 @@ export default async function build(
edgeInfo,
pageType,
dynamicIO: isAppDynamicIOEnabled,
after: isAfterEnabled,
authInterrupts: isAuthInterruptsEnabled,
cacheHandler: config.cacheHandler,
cacheHandlers: config.experimental.cacheHandlers,
Expand Down
6 changes: 0 additions & 6 deletions packages/next/src/build/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1215,7 +1215,6 @@ export async function buildAppStaticPaths({
page,
distDir,
dynamicIO,
after,
authInterrupts,
configFileName,
segments,
Expand All @@ -1233,7 +1232,6 @@ export async function buildAppStaticPaths({
dir: string
page: string
dynamicIO: boolean
after: boolean
authInterrupts: boolean
configFileName: string
segments: AppSegment[]
Expand Down Expand Up @@ -1317,7 +1315,6 @@ export async function buildAppStaticPaths({
supportsDynamicResponse: true,
isRevalidate: false,
experimental: {
after,
dynamicIO,
authInterrupts,
},
Expand Down Expand Up @@ -1500,7 +1497,6 @@ export async function isPageStatic({
edgeInfo,
pageType,
dynamicIO,
after,
authInterrupts,
originalAppPath,
isrFlushToDisk,
Expand All @@ -1516,7 +1512,6 @@ export async function isPageStatic({
page: string
distDir: string
dynamicIO: boolean
after: boolean
authInterrupts: boolean
configFileName: string
runtimeEnvConfig: any
Expand Down Expand Up @@ -1659,7 +1654,6 @@ export async function isPageStatic({
dir,
page,
dynamicIO,
after,
authInterrupts,
configFileName,
segments,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ export function getRender({
if (event?.waitUntil) {
// TODO(after):
// remove `internal_runWithWaitUntil` and the `internal-edge-wait-until` module
// when consumers switch to `unstable_after`.
// when consumers switch to `after`.
const waitUntilPromise = internal_getCurrentFunctionWaitUntil()
if (waitUntilPromise) {
event.waitUntil(waitUntilPromise)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,6 @@ export function getDefineEnv({
),
'process.env.__NEXT_PPR': isPPREnabled,
'process.env.__NEXT_DYNAMIC_IO': isDynamicIOEnabled,
'process.env.__NEXT_AFTER': config.experimental.after ?? false,
'process.env.NEXT_DEPLOYMENT_ID': config.deploymentId || false,
'process.env.__NEXT_FETCH_CACHE_KEY_PREFIX': fetchCacheKeyPrefix ?? '',
...(isTurbopack
Expand Down
1 change: 0 additions & 1 deletion packages/next/src/export/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,6 @@ async function exportAppImpl(
experimental: {
clientTraceMetadata: nextConfig.experimental.clientTraceMetadata,
expireTime: nextConfig.expireTime,
after: nextConfig.experimental.after ?? false,
dynamicIO: nextConfig.experimental.dynamicIO ?? false,
inlineCss: nextConfig.experimental.inlineCss ?? false,
authInterrupts: !!nextConfig.experimental.authInterrupts,
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/export/routes/app-route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export async function exportAppRoute(
htmlFilepath: string,
fileWriter: FileWriter,
experimental: Required<
Pick<ExperimentalConfig, 'after' | 'dynamicIO' | 'authInterrupts'>
Pick<ExperimentalConfig, 'dynamicIO' | 'authInterrupts'>
>,
buildId: string
): Promise<ExportRouteResult> {
Expand Down
13 changes: 2 additions & 11 deletions packages/next/src/server/after/after-context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe('AfterContext', () => {
let workAsyncStorage: WASMod['workAsyncStorage']
let workUnitAsyncStorage: WSMod['workUnitAsyncStorage']
let AfterContext: AfterContextMod['AfterContext']
let after: AfterMod['unstable_after']
let after: AfterMod['after']

beforeAll(async () => {
// @ts-expect-error
Expand All @@ -32,7 +32,7 @@ describe('AfterContext', () => {
AfterContext = AfterContextMod.AfterContext

const AfterMod = await import('./after')
after = AfterMod.unstable_after
after = AfterMod.after
})

const createRun =
Expand All @@ -53,7 +53,6 @@ describe('AfterContext', () => {
})

const afterContext = new AfterContext({
isEnabled: true,
waitUntil,
onClose,
onTaskError: undefined,
Expand Down Expand Up @@ -121,7 +120,6 @@ describe('AfterContext', () => {
})

const afterContext = new AfterContext({
isEnabled: true,
waitUntil,
onClose,
onTaskError: undefined,
Expand Down Expand Up @@ -170,7 +168,6 @@ describe('AfterContext', () => {
})

const afterContext = new AfterContext({
isEnabled: true,
waitUntil,
onClose,
onTaskError: undefined,
Expand Down Expand Up @@ -262,7 +259,6 @@ describe('AfterContext', () => {
})

const afterContext = new AfterContext({
isEnabled: true,
waitUntil,
onClose,
onTaskError: undefined,
Expand Down Expand Up @@ -323,7 +319,6 @@ describe('AfterContext', () => {
})

const afterContext = new AfterContext({
isEnabled: true,
waitUntil,
onClose,
onTaskError: undefined,
Expand Down Expand Up @@ -364,7 +359,6 @@ describe('AfterContext', () => {
const onTaskError = jest.fn()

const afterContext = new AfterContext({
isEnabled: true,
waitUntil,
onClose,
onTaskError,
Expand Down Expand Up @@ -428,7 +422,6 @@ describe('AfterContext', () => {
const onClose = jest.fn()

const afterContext = new AfterContext({
isEnabled: true,
waitUntil,
onClose,
onTaskError: undefined,
Expand Down Expand Up @@ -461,7 +454,6 @@ describe('AfterContext', () => {
})

const afterContext = new AfterContext({
isEnabled: true,
waitUntil,
onClose,
onTaskError: undefined,
Expand Down Expand Up @@ -507,7 +499,6 @@ describe('AfterContext', () => {
})

const afterContext = new AfterContext({
isEnabled: true,
waitUntil,
onClose,
onTaskError: undefined,
Expand Down
24 changes: 7 additions & 17 deletions packages/next/src/server/after/after-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
import { afterTaskAsyncStorage } from '../app-render/after-task-async-storage.external'

export type AfterContextOpts = {
isEnabled: boolean
waitUntil: RequestLifecycleOpts['waitUntil'] | undefined
onClose: RequestLifecycleOpts['onClose']
onTaskError: RequestLifecycleOpts['onAfterTaskError'] | undefined
Expand All @@ -23,22 +22,15 @@ export class AfterContext {
private waitUntil: RequestLifecycleOpts['waitUntil'] | undefined
private onClose: RequestLifecycleOpts['onClose']
private onTaskError: RequestLifecycleOpts['onAfterTaskError'] | undefined
public readonly isEnabled: boolean

private runCallbacksOnClosePromise: Promise<void> | undefined
private callbackQueue: PromiseQueue
private workUnitStores = new Set<WorkUnitStore>()

constructor({
waitUntil,
onClose,
onTaskError,
isEnabled,
}: AfterContextOpts) {
constructor({ waitUntil, onClose, onTaskError }: AfterContextOpts) {
this.waitUntil = waitUntil
this.onClose = onClose
this.onTaskError = onTaskError
this.isEnabled = isEnabled

this.callbackQueue = new PromiseQueue()
this.callbackQueue.pause()
Expand All @@ -56,14 +48,12 @@ export class AfterContext {
// TODO(after): implement tracing
this.addCallback(task)
} else {
throw new Error(
'`unstable_after()`: Argument must be a promise or a function'
)
throw new Error('`after()`: Argument must be a promise or a function')
}
}

private addCallback(callback: AfterCallback) {
// if something is wrong, throw synchronously, bubbling up to the `unstable_after` callsite.
// if something is wrong, throw synchronously, bubbling up to the `after` callsite.
if (!this.waitUntil) {
errorWaitUntilNotAvailable()
}
Expand Down Expand Up @@ -135,8 +125,8 @@ export class AfterContext {
// TODO(after): should we log this if we have a onTaskError callback?
console.error(
taskKind === 'promise'
? `A promise passed to \`unstable_after()\` rejected:`
: `An error occurred in a function passed to \`unstable_after()\`:`,
? `A promise passed to \`after()\` rejected:`
: `An error occurred in a function passed to \`after()\`:`,
error
)
if (this.onTaskError) {
Expand All @@ -146,7 +136,7 @@ export class AfterContext {
} catch (handlerError) {
console.error(
new InvariantError(
'`onTaskError` threw while handling an error thrown from an `unstable_after` task',
'`onTaskError` threw while handling an error thrown from an `after` task',
{
cause: handlerError,
}
Expand All @@ -159,6 +149,6 @@ export class AfterContext {

function errorWaitUntilNotAvailable(): never {
throw new Error(
'`unstable_after()` will not work correctly, because `waitUntil` is not available in the current environment.'
'`after()` will not work correctly, because `waitUntil` is not available in the current environment.'
)
}
12 changes: 3 additions & 9 deletions packages/next/src/server/after/after.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,16 @@ export type AfterCallback<T = unknown> = () => T | Promise<T>
/**
* This function allows you to schedule callbacks to be executed after the current request finishes.
*/
export function unstable_after<T>(task: AfterTask<T>): void {
export function after<T>(task: AfterTask<T>): void {
const workStore = workAsyncStorage.getStore()

if (!workStore) {
// TODO(after): the linked docs page talks about *dynamic* APIs, which unstable_after soon won't be anymore
// TODO(after): the linked docs page talks about *dynamic* APIs, which after soon won't be anymore
throw new Error(
'`unstable_after` was called outside a request scope. Read more: https://nextjs.org/docs/messages/next-dynamic-api-wrong-context'
'`after` was called outside a request scope. Read more: https://nextjs.org/docs/messages/next-dynamic-api-wrong-context'
)
}

const { afterContext } = workStore
if (!afterContext.isEnabled) {
throw new Error(
'`unstable_after` must be explicitly enabled by setting `experimental.after: true` in your next.config.js.'
)
}

return afterContext.after(task)
}
4 changes: 1 addition & 3 deletions packages/next/src/server/after/builtin-request-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,13 @@ export function getBuiltinRequestContext():
return ctx?.get()
}

/** This should be considered unstable until `unstable_after` is stablized. */
const NEXT_REQUEST_CONTEXT_SYMBOL = Symbol.for('@next/request-context')

type GlobalThisWithRequestContext = typeof globalThis & {
[NEXT_REQUEST_CONTEXT_SYMBOL]?: BuiltinRequestContext
}

/** A request context provided by the platform.
* It should be considered unstable until `unstable_after` is stablized. */
/** A request context provided by the platform. */
export type BuiltinRequestContext = {
get(): BuiltinRequestContextValue | undefined
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { afterTaskAsyncStorageInstance as afterTaskAsyncStorage } from './after-
import type { WorkUnitStore } from './work-unit-async-storage.external'

export interface AfterTaskStore {
/** The phase in which the topmost `unstable_after` was called.
/** The phase in which the topmost `after` was called.
*
* NOTE: Can be undefined when running `generateStaticParams`,
* where we only have a `workStore`, no `workUnitStore`.
Expand Down
1 change: 0 additions & 1 deletion packages/next/src/server/app-render/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,6 @@ export interface RenderOptsPartial {
isRoutePPREnabled?: boolean
expireTime: ExpireTime | undefined
clientTraceMetadata: string[] | undefined
after: boolean
dynamicIO: boolean
inlineCss: boolean
authInterrupts: boolean
Expand Down
Loading

0 comments on commit 82e84e4

Please sign in to comment.