next_core/next_app/app_client_references_chunks.rs
1use anyhow::Result;
2use tracing::Instrument;
3use turbo_rcstr::rcstr;
4use turbo_tasks::{
5 FxIndexMap, FxIndexSet, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt, ValueToStringRef, Vc,
6};
7use turbopack_core::{
8 chunk::{ChunkGroupResult, ChunkingContext, availability_info::AvailabilityInfo},
9 module::Module,
10 module_graph::{ModuleGraph, chunk_group_info::ChunkGroup},
11 output::{OutputAsset, OutputAssets, OutputAssetsWithReferenced},
12};
13
14use crate::{
15 next_client_reference::{
16 ClientReferenceType,
17 ecmascript_client_reference::ecmascript_client_reference_module::{
18 ecmascript_client_reference_merge_tag, ecmascript_client_reference_merge_tag_ssr,
19 },
20 visit_client_reference::ClientReferenceGraphResult,
21 },
22 next_server_component::server_component_module::NextServerComponentModule,
23};
24
25#[turbo_tasks::value]
26pub struct ClientReferencesChunks {
27 #[bincode(with = "turbo_bincode::indexmap")]
28 pub client_component_client_chunks:
29 FxIndexMap<ClientReferenceType, ResolvedVc<ChunkGroupResult>>,
30 #[bincode(with = "turbo_bincode::indexmap")]
31 pub client_component_ssr_chunks:
32 FxIndexMap<ClientReferenceType, ResolvedVc<OutputAssetsWithReferenced>>,
33 #[bincode(with = "turbo_bincode::indexmap")]
34 pub layout_segment_client_chunks:
35 FxIndexMap<ResolvedVc<NextServerComponentModule>, ResolvedVc<OutputAssetsWithReferenced>>,
36}
37
38/// Computes all client references chunks.
39///
40/// This returns a map from client reference type to the chunks that the reference
41/// type needs to load.
42#[turbo_tasks::function]
43pub async fn get_app_client_references_chunks(
44 app_client_references: Vc<ClientReferenceGraphResult>,
45 module_graph: Vc<ModuleGraph>,
46 client_chunking_context: Vc<Box<dyn ChunkingContext>>,
47 client_availability_info: AvailabilityInfo,
48 ssr_chunking_context: Option<Vc<Box<dyn ChunkingContext>>>,
49) -> Result<Vc<ClientReferencesChunks>> {
50 async move {
51 // TODO Reconsider this. Maybe it need to be true in production.
52 let separate_chunk_group_per_client_reference = false;
53 let app_client_references = app_client_references.await?;
54 if separate_chunk_group_per_client_reference {
55 todo!();
56 // let app_client_references_chunks: Vec<(_, (_, Option<_>))> = app_client_references
57 // .client_references
58 // .iter()
59 // .map(|client_reference| async move {
60 // Ok((
61 // client_reference.ty,
62 // match client_reference.ty {
63 // ClientReferenceType::EcmascriptClientReference(
64 // ecmascript_client_reference,
65 // ) => {
66 // let ecmascript_client_reference_ref =
67 // ecmascript_client_reference.await?;
68
69 // let client_chunk_group = client_chunking_context
70 // .root_chunk_group(
71 // module_graph,
72 // *ResolvedVc::upcast(
73 // ecmascript_client_reference_ref.client_module,
74 // ),
75 // )
76 // .await?;
77
78 // (
79 // (
80 // client_chunk_group.assets,
81 // client_chunk_group.availability_info,
82 // ),
83 // if let Some(ssr_chunking_context) = ssr_chunking_context {
84 // let ssr_chunk_group = ssr_chunking_context
85 // .root_chunk_group(
86 // *ResolvedVc::upcast(
87 // ecmascript_client_reference_ref.ssr_module,
88 // ),
89 // module_graph,
90 // )
91 // .await?;
92
93 // Some((
94 // ssr_chunk_group.assets,
95 // ssr_chunk_group.availability_info,
96 // ))
97 // } else {
98 // None
99 // },
100 // )
101 // }
102 // ClientReferenceType::CssClientReference(css_client_reference) => {
103 // let client_chunk_group = client_chunking_context
104 // .root_chunk_group(
105 // *ResolvedVc::upcast(css_client_reference),
106 // module_graph,
107 // )
108 // .await?;
109
110 // (
111 // (
112 // client_chunk_group.assets,
113 // client_chunk_group.availability_info,
114 // ),
115 // None,
116 // )
117 // }
118 // },
119 // ))
120 // })
121 // .try_join()
122 // .await?;
123
124 // Ok(ClientReferencesChunks {
125 // client_component_client_chunks: app_client_references_chunks
126 // .iter()
127 // .map(|&(client_reference_ty, (client_chunks, _))| {
128 // (client_reference_ty, client_chunks)
129 // })
130 // .collect(),
131 // client_component_ssr_chunks: app_client_references_chunks
132 // .iter()
133 // .flat_map(|&(client_reference_ty, (_, ssr_chunks))| {
134 // ssr_chunks.map(|ssr_chunks| (client_reference_ty, ssr_chunks))
135 // })
136 // .collect(),
137 // layout_segment_client_chunks: FxIndexMap::default(),
138 // }
139 // .cell())
140 } else {
141 let mut client_references_by_server_component: FxIndexMap<_, Vec<_>> =
142 FxIndexMap::default();
143 let mut framework_reference_types = Vec::new();
144 for &server_component in app_client_references.server_component_entries.iter() {
145 client_references_by_server_component
146 .entry(server_component)
147 .or_default();
148 }
149 for client_reference in app_client_references.client_references.iter() {
150 if let Some(server_component) = client_reference.server_component {
151 client_references_by_server_component
152 .entry(server_component)
153 .or_default()
154 .push(client_reference.ty);
155 } else {
156 framework_reference_types.push(client_reference.ty);
157 }
158 }
159 // Framework components need to go into first layout segment
160 if let Some((_, list)) = client_references_by_server_component.first_mut() {
161 list.extend(framework_reference_types);
162 }
163
164 let chunk_group_info = module_graph.chunk_group_info();
165
166 let mut current_client_chunk_group = ChunkGroupResult {
167 assets: ResolvedVc::cell(vec![]),
168 referenced_assets: ResolvedVc::cell(vec![]),
169 references: ResolvedVc::cell(vec![]),
170 availability_info: client_availability_info,
171 chunk_group_bootstrap_params: None,
172 }
173 .resolved_cell();
174 let mut current_ssr_chunk_group = ChunkGroupResult::empty_resolved();
175
176 let mut layout_segment_client_chunks = FxIndexMap::default();
177 let mut client_component_ssr_chunks = FxIndexMap::default();
178 let mut client_component_client_chunks = FxIndexMap::default();
179
180 for (server_component, client_reference_types) in
181 client_references_by_server_component.into_iter()
182 {
183 let parent_chunk_group = *chunk_group_info
184 .get_index_of(ChunkGroup::Shared(ResolvedVc::upcast(server_component)))
185 .await?;
186
187 let base_ident = server_component.ident().owned().await?;
188
189 let server_path = server_component.server_path().owned().await?;
190 let is_layout = server_path.file_stem() == Some("layout");
191 let server_component_path = server_path.to_string_ref().await?;
192
193 let ssr_modules = client_reference_types
194 .iter()
195 .map(|client_reference_ty| async move {
196 Ok(match client_reference_ty {
197 ClientReferenceType::EcmascriptClientReference(
198 ecmascript_client_reference,
199 ) => {
200 let ecmascript_client_reference_ref =
201 ecmascript_client_reference.await?;
202
203 Some(ResolvedVc::upcast(
204 ecmascript_client_reference_ref.ssr_module,
205 ))
206 }
207 _ => None,
208 })
209 })
210 .try_flat_join()
211 .await?;
212
213 let ssr_chunk_group = if !ssr_modules.is_empty()
214 && let Some(ssr_chunking_context) = ssr_chunking_context
215 {
216 let availability_info = current_ssr_chunk_group.await?.availability_info;
217 let _span = tracing::info_span!(
218 "server side rendering",
219 layout_segment = display(&server_component_path),
220 )
221 .entered();
222
223 Some(
224 ssr_chunking_context.chunk_group(
225 base_ident
226 .clone()
227 .with_modifier(rcstr!("ssr modules"))
228 .into_vc(),
229 ChunkGroup::IsolatedMerged {
230 parent: parent_chunk_group,
231 merge_tag: ecmascript_client_reference_merge_tag_ssr(),
232 entries: ssr_modules,
233 },
234 module_graph,
235 availability_info,
236 ),
237 )
238 } else {
239 None
240 };
241
242 let client_modules = client_reference_types
243 .iter()
244 .map(|client_reference_ty| async move {
245 Ok(match client_reference_ty {
246 ClientReferenceType::EcmascriptClientReference(
247 ecmascript_client_reference,
248 ) => {
249 ResolvedVc::upcast(ecmascript_client_reference.await?.client_module)
250 }
251 ClientReferenceType::CssClientReference(css_client_reference) => {
252 ResolvedVc::upcast(*css_client_reference)
253 }
254 })
255 })
256 .try_join()
257 .await?;
258 let client_chunk_group = if !client_modules.is_empty() {
259 let availability_info = current_client_chunk_group.await?.availability_info;
260 let _span = tracing::info_span!(
261 "client side rendering",
262 layout_segment = display(&server_component_path),
263 )
264 .entered();
265
266 Some(client_chunking_context.chunk_group(
267 base_ident.with_modifier(rcstr!("client modules")).into_vc(),
268 ChunkGroup::IsolatedMerged {
269 parent: parent_chunk_group,
270 merge_tag: ecmascript_client_reference_merge_tag(),
271 entries: client_modules,
272 },
273 module_graph,
274 availability_info,
275 ))
276 } else {
277 None
278 };
279
280 if let Some(client_chunk_group) = client_chunk_group {
281 let client_chunk_group = current_client_chunk_group
282 .concatenate(client_chunk_group)
283 .to_resolved()
284 .await?;
285
286 if is_layout {
287 current_client_chunk_group = client_chunk_group;
288 }
289
290 let assets = client_chunk_group
291 .output_assets_with_referenced()
292 .to_resolved()
293 .await?;
294 layout_segment_client_chunks.insert(server_component, assets);
295
296 for &client_reference_ty in client_reference_types.iter() {
297 if let ClientReferenceType::EcmascriptClientReference(_) =
298 client_reference_ty
299 {
300 client_component_client_chunks
301 .insert(client_reference_ty, client_chunk_group);
302 }
303 }
304 }
305
306 if let Some(ssr_chunk_group) = ssr_chunk_group {
307 let ssr_chunk_group = current_ssr_chunk_group
308 .concatenate(ssr_chunk_group)
309 .to_resolved()
310 .await?;
311
312 if is_layout {
313 current_ssr_chunk_group = ssr_chunk_group;
314 }
315
316 let assets = ssr_chunk_group
317 .output_assets_with_referenced()
318 .to_resolved()
319 .await?;
320 for &client_reference_ty in client_reference_types.iter() {
321 if let ClientReferenceType::EcmascriptClientReference(_) =
322 client_reference_ty
323 {
324 client_component_ssr_chunks.insert(client_reference_ty, assets);
325 }
326 }
327 }
328 }
329
330 Ok(ClientReferencesChunks {
331 client_component_client_chunks,
332 client_component_ssr_chunks,
333 layout_segment_client_chunks,
334 }
335 .cell())
336 }
337 }
338 .instrument(tracing::info_span!("process client references"))
339 .await
340}
341
342/// Flattens all client-side output assets from `client_references_chunks` so the
343/// page's HMR chunk list can subscribe to updates for chunks built outside the
344/// entry's own module graph (each `chunk_group(IsolatedMerged)` call for a
345/// client component group generates chunks separately).
346#[turbo_tasks::function]
347pub async fn get_client_references_chunks_for_hmr(
348 client_references_chunks: Vc<ClientReferencesChunks>,
349) -> Result<Vc<OutputAssets>> {
350 let client_references_chunks_ref = client_references_chunks.await?;
351 let mut extras: FxIndexSet<ResolvedVc<Box<dyn OutputAsset>>> = client_references_chunks_ref
352 .layout_segment_client_chunks
353 .values()
354 .map(|&assets| async move {
355 let primary = assets.primary_assets().await?;
356 Ok(primary.iter().copied().collect::<Vec<_>>())
357 })
358 .try_flat_join()
359 .await?
360 .into_iter()
361 .collect();
362 for &chunk_group in client_references_chunks_ref
363 .client_component_client_chunks
364 .values()
365 {
366 // Use all_assets() (not primary_assets()) to also follow async loader references
367 // transitively. This ensures that dynamic imports within 'use client' pages are
368 // covered by the page's HMR subscription, not just the page module itself.
369 extras.extend(chunk_group.all_assets().await?.iter().copied());
370 }
371 // client_component_ssr_chunks are intentionally excluded: they run on the server
372 // (Node.js/Edge), not in the browser, so they don't belong in the client HMR chunk list.
373 Ok(Vc::cell(extras.into_iter().collect()))
374}