Skip to content

Commit 8b82225

Browse files
authored
Fix ASL bundling for dynamic css (#64451)
### Why For app page rendering on edge, the `AsyncLocalStorage` (ALS) should be bundled as same instance across layers. We're accessing the ALS in `next/dynamic` modules during SSR for preloading CSS chunks. There's a bug that we can't get the ALS store during SSR in edge, I digged into it and found the root cause is: We have both import paths: `module (rsc layer) -> request ALS (shared layer)` `module (ssr layer) -> request ALS (shared layer)` We expect the ALS to be the same module since we're using the same layer but found that they're treated as different modules due to applying another loader transform on ssr layer. They're resulted in the same `shared` layer, but with different resource queries. This PR excluded that transform so now they're identical across layers. ### What For webpack, we aligned the loaders applying to the async local storage, so that they're resolved as the same module now. For turbopack, we leverage module transition, sort of creating a new `app-shared` layer for these modules, and apply the transition to all async local storage instances therefore the instances of them are only bundled once. To make the turbopack chanegs work, we change how the async local storage modules defined, separate the instance into a single file and mark it as "next-shared" layer with import: ``` any module -> async local storage --- use transition, specify "next-shared" layer ---> async local storage instance ``` Closes NEXT-3085
1 parent 07a0700 commit 8b82225

File tree

12 files changed

+114
-16
lines changed

12 files changed

+114
-16
lines changed

packages/next-swc/crates/next-api/src/app.rs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,10 @@ impl AppProject {
283283
))),
284284
),
285285
("next-ssr".to_string(), Vc::upcast(self.ssr_transition())),
286+
(
287+
"next-shared".to_string(),
288+
Vc::upcast(self.shared_transition()),
289+
),
286290
]
287291
.into_iter()
288292
.collect();
@@ -315,6 +319,10 @@ impl AppProject {
315319
"next-ssr".to_string(),
316320
Vc::upcast(self.edge_ssr_transition()),
317321
),
322+
(
323+
"next-shared".to_string(),
324+
Vc::upcast(self.edge_shared_transition()),
325+
),
318326
]
319327
.into_iter()
320328
.collect();
@@ -344,6 +352,10 @@ impl AppProject {
344352
))),
345353
),
346354
("next-ssr".to_string(), Vc::upcast(self.ssr_transition())),
355+
(
356+
"next-shared".to_string(),
357+
Vc::upcast(self.shared_transition()),
358+
),
347359
]
348360
.into_iter()
349361
.collect();
@@ -359,8 +371,30 @@ impl AppProject {
359371

360372
#[turbo_tasks::function]
361373
fn edge_route_module_context(self: Vc<Self>) -> Vc<ModuleAssetContext> {
374+
let transitions = [
375+
(
376+
ECMASCRIPT_CLIENT_TRANSITION_NAME.to_string(),
377+
Vc::upcast(NextEcmascriptClientReferenceTransition::new(
378+
Vc::upcast(self.client_transition()),
379+
self.edge_ssr_transition(),
380+
)),
381+
),
382+
(
383+
"next-dynamic".to_string(),
384+
Vc::upcast(NextDynamicTransition::new(Vc::upcast(
385+
self.client_transition(),
386+
))),
387+
),
388+
("next-ssr".to_string(), Vc::upcast(self.ssr_transition())),
389+
(
390+
"next-shared".to_string(),
391+
Vc::upcast(self.edge_shared_transition()),
392+
),
393+
]
394+
.into_iter()
395+
.collect();
362396
ModuleAssetContext::new(
363-
Default::default(),
397+
Vc::cell(transitions),
364398
self.project().edge_compile_time_info(),
365399
self.edge_route_module_options_context(),
366400
self.edge_route_resolve_options_context(),
@@ -435,6 +469,16 @@ impl AppProject {
435469
)
436470
}
437471

472+
#[turbo_tasks::function]
473+
fn shared_transition(self: Vc<Self>) -> Vc<ContextTransition> {
474+
ContextTransition::new(
475+
self.project().server_compile_time_info(),
476+
self.ssr_module_options_context(),
477+
self.ssr_resolve_options_context(),
478+
Vc::cell("app-shared".to_string()),
479+
)
480+
}
481+
438482
#[turbo_tasks::function]
439483
fn edge_ssr_transition(self: Vc<Self>) -> Vc<ContextTransition> {
440484
ContextTransition::new(
@@ -445,6 +489,16 @@ impl AppProject {
445489
)
446490
}
447491

492+
#[turbo_tasks::function]
493+
fn edge_shared_transition(self: Vc<Self>) -> Vc<ContextTransition> {
494+
ContextTransition::new(
495+
self.project().edge_compile_time_info(),
496+
self.edge_ssr_module_options_context(),
497+
self.edge_ssr_resolve_options_context(),
498+
Vc::cell("app-edge-shared".to_string()),
499+
)
500+
}
501+
448502
#[turbo_tasks::function]
449503
async fn runtime_entries(self: Vc<Self>) -> Result<Vc<RuntimeEntries>> {
450504
Ok(get_server_runtime_entries(

packages/next/src/build/webpack-config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1383,15 +1383,14 @@ export default async function getBaseWebpackConfig(
13831383
// Alias react for switching between default set and share subset.
13841384
oneOf: [
13851385
{
1386-
exclude: asyncStoragesRegex,
13871386
issuerLayer: isWebpackServerOnlyLayer,
13881387
test: {
13891388
// Resolve it if it is a source code file, and it has NOT been
13901389
// opted out of bundling.
13911390
and: [
13921391
codeCondition.test,
13931392
{
1394-
not: [optOutBundlingPackageRegex],
1393+
not: [optOutBundlingPackageRegex, asyncStoragesRegex],
13951394
},
13961395
],
13971396
},
@@ -1499,6 +1498,7 @@ export default async function getBaseWebpackConfig(
14991498
{
15001499
test: codeCondition.test,
15011500
issuerLayer: WEBPACK_LAYERS.serverSideRendering,
1501+
exclude: asyncStoragesRegex,
15021502
use: appSSRLayerLoaders,
15031503
resolve: {
15041504
mainFields: getMainField(compilerType, true),
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import type { ActionAsyncStorage } from './action-async-storage.external'
2+
import { createAsyncLocalStorage } from './async-local-storage'
3+
4+
export const actionAsyncStorage: ActionAsyncStorage = createAsyncLocalStorage()
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import type { AsyncLocalStorage } from 'async_hooks'
2-
import { createAsyncLocalStorage } from './async-local-storage'
32

3+
// Share the instance module in the next-shared layer
4+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
5+
;('TURBOPACK { transition: next-shared }')
6+
import { actionAsyncStorage } from './action-async-storage-instance'
47
export interface ActionStore {
58
readonly isAction?: boolean
69
readonly isAppRoute?: boolean
710
}
811

912
export type ActionAsyncStorage = AsyncLocalStorage<ActionStore>
1013

11-
export const actionAsyncStorage: ActionAsyncStorage = createAsyncLocalStorage()
14+
export { actionAsyncStorage }
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { createAsyncLocalStorage } from './async-local-storage'
2+
import type { RequestAsyncStorage } from './request-async-storage.external'
3+
4+
export const requestAsyncStorage: RequestAsyncStorage =
5+
createAsyncLocalStorage()

packages/next/src/client/components/request-async-storage.external.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import type { ResponseCookies } from '../../server/web/spec-extension/cookies'
44
import type { ReadonlyHeaders } from '../../server/web/spec-extension/adapters/headers'
55
import type { ReadonlyRequestCookies } from '../../server/web/spec-extension/adapters/request-cookies'
66

7-
import { createAsyncLocalStorage } from './async-local-storage'
7+
// Share the instance module in the next-shared layer
8+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
9+
;('TURBOPACK { transition: next-shared }')
10+
import { requestAsyncStorage } from './request-async-storage-instance'
811
import type { DeepReadonly } from '../../shared/lib/deep-readonly'
912

1013
export interface RequestStore {
@@ -20,8 +23,7 @@ export interface RequestStore {
2023

2124
export type RequestAsyncStorage = AsyncLocalStorage<RequestStore>
2225

23-
export const requestAsyncStorage: RequestAsyncStorage =
24-
createAsyncLocalStorage()
26+
export { requestAsyncStorage }
2527

2628
export function getExpectedRequestStore(callingExpression: string) {
2729
const store = requestAsyncStorage.getStore()
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import type { StaticGenerationAsyncStorage } from './static-generation-async-storage.external'
2+
import { createAsyncLocalStorage } from './async-local-storage'
3+
4+
export const staticGenerationAsyncStorage: StaticGenerationAsyncStorage =
5+
createAsyncLocalStorage()

packages/next/src/client/components/static-generation-async-storage.external.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import type { FetchMetrics } from '../../server/base-http'
55
import type { Revalidate } from '../../server/lib/revalidate'
66
import type { PrerenderState } from '../../server/app-render/dynamic-rendering'
77

8-
import { createAsyncLocalStorage } from './async-local-storage'
8+
// Share the instance module in the next-shared layer
9+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
10+
;('TURBOPACK { transition: next-shared }')
11+
import { staticGenerationAsyncStorage } from './static-generation-async-storage-instance'
912

1013
export interface StaticGenerationStore {
1114
readonly isStaticGeneration: boolean
@@ -53,5 +56,4 @@ export interface StaticGenerationStore {
5356
export type StaticGenerationAsyncStorage =
5457
AsyncLocalStorage<StaticGenerationStore>
5558

56-
export const staticGenerationAsyncStorage: StaticGenerationAsyncStorage =
57-
createAsyncLocalStorage()
59+
export { staticGenerationAsyncStorage }

packages/next/src/lib/constants.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,6 @@ const WEBPACK_LAYERS = {
170170
clientOnly: [
171171
WEBPACK_LAYERS_NAMES.serverSideRendering,
172172
WEBPACK_LAYERS_NAMES.appPagesBrowser,
173-
WEBPACK_LAYERS_NAMES.shared,
174173
],
175174
nonClientServerTarget: [
176175
// middleware and pages api

packages/next/src/shared/lib/lazy-dynamic/preload-css.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
'use client'
22

3+
import { getExpectedRequestStore } from '../../../client/components/request-async-storage.external'
4+
35
export function PreloadCss({ moduleIds }: { moduleIds: string[] | undefined }) {
46
// Early return in client compilation and only load requestStore on server side
57
if (typeof window !== 'undefined') {
68
return null
79
}
8-
const {
9-
getExpectedRequestStore,
10-
} = require('../../../client/components/request-async-storage.external')
11-
const requestStore = getExpectedRequestStore()
1210

11+
const requestStore = getExpectedRequestStore('next/dynamic css')
1312
const allFiles = []
1413

1514
// Search the current dynamic call unique key id in react loadable manifest,

0 commit comments

Comments
 (0)