From d09dd56fc7f829052deca467c5860c4ab29446a9 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Sat, 13 Apr 2024 20:29:45 +0200 Subject: [PATCH 1/4] Fix ASL bundling for dynamic css --- packages/next/src/build/webpack-config.ts | 4 ++-- packages/next/src/lib/constants.ts | 1 - .../shared/lib/lazy-dynamic/preload-css.tsx | 8 +++---- .../app-dir/dynamic-css/app/ssr/edge/page.js | 3 +++ test/e2e/app-dir/dynamic-css/index.test.ts | 22 +++++++++++++++++++ 5 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 test/e2e/app-dir/dynamic-css/app/ssr/edge/page.js diff --git a/packages/next/src/build/webpack-config.ts b/packages/next/src/build/webpack-config.ts index 0f48f804c567dd..9816b5ebb41c9a 100644 --- a/packages/next/src/build/webpack-config.ts +++ b/packages/next/src/build/webpack-config.ts @@ -1383,7 +1383,6 @@ export default async function getBaseWebpackConfig( // Alias react for switching between default set and share subset. oneOf: [ { - exclude: asyncStoragesRegex, issuerLayer: isWebpackServerOnlyLayer, test: { // Resolve it if it is a source code file, and it has NOT been @@ -1391,7 +1390,7 @@ export default async function getBaseWebpackConfig( and: [ codeCondition.test, { - not: [optOutBundlingPackageRegex], + not: [optOutBundlingPackageRegex, asyncStoragesRegex], }, ], }, @@ -1499,6 +1498,7 @@ export default async function getBaseWebpackConfig( { test: codeCondition.test, issuerLayer: WEBPACK_LAYERS.serverSideRendering, + exclude: asyncStoragesRegex, use: appSSRLayerLoaders, resolve: { mainFields: getMainField(compilerType, true), diff --git a/packages/next/src/lib/constants.ts b/packages/next/src/lib/constants.ts index 7c8c320e256b09..ed3679d06273d2 100644 --- a/packages/next/src/lib/constants.ts +++ b/packages/next/src/lib/constants.ts @@ -170,7 +170,6 @@ const WEBPACK_LAYERS = { clientOnly: [ WEBPACK_LAYERS_NAMES.serverSideRendering, WEBPACK_LAYERS_NAMES.appPagesBrowser, - WEBPACK_LAYERS_NAMES.shared, ], nonClientServerTarget: [ // middleware and pages api diff --git a/packages/next/src/shared/lib/lazy-dynamic/preload-css.tsx b/packages/next/src/shared/lib/lazy-dynamic/preload-css.tsx index cc1549e970cc07..2645872acb4cc4 100644 --- a/packages/next/src/shared/lib/lazy-dynamic/preload-css.tsx +++ b/packages/next/src/shared/lib/lazy-dynamic/preload-css.tsx @@ -1,14 +1,14 @@ 'use client' +import { getExpectedRequestStore } from '../../../client/components/request-async-storage.external' + export function PreloadCss({ moduleIds }: { moduleIds: string[] | undefined }) { // Early return in client compilation and only load requestStore on server side if (typeof window !== 'undefined') { return null } - const { - getExpectedRequestStore, - } = require('../../../client/components/request-async-storage.external') - const requestStore = getExpectedRequestStore() + + const requestStore = getExpectedRequestStore('next/dynamic css') const allFiles = [] diff --git a/test/e2e/app-dir/dynamic-css/app/ssr/edge/page.js b/test/e2e/app-dir/dynamic-css/app/ssr/edge/page.js new file mode 100644 index 00000000000000..943a37c4b42308 --- /dev/null +++ b/test/e2e/app-dir/dynamic-css/app/ssr/edge/page.js @@ -0,0 +1,3 @@ +export { default } from '../page' + +export const runtime = 'edge' diff --git a/test/e2e/app-dir/dynamic-css/index.test.ts b/test/e2e/app-dir/dynamic-css/index.test.ts index 2892e69fbc4666..c44cb4a82bfaa6 100644 --- a/test/e2e/app-dir/dynamic-css/index.test.ts +++ b/test/e2e/app-dir/dynamic-css/index.test.ts @@ -31,6 +31,23 @@ createNextDescribe( }) }) + it('should only apply corresponding css for page loaded in edge runtime', async () => { + const browser = await next.browser('/ssr/edge') + await retry(async () => { + expect( + await browser.eval( + `window.getComputedStyle(document.querySelector('.text')).color` + ) + ).toBe('rgb(255, 0, 0)') + // Default border width, which is not effected by bar.css that is not loaded in /ssr + expect( + await browser.eval( + `window.getComputedStyle(document.querySelector('.text')).borderWidth` + ) + ).toBe('0px') + }) + }) + it('should only apply corresponding css for page loaded that /another', async () => { const browser = await next.browser('/another') await retry(async () => { @@ -47,5 +64,10 @@ createNextDescribe( ).toBe('1px') }) }) + + it('should not throw with accessing to ALS in preload css', async () => { + const output = next.cliOutput + expect(output).not.toContain('was called outside a request scope') + }) } ) From 7faa51d186a376a65f1594aed39be5e68c01208b Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Tue, 16 Apr 2024 20:14:36 +0200 Subject: [PATCH 2/4] use a different api --- packages/next-swc/crates/next-api/src/app.rs | 56 ++++++++++++++++++- .../request-async-storage.external.ts | 1 + .../shared/lib/lazy-dynamic/preload-css.tsx | 7 +-- 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/packages/next-swc/crates/next-api/src/app.rs b/packages/next-swc/crates/next-api/src/app.rs index 9f82537007f13c..1fda6368d3aed5 100644 --- a/packages/next-swc/crates/next-api/src/app.rs +++ b/packages/next-swc/crates/next-api/src/app.rs @@ -283,6 +283,10 @@ impl AppProject { ))), ), ("next-ssr".to_string(), Vc::upcast(self.ssr_transition())), + ( + "next-shared".to_string(), + Vc::upcast(self.shared_transition()), + ), ] .into_iter() .collect(); @@ -315,6 +319,10 @@ impl AppProject { "next-ssr".to_string(), Vc::upcast(self.edge_ssr_transition()), ), + ( + "next-shared".to_string(), + Vc::upcast(self.edge_shared_transition()), + ), ] .into_iter() .collect(); @@ -344,6 +352,10 @@ impl AppProject { ))), ), ("next-ssr".to_string(), Vc::upcast(self.ssr_transition())), + ( + "next-shared".to_string(), + Vc::upcast(self.shared_transition()), + ), ] .into_iter() .collect(); @@ -359,8 +371,30 @@ impl AppProject { #[turbo_tasks::function] fn edge_route_module_context(self: Vc) -> Vc { + let transitions = [ + ( + ECMASCRIPT_CLIENT_TRANSITION_NAME.to_string(), + Vc::upcast(NextEcmascriptClientReferenceTransition::new( + Vc::upcast(self.client_transition()), + self.edge_ssr_transition(), + )), + ), + ( + "next-dynamic".to_string(), + Vc::upcast(NextDynamicTransition::new(Vc::upcast( + self.client_transition(), + ))), + ), + ("next-ssr".to_string(), Vc::upcast(self.ssr_transition())), + ( + "next-shared".to_string(), + Vc::upcast(self.edge_shared_transition()), + ), + ] + .into_iter() + .collect(); ModuleAssetContext::new( - Default::default(), + Vc::cell(transitions), self.project().edge_compile_time_info(), self.edge_route_module_options_context(), self.edge_route_resolve_options_context(), @@ -435,6 +469,16 @@ impl AppProject { ) } + #[turbo_tasks::function] + fn shared_transition(self: Vc) -> Vc { + ContextTransition::new( + self.project().server_compile_time_info(), + self.ssr_module_options_context(), + self.ssr_resolve_options_context(), + Vc::cell("app-shared".to_string()), + ) + } + #[turbo_tasks::function] fn edge_ssr_transition(self: Vc) -> Vc { ContextTransition::new( @@ -445,6 +489,16 @@ impl AppProject { ) } + #[turbo_tasks::function] + fn edge_shared_transition(self: Vc) -> Vc { + ContextTransition::new( + self.project().edge_compile_time_info(), + self.edge_ssr_module_options_context(), + self.edge_ssr_resolve_options_context(), + Vc::cell("app-edge-shared".to_string()), + ) + } + #[turbo_tasks::function] async fn runtime_entries(self: Vc) -> Result> { Ok(get_server_runtime_entries( diff --git a/packages/next/src/client/components/request-async-storage.external.ts b/packages/next/src/client/components/request-async-storage.external.ts index dbd33809881c31..f2b9568d07afe8 100644 --- a/packages/next/src/client/components/request-async-storage.external.ts +++ b/packages/next/src/client/components/request-async-storage.external.ts @@ -4,6 +4,7 @@ import type { ResponseCookies } from '../../server/web/spec-extension/cookies' import type { ReadonlyHeaders } from '../../server/web/spec-extension/adapters/headers' import type { ReadonlyRequestCookies } from '../../server/web/spec-extension/adapters/request-cookies' +;('TURBOPACK { transition: next-shared }') import { createAsyncLocalStorage } from './async-local-storage' import type { DeepReadonly } from '../../shared/lib/deep-readonly' diff --git a/packages/next/src/shared/lib/lazy-dynamic/preload-css.tsx b/packages/next/src/shared/lib/lazy-dynamic/preload-css.tsx index 2645872acb4cc4..8d4cb735afba3f 100644 --- a/packages/next/src/shared/lib/lazy-dynamic/preload-css.tsx +++ b/packages/next/src/shared/lib/lazy-dynamic/preload-css.tsx @@ -1,6 +1,6 @@ 'use client' -import { getExpectedRequestStore } from '../../../client/components/request-async-storage.external' +import { requestAsyncStorage } from '../../../client/components/request-async-storage.external' export function PreloadCss({ moduleIds }: { moduleIds: string[] | undefined }) { // Early return in client compilation and only load requestStore on server side @@ -8,13 +8,12 @@ export function PreloadCss({ moduleIds }: { moduleIds: string[] | undefined }) { return null } - const requestStore = getExpectedRequestStore('next/dynamic css') - + const requestStore = requestAsyncStorage.getStore() const allFiles = [] // Search the current dynamic call unique key id in react loadable manifest, // and find the corresponding CSS files to preload - if (requestStore.reactLoadableManifest && moduleIds) { + if (requestStore?.reactLoadableManifest && moduleIds) { const manifest = requestStore.reactLoadableManifest for (const key of moduleIds) { if (!manifest[key]) continue From 3d26450d90823c63a5b93df1f514b0c849763c4f Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Tue, 16 Apr 2024 20:58:14 +0200 Subject: [PATCH 3/4] create instance once --- .../src/client/components/action-async-storage-instance.ts | 4 ++++ .../src/client/components/action-async-storage.external.ts | 6 ++++-- .../client/components/request-async-storage-instance.ts | 5 +++++ .../client/components/request-async-storage.external.ts | 6 +++--- .../components/static-generation-async-storage-instance.ts | 5 +++++ .../components/static-generation-async-storage.external.ts | 7 ++++--- packages/next/src/shared/lib/lazy-dynamic/preload-css.tsx | 6 +++--- 7 files changed, 28 insertions(+), 11 deletions(-) create mode 100644 packages/next/src/client/components/action-async-storage-instance.ts create mode 100644 packages/next/src/client/components/request-async-storage-instance.ts create mode 100644 packages/next/src/client/components/static-generation-async-storage-instance.ts diff --git a/packages/next/src/client/components/action-async-storage-instance.ts b/packages/next/src/client/components/action-async-storage-instance.ts new file mode 100644 index 00000000000000..161879ae2d7607 --- /dev/null +++ b/packages/next/src/client/components/action-async-storage-instance.ts @@ -0,0 +1,4 @@ +import type { ActionAsyncStorage } from './action-async-storage.external' +import { createAsyncLocalStorage } from './async-local-storage' + +export const actionAsyncStorage: ActionAsyncStorage = createAsyncLocalStorage() diff --git a/packages/next/src/client/components/action-async-storage.external.ts b/packages/next/src/client/components/action-async-storage.external.ts index d34b8225c818b1..9a46eeeb63184b 100644 --- a/packages/next/src/client/components/action-async-storage.external.ts +++ b/packages/next/src/client/components/action-async-storage.external.ts @@ -1,6 +1,8 @@ import type { AsyncLocalStorage } from 'async_hooks' -import { createAsyncLocalStorage } from './async-local-storage' +// Share the instance module in the next-shared layer +;('TURBOPACK { transition: next-shared }') +import { actionAsyncStorage } from './action-async-storage-instance' export interface ActionStore { readonly isAction?: boolean readonly isAppRoute?: boolean @@ -8,4 +10,4 @@ export interface ActionStore { export type ActionAsyncStorage = AsyncLocalStorage -export const actionAsyncStorage: ActionAsyncStorage = createAsyncLocalStorage() +export { actionAsyncStorage } diff --git a/packages/next/src/client/components/request-async-storage-instance.ts b/packages/next/src/client/components/request-async-storage-instance.ts new file mode 100644 index 00000000000000..a9b67084329875 --- /dev/null +++ b/packages/next/src/client/components/request-async-storage-instance.ts @@ -0,0 +1,5 @@ +import { createAsyncLocalStorage } from './async-local-storage' +import type { RequestAsyncStorage } from './request-async-storage.external' + +export const requestAsyncStorage: RequestAsyncStorage = + createAsyncLocalStorage() diff --git a/packages/next/src/client/components/request-async-storage.external.ts b/packages/next/src/client/components/request-async-storage.external.ts index f2b9568d07afe8..9aa173d15e00c4 100644 --- a/packages/next/src/client/components/request-async-storage.external.ts +++ b/packages/next/src/client/components/request-async-storage.external.ts @@ -4,8 +4,9 @@ import type { ResponseCookies } from '../../server/web/spec-extension/cookies' import type { ReadonlyHeaders } from '../../server/web/spec-extension/adapters/headers' import type { ReadonlyRequestCookies } from '../../server/web/spec-extension/adapters/request-cookies' +// Share the instance module in the next-shared layer ;('TURBOPACK { transition: next-shared }') -import { createAsyncLocalStorage } from './async-local-storage' +import { requestAsyncStorage } from './request-async-storage-instance' import type { DeepReadonly } from '../../shared/lib/deep-readonly' export interface RequestStore { @@ -21,8 +22,7 @@ export interface RequestStore { export type RequestAsyncStorage = AsyncLocalStorage -export const requestAsyncStorage: RequestAsyncStorage = - createAsyncLocalStorage() +export { requestAsyncStorage } export function getExpectedRequestStore(callingExpression: string) { const store = requestAsyncStorage.getStore() diff --git a/packages/next/src/client/components/static-generation-async-storage-instance.ts b/packages/next/src/client/components/static-generation-async-storage-instance.ts new file mode 100644 index 00000000000000..3ac18d8f1c2ef9 --- /dev/null +++ b/packages/next/src/client/components/static-generation-async-storage-instance.ts @@ -0,0 +1,5 @@ +import type { StaticGenerationAsyncStorage } from './static-generation-async-storage.external' +import { createAsyncLocalStorage } from './async-local-storage' + +export const staticGenerationAsyncStorage: StaticGenerationAsyncStorage = + createAsyncLocalStorage() diff --git a/packages/next/src/client/components/static-generation-async-storage.external.ts b/packages/next/src/client/components/static-generation-async-storage.external.ts index 4433992a8f331f..f1368133535ff9 100644 --- a/packages/next/src/client/components/static-generation-async-storage.external.ts +++ b/packages/next/src/client/components/static-generation-async-storage.external.ts @@ -5,7 +5,9 @@ import type { FetchMetrics } from '../../server/base-http' import type { Revalidate } from '../../server/lib/revalidate' import type { PrerenderState } from '../../server/app-render/dynamic-rendering' -import { createAsyncLocalStorage } from './async-local-storage' +// Share the instance module in the next-shared layer +;('TURBOPACK { transition: next-shared }') +import { staticGenerationAsyncStorage } from './static-generation-async-storage-instance' export interface StaticGenerationStore { readonly isStaticGeneration: boolean @@ -53,5 +55,4 @@ export interface StaticGenerationStore { export type StaticGenerationAsyncStorage = AsyncLocalStorage -export const staticGenerationAsyncStorage: StaticGenerationAsyncStorage = - createAsyncLocalStorage() +export { staticGenerationAsyncStorage } diff --git a/packages/next/src/shared/lib/lazy-dynamic/preload-css.tsx b/packages/next/src/shared/lib/lazy-dynamic/preload-css.tsx index 8d4cb735afba3f..aac260fc2b2518 100644 --- a/packages/next/src/shared/lib/lazy-dynamic/preload-css.tsx +++ b/packages/next/src/shared/lib/lazy-dynamic/preload-css.tsx @@ -1,6 +1,6 @@ 'use client' -import { requestAsyncStorage } from '../../../client/components/request-async-storage.external' +import { getExpectedRequestStore } from '../../../client/components/request-async-storage.external' export function PreloadCss({ moduleIds }: { moduleIds: string[] | undefined }) { // Early return in client compilation and only load requestStore on server side @@ -8,12 +8,12 @@ export function PreloadCss({ moduleIds }: { moduleIds: string[] | undefined }) { return null } - const requestStore = requestAsyncStorage.getStore() + const requestStore = getExpectedRequestStore('next/dynamic css') const allFiles = [] // Search the current dynamic call unique key id in react loadable manifest, // and find the corresponding CSS files to preload - if (requestStore?.reactLoadableManifest && moduleIds) { + if (requestStore.reactLoadableManifest && moduleIds) { const manifest = requestStore.reactLoadableManifest for (const key of moduleIds) { if (!manifest[key]) continue From 0d9546aa2e28509d5cd1b966fe736230cd95fdfb Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Tue, 16 Apr 2024 21:06:00 +0200 Subject: [PATCH 4/4] add eslint --- .../next/src/client/components/action-async-storage.external.ts | 1 + .../next/src/client/components/request-async-storage.external.ts | 1 + .../components/static-generation-async-storage.external.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/packages/next/src/client/components/action-async-storage.external.ts b/packages/next/src/client/components/action-async-storage.external.ts index 9a46eeeb63184b..beaac5d06902f9 100644 --- a/packages/next/src/client/components/action-async-storage.external.ts +++ b/packages/next/src/client/components/action-async-storage.external.ts @@ -1,6 +1,7 @@ import type { AsyncLocalStorage } from 'async_hooks' // Share the instance module in the next-shared layer +// eslint-disable-next-line @typescript-eslint/no-unused-expressions ;('TURBOPACK { transition: next-shared }') import { actionAsyncStorage } from './action-async-storage-instance' export interface ActionStore { diff --git a/packages/next/src/client/components/request-async-storage.external.ts b/packages/next/src/client/components/request-async-storage.external.ts index 9aa173d15e00c4..0af201362a3da5 100644 --- a/packages/next/src/client/components/request-async-storage.external.ts +++ b/packages/next/src/client/components/request-async-storage.external.ts @@ -5,6 +5,7 @@ import type { ReadonlyHeaders } from '../../server/web/spec-extension/adapters/h import type { ReadonlyRequestCookies } from '../../server/web/spec-extension/adapters/request-cookies' // Share the instance module in the next-shared layer +// eslint-disable-next-line @typescript-eslint/no-unused-expressions ;('TURBOPACK { transition: next-shared }') import { requestAsyncStorage } from './request-async-storage-instance' import type { DeepReadonly } from '../../shared/lib/deep-readonly' diff --git a/packages/next/src/client/components/static-generation-async-storage.external.ts b/packages/next/src/client/components/static-generation-async-storage.external.ts index f1368133535ff9..2a21c77357dfe4 100644 --- a/packages/next/src/client/components/static-generation-async-storage.external.ts +++ b/packages/next/src/client/components/static-generation-async-storage.external.ts @@ -6,6 +6,7 @@ import type { Revalidate } from '../../server/lib/revalidate' import type { PrerenderState } from '../../server/app-render/dynamic-rendering' // Share the instance module in the next-shared layer +// eslint-disable-next-line @typescript-eslint/no-unused-expressions ;('TURBOPACK { transition: next-shared }') import { staticGenerationAsyncStorage } from './static-generation-async-storage-instance'