Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(after): stabilize unstable_after #73605

Merged
merged 4 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -180,7 +180,6 @@ export interface RenderOptsPartial {
isRoutePPREnabled?: boolean
expireTime: ExpireTime | undefined
clientTraceMetadata: string[] | undefined
after: boolean
dynamicIO: boolean
inlineCss: boolean
authInterrupts: boolean
Expand Down
Loading
Loading