From 7191c0421c02cdbe4c83c6c328726b5217d5900d Mon Sep 17 00:00:00 2001 From: Vordgi Date: Fri, 17 May 2024 21:45:21 +0400 Subject: [PATCH] add page context logic --- .../next/src/build/templates/app-route.ts | 9 +++- .../src/client/components/get-page-context.ts | 5 ++ .../page-context-async-storage-instance.ts | 5 ++ .../page-context-async-storage.external.ts | 10 ++++ .../next/src/server/app-render/app-render.tsx | 51 ++++++++++++++++++- .../next/src/server/app-render/entry-base.ts | 2 + .../page-context-async-storage-wrapper.ts | 21 ++++++++ .../future/route-modules/app-route/module.ts | 6 +++ .../next/src/server/typescript/constant.ts | 1 + 9 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 packages/next/src/client/components/get-page-context.ts create mode 100644 packages/next/src/client/components/page-context-async-storage-instance.ts create mode 100644 packages/next/src/client/components/page-context-async-storage.external.ts create mode 100644 packages/next/src/server/async-storage/page-context-async-storage-wrapper.ts diff --git a/packages/next/src/build/templates/app-route.ts b/packages/next/src/build/templates/app-route.ts index 4aaea88b9a4d9..eed7dd63f5d0c 100644 --- a/packages/next/src/build/templates/app-route.ts +++ b/packages/next/src/build/templates/app-route.ts @@ -32,8 +32,12 @@ const routeModule = new AppRouteRouteModule({ // Pull out the exports that we need to expose from the module. This should // be eliminated when we've moved the other routes to the new format. These // are used to hook into the route. -const { requestAsyncStorage, staticGenerationAsyncStorage, serverHooks } = - routeModule +const { + requestAsyncStorage, + staticGenerationAsyncStorage, + pageContextAsyncStorage, + serverHooks, +} = routeModule const originalPathname = 'VAR_ORIGINAL_PATHNAME' @@ -45,6 +49,7 @@ export { routeModule, requestAsyncStorage, staticGenerationAsyncStorage, + pageContextAsyncStorage, serverHooks, originalPathname, patchFetch, diff --git a/packages/next/src/client/components/get-page-context.ts b/packages/next/src/client/components/get-page-context.ts new file mode 100644 index 0000000000000..43074cab99499 --- /dev/null +++ b/packages/next/src/client/components/get-page-context.ts @@ -0,0 +1,5 @@ +import { pageContextAsyncStorage } from './page-context-async-storage.external' + +export const getPageContext = () => { + return pageContextAsyncStorage.getStore() as T +} diff --git a/packages/next/src/client/components/page-context-async-storage-instance.ts b/packages/next/src/client/components/page-context-async-storage-instance.ts new file mode 100644 index 0000000000000..2103411fe47bc --- /dev/null +++ b/packages/next/src/client/components/page-context-async-storage-instance.ts @@ -0,0 +1,5 @@ +import type { PageContextAsyncStorage } from './page-context-async-storage.external' +import { createAsyncLocalStorage } from './async-local-storage' + +export const pageContextAsyncStorage: PageContextAsyncStorage = + createAsyncLocalStorage() diff --git a/packages/next/src/client/components/page-context-async-storage.external.ts b/packages/next/src/client/components/page-context-async-storage.external.ts new file mode 100644 index 0000000000000..123ba7c461f31 --- /dev/null +++ b/packages/next/src/client/components/page-context-async-storage.external.ts @@ -0,0 +1,10 @@ +import type { AsyncLocalStorage } from 'async_hooks' + +// Share the instance module in the next-shared layer +import { pageContextAsyncStorage } from './page-context-async-storage-instance' with { 'turbopack-transition': 'next-shared' } + +export type PageStore = { [key: string]: unknown } + +export type PageContextAsyncStorage = AsyncLocalStorage + +export { pageContextAsyncStorage } diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index 95ec13aa14683..d5edcffc5c739 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -11,7 +11,7 @@ import type { import type { StaticGenerationStore } from '../../client/components/static-generation-async-storage.external' import type { RequestStore } from '../../client/components/request-async-storage.external' import type { NextParsedUrlQuery } from '../request-meta' -import type { LoaderTree } from '../lib/app-dir-module' +import { getLayoutOrPageModule, type LoaderTree } from '../lib/app-dir-module' import type { AppPageModule } from '../future/route-modules/app-page/module' import type { ClientReferenceManifest } from '../../build/webpack/plugins/flight-manifest-plugin' import type { Revalidate } from '../lib/revalidate' @@ -113,6 +113,7 @@ import { import { createServerModuleMap } from './action-utils' import { isNodeNextRequest } from '../base-http/helpers' import { parseParameter } from '../../shared/lib/router/utils/route-regex' +import { PageContextAsyncStorageWrapper } from '../async-storage/page-context-async-storage-wrapper' export type GetDynamicParamFromSegment = ( // [slug] / [[slug]] / [...slug] @@ -1487,6 +1488,52 @@ export type AppPageRender = ( renderOpts: RenderOpts ) => Promise> +async function getPage(treeArg: any): Promise { + const [, parallelRoutes, { page }] = treeArg + const isPage = typeof page !== 'undefined' + if (isPage) { + const [mod] = await getLayoutOrPageModule(treeArg) + return mod + } + for (const key in parallelRoutes) { + const childTree = parallelRoutes[key] + const mod = await getPage(childTree) + if (mod) return mod + } +} + +async function renderToHTMLOrFlightImplWrapper( + req: BaseNextRequest, + res: BaseNextResponse, + pagePath: string, + query: NextParsedUrlQuery, + renderOpts: RenderOpts, + baseCtx: AppRenderBaseContext, + requestEndedState: { ended?: boolean } +) { + const pageModule = await getPage(renderOpts.ComponentMod.tree) + + let data = null + if (typeof pageModule.createPageContext === 'function') { + data = await pageModule.createPageContext({ params: renderOpts.params }) + } + + return PageContextAsyncStorageWrapper.wrap( + renderOpts.ComponentMod.pageContextAsyncStorage, + data, + () => + renderToHTMLOrFlightImpl( + req, + res, + pagePath, + query, + renderOpts, + baseCtx, + requestEndedState + ) + ) +} + export const renderToHTMLOrFlight: AppPageRender = ( req, res, @@ -1509,7 +1556,7 @@ export const renderToHTMLOrFlight: AppPageRender = ( requestEndedState: { ended: false }, }, (staticGenerationStore) => - renderToHTMLOrFlightImpl( + renderToHTMLOrFlightImplWrapper( req, res, pagePath, diff --git a/packages/next/src/server/app-render/entry-base.ts b/packages/next/src/server/app-render/entry-base.ts index 52decc09a7219..8f5de18951b5d 100644 --- a/packages/next/src/server/app-render/entry-base.ts +++ b/packages/next/src/server/app-render/entry-base.ts @@ -11,6 +11,7 @@ import LayoutRouter from '../../client/components/layout-router' import RenderFromTemplateContext from '../../client/components/render-from-template-context' import { staticGenerationAsyncStorage } from '../../client/components/static-generation-async-storage.external' import { requestAsyncStorage } from '../../client/components/request-async-storage.external' +import { pageContextAsyncStorage } from '../../client/components/page-context-async-storage.external' import { actionAsyncStorage } from '../../client/components/action-async-storage.external' import { ClientPageRoot } from '../../client/components/client-page' import { @@ -43,6 +44,7 @@ export { RenderFromTemplateContext, staticGenerationAsyncStorage, requestAsyncStorage, + pageContextAsyncStorage, actionAsyncStorage, createUntrackedSearchParams, createDynamicallyTrackedSearchParams, diff --git a/packages/next/src/server/async-storage/page-context-async-storage-wrapper.ts b/packages/next/src/server/async-storage/page-context-async-storage-wrapper.ts new file mode 100644 index 0000000000000..c723e3928386d --- /dev/null +++ b/packages/next/src/server/async-storage/page-context-async-storage-wrapper.ts @@ -0,0 +1,21 @@ +import type { AsyncStorageWrapper } from './async-storage-wrapper' +import type { PageStore } from '../../client/components/page-context-async-storage.external' +import type { AsyncLocalStorage } from 'async_hooks' + +type PageContext = { [key: string]: unknown } + +export const PageContextAsyncStorageWrapper: AsyncStorageWrapper< + PageStore, + PageContext +> = { + wrap( + storage: AsyncLocalStorage, + ctx: PageContext, + callback: (store: PageStore) => Result + ): Result { + // renderOpts.params + const store: PageStore = ctx + + return storage.run(store, callback, store) + }, +} diff --git a/packages/next/src/server/future/route-modules/app-route/module.ts b/packages/next/src/server/future/route-modules/app-route/module.ts index 10671285fa829..93b03b1dd7467 100644 --- a/packages/next/src/server/future/route-modules/app-route/module.ts +++ b/packages/next/src/server/future/route-modules/app-route/module.ts @@ -44,6 +44,7 @@ import { DynamicServerError } from '../../../../client/components/hooks-server-c import { requestAsyncStorage } from '../../../../client/components/request-async-storage.external' import { staticGenerationAsyncStorage } from '../../../../client/components/static-generation-async-storage.external' +import { pageContextAsyncStorage } from '../../../../client/components/page-context-async-storage.external' import { actionAsyncStorage } from '../../../../client/components/action-async-storage.external' import * as sharedModules from './shared-modules' import { getIsServerAction } from '../../../lib/server-action-request-meta' @@ -136,6 +137,11 @@ export class AppRouteRouteModule extends RouteModule< */ public readonly staticGenerationAsyncStorage = staticGenerationAsyncStorage + /** + * A reference to the page context async storage. + */ + public readonly pageContextAsyncStorage = pageContextAsyncStorage + /** * An interface to call server hooks which interact with the underlying * storage. diff --git a/packages/next/src/server/typescript/constant.ts b/packages/next/src/server/typescript/constant.ts index 42ddd95ce9eb5..8c819b6d1b14e 100644 --- a/packages/next/src/server/typescript/constant.ts +++ b/packages/next/src/server/typescript/constant.ts @@ -15,6 +15,7 @@ export const NEXT_TS_ERRORS = { export const ALLOWED_EXPORTS = [ 'config', 'generateStaticParams', + 'createPageContext', 'metadata', 'generateMetadata', 'viewport',