next_core/next_client_reference/
visit_client_reference.rs

1use std::future::Future;
2
3use anyhow::Result;
4use rustc_hash::FxHashSet;
5use serde::{Deserialize, Serialize};
6use tracing::Instrument;
7use turbo_rcstr::RcStr;
8use turbo_tasks::{
9    FxIndexMap, FxIndexSet, NonLocalValue, ReadRef, ResolvedVc, TryJoinIterExt, ValueToString, Vc,
10    debug::ValueDebugFormat,
11    graph::{AdjacencyMap, GraphTraversal, Visit, VisitControlFlow},
12    trace::TraceRawVcs,
13};
14use turbo_tasks_fs::FileSystemPath;
15use turbopack::css::chunk::CssChunkPlaceable;
16use turbopack_core::{
17    chunk::ChunkingType, module::Module, reference::primary_chunkable_referenced_modules,
18};
19
20use crate::{
21    next_client_reference::{
22        CssClientReferenceModule,
23        ecmascript_client_reference::ecmascript_client_reference_module::EcmascriptClientReferenceModule,
24    },
25    next_server_component::server_component_module::NextServerComponentModule,
26    next_server_utility::server_utility_module::NextServerUtilityModule,
27};
28
29#[derive(
30    Copy,
31    Clone,
32    Eq,
33    PartialEq,
34    Hash,
35    Serialize,
36    Deserialize,
37    Debug,
38    ValueDebugFormat,
39    TraceRawVcs,
40    NonLocalValue,
41)]
42pub struct ClientReference {
43    pub server_component: Option<ResolvedVc<NextServerComponentModule>>,
44    pub ty: ClientReferenceType,
45}
46
47impl ClientReference {
48    pub fn server_component(&self) -> Option<ResolvedVc<NextServerComponentModule>> {
49        self.server_component
50    }
51
52    pub fn ty(&self) -> ClientReferenceType {
53        self.ty
54    }
55}
56
57#[derive(
58    Copy,
59    Clone,
60    Eq,
61    PartialEq,
62    Hash,
63    Serialize,
64    Deserialize,
65    Debug,
66    ValueDebugFormat,
67    TraceRawVcs,
68    NonLocalValue,
69)]
70pub enum ClientReferenceType {
71    EcmascriptClientReference(ResolvedVc<EcmascriptClientReferenceModule>),
72    CssClientReference(ResolvedVc<Box<dyn CssChunkPlaceable>>),
73}
74
75#[turbo_tasks::value(shared)]
76#[derive(Clone, Debug)]
77pub struct ClientReferenceGraphResult {
78    pub client_references: Vec<ClientReference>,
79    /// Only the [`ClientReferenceType::EcmascriptClientReference`]s are listed in this map.
80    #[allow(clippy::type_complexity)]
81    pub client_references_by_server_component:
82        FxIndexMap<Option<ResolvedVc<NextServerComponentModule>>, Vec<ResolvedVc<Box<dyn Module>>>>,
83    pub server_component_entries: Vec<ResolvedVc<NextServerComponentModule>>,
84    pub server_utils: Vec<ResolvedVc<NextServerUtilityModule>>,
85    pub visited_nodes: ResolvedVc<VisitedClientReferenceGraphNodes>,
86}
87
88impl Default for ClientReferenceGraphResult {
89    fn default() -> Self {
90        ClientReferenceGraphResult {
91            client_references: Default::default(),
92            client_references_by_server_component: Default::default(),
93            server_component_entries: Default::default(),
94            server_utils: Default::default(),
95            visited_nodes: VisitedClientReferenceGraphNodes(Default::default()).resolved_cell(),
96        }
97    }
98}
99
100#[turbo_tasks::value(shared)]
101pub struct VisitedClientReferenceGraphNodes(FxHashSet<VisitClientReferenceNode>);
102
103#[turbo_tasks::value_impl]
104impl VisitedClientReferenceGraphNodes {
105    #[turbo_tasks::function]
106    pub fn empty() -> Vc<Self> {
107        VisitedClientReferenceGraphNodes(Default::default()).cell()
108    }
109}
110
111#[turbo_tasks::value(transparent)]
112pub struct ClientReferenceTypes(FxIndexSet<ClientReferenceType>);
113
114#[turbo_tasks::value_impl]
115impl ClientReferenceGraphResult {
116    #[turbo_tasks::function]
117    pub fn types(&self) -> Vc<ClientReferenceTypes> {
118        Vc::cell(
119            self.client_references
120                .iter()
121                .map(|r| r.ty())
122                .collect::<FxIndexSet<_>>(),
123        )
124    }
125}
126
127impl ClientReferenceGraphResult {
128    /// Merges multiple return values of client_reference_graph together.
129    pub fn extend(&mut self, other: &Self) {
130        self.client_references
131            .extend(other.client_references.iter().copied());
132        for (k, v) in other.client_references_by_server_component.iter() {
133            self.client_references_by_server_component
134                .entry(*k)
135                .or_insert_with(Vec::new)
136                .extend(v);
137        }
138        self.server_component_entries
139            .extend(other.server_component_entries.iter().copied());
140        self.server_utils.extend(other.server_utils.iter().copied());
141        // This is merged already by `client_reference_graph` itself
142        self.visited_nodes = other.visited_nodes;
143    }
144}
145
146#[turbo_tasks::value(shared)]
147#[derive(Clone, Debug)]
148pub struct ServerEntries {
149    pub server_component_entries: Vec<ResolvedVc<NextServerComponentModule>>,
150    pub server_utils: Vec<ResolvedVc<NextServerUtilityModule>>,
151}
152
153#[turbo_tasks::function]
154pub async fn find_server_entries(
155    entry: ResolvedVc<Box<dyn Module>>,
156    include_traced: bool,
157) -> Result<Vc<ServerEntries>> {
158    async move {
159        let entry_path = entry.ident().path().to_resolved().await?;
160        let graph = AdjacencyMap::new()
161            .skip_duplicates()
162            .visit(
163                vec![VisitClientReferenceNode {
164                    state: { VisitClientReferenceNodeState::Entry { entry_path } },
165                    ty: VisitClientReferenceNodeType::Internal(
166                        entry,
167                        entry.ident().to_string().await?,
168                    ),
169                }],
170                VisitClientReference {
171                    stop_at_server_entries: true,
172                    include_traced,
173                },
174            )
175            .await
176            .completed()?
177            .into_inner();
178
179        let mut server_component_entries = vec![];
180        let mut server_utils = vec![];
181        for node in graph.postorder_topological() {
182            match &node.ty {
183                VisitClientReferenceNodeType::ServerUtilEntry(server_util, _) => {
184                    server_utils.push(*server_util);
185                }
186                VisitClientReferenceNodeType::ServerComponentEntry(server_component, _) => {
187                    server_component_entries.push(*server_component);
188                }
189                VisitClientReferenceNodeType::Internal(_, _)
190                | VisitClientReferenceNodeType::ClientReference(_, _) => {}
191            }
192        }
193
194        Ok(ServerEntries {
195            server_component_entries,
196            server_utils,
197        }
198        .cell())
199    }
200    .instrument(tracing::info_span!("find server entries"))
201    .await
202}
203
204struct VisitClientReference {
205    /// Whether to walk ChunkingType::Traced references
206    include_traced: bool,
207    /// Used to discover ServerComponents and ServerUtils
208    stop_at_server_entries: bool,
209}
210
211#[derive(
212    Clone,
213    Eq,
214    PartialEq,
215    Hash,
216    Serialize,
217    Deserialize,
218    Debug,
219    ValueDebugFormat,
220    TraceRawVcs,
221    NonLocalValue,
222)]
223struct VisitClientReferenceNode {
224    state: VisitClientReferenceNodeState,
225    ty: VisitClientReferenceNodeType,
226}
227
228#[derive(
229    Clone,
230    Copy,
231    Eq,
232    PartialEq,
233    Hash,
234    Serialize,
235    Deserialize,
236    Debug,
237    ValueDebugFormat,
238    TraceRawVcs,
239    NonLocalValue,
240)]
241enum VisitClientReferenceNodeState {
242    Entry {
243        entry_path: ResolvedVc<FileSystemPath>,
244    },
245    InServerComponent {
246        server_component: ResolvedVc<NextServerComponentModule>,
247    },
248    InServerUtil,
249}
250impl VisitClientReferenceNodeState {
251    fn server_component(&self) -> Option<ResolvedVc<NextServerComponentModule>> {
252        match self {
253            VisitClientReferenceNodeState::Entry { .. } => None,
254            VisitClientReferenceNodeState::InServerComponent { server_component } => {
255                Some(*server_component)
256            }
257            VisitClientReferenceNodeState::InServerUtil => None,
258        }
259    }
260}
261
262#[derive(
263    Clone,
264    Eq,
265    PartialEq,
266    Hash,
267    Serialize,
268    Deserialize,
269    Debug,
270    ValueDebugFormat,
271    TraceRawVcs,
272    NonLocalValue,
273)]
274enum VisitClientReferenceNodeType {
275    ClientReference(ClientReference, ReadRef<RcStr>),
276    ServerComponentEntry(ResolvedVc<NextServerComponentModule>, ReadRef<RcStr>),
277    ServerUtilEntry(ResolvedVc<NextServerUtilityModule>, ReadRef<RcStr>),
278    Internal(ResolvedVc<Box<dyn Module>>, ReadRef<RcStr>),
279}
280
281impl Visit<VisitClientReferenceNode> for VisitClientReference {
282    type Edge = VisitClientReferenceNode;
283    type EdgesIntoIter = Vec<Self::Edge>;
284    type EdgesFuture = impl Future<Output = Result<Self::EdgesIntoIter>>;
285
286    fn visit(&mut self, edge: Self::Edge) -> VisitControlFlow<VisitClientReferenceNode> {
287        if self.stop_at_server_entries
288            && matches!(
289                edge.ty,
290                VisitClientReferenceNodeType::ServerUtilEntry(..)
291                    | VisitClientReferenceNodeType::ServerComponentEntry(..)
292            )
293        {
294            return VisitControlFlow::Skip(edge);
295        }
296
297        match edge.ty {
298            VisitClientReferenceNodeType::ClientReference(..) => VisitControlFlow::Skip(edge),
299            VisitClientReferenceNodeType::Internal(..)
300            | VisitClientReferenceNodeType::ServerUtilEntry(..)
301            | VisitClientReferenceNodeType::ServerComponentEntry(..) => {
302                VisitControlFlow::Continue(edge)
303            }
304        }
305    }
306
307    fn edges(&mut self, node: &VisitClientReferenceNode) -> Self::EdgesFuture {
308        let node = node.clone();
309        let include_traced = self.include_traced;
310        async move {
311            let parent_module = match node.ty {
312                // This should never occur since we always skip visiting these
313                // nodes' edges.
314                VisitClientReferenceNodeType::ClientReference(..) => return Ok(vec![]),
315                VisitClientReferenceNodeType::Internal(module, _) => module,
316                VisitClientReferenceNodeType::ServerUtilEntry(module, _) => {
317                    ResolvedVc::upcast(module)
318                }
319                VisitClientReferenceNodeType::ServerComponentEntry(module, _) => {
320                    ResolvedVc::upcast(module)
321                }
322            };
323
324            let referenced_modules =
325                primary_chunkable_referenced_modules(*parent_module, include_traced).await?;
326
327            let referenced_modules = referenced_modules
328                .iter()
329                .flat_map(|(chunking_type, modules)| match chunking_type {
330                    ChunkingType::Traced => None,
331                    _ => Some(modules.iter()),
332                })
333                .flatten()
334                .map(|module| async move {
335                    if let Some(client_reference_module) =
336                        ResolvedVc::try_downcast_type::<EcmascriptClientReferenceModule>(*module)
337                    {
338                        return Ok(VisitClientReferenceNode {
339                            state: node.state,
340                            ty: VisitClientReferenceNodeType::ClientReference(
341                                ClientReference {
342                                    server_component: node.state.server_component(),
343                                    ty: ClientReferenceType::EcmascriptClientReference(
344                                        client_reference_module,
345                                    ),
346                                },
347                                client_reference_module.ident().to_string().await?,
348                            ),
349                        });
350                    }
351
352                    if let Some(client_reference_module) =
353                        ResolvedVc::try_downcast_type::<CssClientReferenceModule>(*module)
354                    {
355                        return Ok(VisitClientReferenceNode {
356                            state: node.state,
357                            ty: VisitClientReferenceNodeType::ClientReference(
358                                ClientReference {
359                                    server_component: node.state.server_component(),
360                                    ty: ClientReferenceType::CssClientReference(
361                                        client_reference_module.await?.client_module,
362                                    ),
363                                },
364                                client_reference_module.ident().to_string().await?,
365                            ),
366                        });
367                    }
368
369                    if let Some(server_component_asset) =
370                        ResolvedVc::try_downcast_type::<NextServerComponentModule>(*module)
371                    {
372                        return Ok(VisitClientReferenceNode {
373                            state: VisitClientReferenceNodeState::InServerComponent {
374                                server_component: server_component_asset,
375                            },
376                            ty: VisitClientReferenceNodeType::ServerComponentEntry(
377                                server_component_asset,
378                                server_component_asset.ident().to_string().await?,
379                            ),
380                        });
381                    }
382
383                    if let Some(server_util_module) =
384                        ResolvedVc::try_downcast_type::<NextServerUtilityModule>(*module)
385                    {
386                        return Ok(VisitClientReferenceNode {
387                            state: VisitClientReferenceNodeState::InServerUtil,
388                            ty: VisitClientReferenceNodeType::ServerUtilEntry(
389                                server_util_module,
390                                module.ident().to_string().await?,
391                            ),
392                        });
393                    }
394
395                    Ok(VisitClientReferenceNode {
396                        state: node.state,
397                        ty: VisitClientReferenceNodeType::Internal(
398                            *module,
399                            module.ident().to_string().await?,
400                        ),
401                    })
402                });
403
404            let assets = referenced_modules.try_join().await?;
405
406            Ok(assets)
407        }
408    }
409
410    fn span(&mut self, node: &VisitClientReferenceNode) -> tracing::Span {
411        match &node.ty {
412            VisitClientReferenceNodeType::ClientReference(_, name) => {
413                tracing::info_span!("client reference", name = name.to_string())
414            }
415            VisitClientReferenceNodeType::Internal(_, name) => {
416                tracing::info_span!("module", name = name.to_string())
417            }
418            VisitClientReferenceNodeType::ServerUtilEntry(_, name) => {
419                tracing::info_span!("server util", name = name.to_string())
420            }
421            VisitClientReferenceNodeType::ServerComponentEntry(_, name) => {
422                tracing::info_span!("layout segment", name = name.to_string())
423            }
424        }
425    }
426}