Skip to main content

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}