next_core/next_client_reference/
visit_client_reference.rs

1use std::future::Future;
2
3use anyhow::Result;
4use serde::{Deserialize, Serialize};
5use tracing::{Instrument, Level, Span};
6use turbo_rcstr::RcStr;
7use turbo_tasks::{
8    NonLocalValue, ReadRef, ResolvedVc, TryJoinIterExt, Vc,
9    debug::ValueDebugFormat,
10    graph::{AdjacencyMap, GraphTraversal, Visit, VisitControlFlow},
11    trace::TraceRawVcs,
12};
13use turbopack::css::chunk::CssChunkPlaceable;
14use turbopack_core::{
15    chunk::ChunkingType, module::Module, reference::primary_chunkable_referenced_modules,
16};
17
18use crate::{
19    next_client_reference::{
20        CssClientReferenceModule,
21        ecmascript_client_reference::ecmascript_client_reference_module::EcmascriptClientReferenceModule,
22    },
23    next_server_component::server_component_module::NextServerComponentModule,
24    next_server_utility::server_utility_module::NextServerUtilityModule,
25};
26
27#[derive(
28    Copy,
29    Clone,
30    Eq,
31    PartialEq,
32    Hash,
33    Serialize,
34    Deserialize,
35    Debug,
36    ValueDebugFormat,
37    TraceRawVcs,
38    NonLocalValue,
39)]
40pub struct ClientReference {
41    pub server_component: Option<ResolvedVc<NextServerComponentModule>>,
42    pub ty: ClientReferenceType,
43}
44
45#[derive(
46    Copy,
47    Clone,
48    Eq,
49    PartialEq,
50    Hash,
51    Serialize,
52    Deserialize,
53    Debug,
54    ValueDebugFormat,
55    TraceRawVcs,
56    NonLocalValue,
57)]
58pub enum ClientReferenceType {
59    EcmascriptClientReference(ResolvedVc<EcmascriptClientReferenceModule>),
60    CssClientReference(ResolvedVc<Box<dyn CssChunkPlaceable>>),
61}
62
63#[turbo_tasks::value(shared)]
64#[derive(Clone, Debug, Default)]
65pub struct ClientReferenceGraphResult {
66    pub client_references: Vec<ClientReference>,
67    pub server_component_entries: Vec<ResolvedVc<NextServerComponentModule>>,
68    pub server_utils: Vec<ResolvedVc<NextServerUtilityModule>>,
69}
70
71#[turbo_tasks::value(shared)]
72#[derive(Clone, Debug)]
73pub struct ServerEntries {
74    pub server_component_entries: Vec<ResolvedVc<NextServerComponentModule>>,
75    pub server_utils: Vec<ResolvedVc<NextServerUtilityModule>>,
76}
77
78/// For a given RSC entry, finds all server components (i.e. layout segments) and server utils that
79/// are referenced by the entry.
80#[turbo_tasks::function]
81pub async fn find_server_entries(
82    entry: ResolvedVc<Box<dyn Module>>,
83    include_traced: bool,
84) -> Result<Vc<ServerEntries>> {
85    async move {
86        let emit_spans = tracing::enabled!(Level::INFO);
87        let graph = AdjacencyMap::new()
88            .skip_duplicates()
89            .visit(
90                vec![FindServerEntriesNode::Internal(
91                    entry,
92                    if emit_spans {
93                        // INVALIDATION: we don't need to invalidate when the span name changes
94                        Some(entry.ident_string().untracked().await?)
95                    } else {
96                        None
97                    },
98                )],
99                FindServerEntries {
100                    include_traced,
101                    emit_spans,
102                },
103            )
104            .await
105            .completed()?
106            .into_inner();
107
108        let mut server_component_entries = vec![];
109        let mut server_utils = vec![];
110        for node in graph.postorder_topological() {
111            match node {
112                FindServerEntriesNode::ServerUtilEntry(server_util, _) => {
113                    server_utils.push(*server_util);
114                }
115                FindServerEntriesNode::ServerComponentEntry(server_component, _) => {
116                    server_component_entries.push(*server_component);
117                }
118                FindServerEntriesNode::Internal(_, _) | FindServerEntriesNode::ClientReference => {}
119            }
120        }
121
122        Ok(ServerEntries {
123            server_component_entries,
124            server_utils,
125        }
126        .cell())
127    }
128    .instrument(tracing::info_span!("find server entries"))
129    .await
130}
131
132struct FindServerEntries {
133    /// Whether to walk ChunkingType::Traced references
134    include_traced: bool,
135    emit_spans: bool,
136}
137
138#[derive(
139    Clone,
140    Eq,
141    PartialEq,
142    Hash,
143    Serialize,
144    Deserialize,
145    Debug,
146    ValueDebugFormat,
147    TraceRawVcs,
148    NonLocalValue,
149)]
150enum FindServerEntriesNode {
151    ClientReference,
152    ServerComponentEntry(
153        ResolvedVc<NextServerComponentModule>,
154        Option<ReadRef<RcStr>>,
155    ),
156    ServerUtilEntry(ResolvedVc<NextServerUtilityModule>, Option<ReadRef<RcStr>>),
157    Internal(ResolvedVc<Box<dyn Module>>, Option<ReadRef<RcStr>>),
158}
159
160impl Visit<FindServerEntriesNode> for FindServerEntries {
161    type Edge = FindServerEntriesNode;
162    type EdgesIntoIter = Vec<Self::Edge>;
163    type EdgesFuture = impl Future<Output = Result<Self::EdgesIntoIter>>;
164
165    fn visit(&mut self, edge: Self::Edge) -> VisitControlFlow<FindServerEntriesNode> {
166        match edge {
167            FindServerEntriesNode::Internal(..) => VisitControlFlow::Continue(edge),
168            FindServerEntriesNode::ClientReference
169            | FindServerEntriesNode::ServerUtilEntry(..)
170            | FindServerEntriesNode::ServerComponentEntry(..) => VisitControlFlow::Skip(edge),
171        }
172    }
173
174    fn edges(&mut self, node: &FindServerEntriesNode) -> Self::EdgesFuture {
175        let include_traced = self.include_traced;
176        let parent_module = match node {
177            // This should never occur since we always skip visiting these
178            // nodes' edges.
179            FindServerEntriesNode::ClientReference => {
180                unreachable!("ClientReference node should not be visited")
181            }
182            FindServerEntriesNode::Internal(module, _) => **module,
183            FindServerEntriesNode::ServerUtilEntry(module, _) => Vc::upcast(**module),
184            FindServerEntriesNode::ServerComponentEntry(module, _) => Vc::upcast(**module),
185        };
186        let emit_spans = self.emit_spans;
187        async move {
188            // Pass include_traced to reuse the same cached `primary_chunkable_referenced_modules`
189            // task result, but the traced references will be filtered out again afterwards.
190            let referenced_modules =
191                primary_chunkable_referenced_modules(parent_module, include_traced).await?;
192
193            let referenced_modules = referenced_modules
194                .iter()
195                .flat_map(|(chunking_type, _, modules)| match chunking_type {
196                    ChunkingType::Traced => None,
197                    _ => Some(modules.iter()),
198                })
199                .flatten()
200                .map(async |module| {
201                    if ResolvedVc::try_downcast_type::<EcmascriptClientReferenceModule>(*module)
202                        .is_some()
203                        || ResolvedVc::try_downcast_type::<CssClientReferenceModule>(*module)
204                            .is_some()
205                    {
206                        return Ok(FindServerEntriesNode::ClientReference);
207                    }
208
209                    if let Some(server_component_asset) =
210                        ResolvedVc::try_downcast_type::<NextServerComponentModule>(*module)
211                    {
212                        return Ok(FindServerEntriesNode::ServerComponentEntry(
213                            server_component_asset,
214                            if emit_spans {
215                                // INVALIDATION: we don't need to invalidate when the span name
216                                // changes
217                                Some(server_component_asset.ident_string().untracked().await?)
218                            } else {
219                                None
220                            },
221                        ));
222                    }
223
224                    if let Some(server_util_module) =
225                        ResolvedVc::try_downcast_type::<NextServerUtilityModule>(*module)
226                    {
227                        return Ok(FindServerEntriesNode::ServerUtilEntry(
228                            server_util_module,
229                            if emit_spans {
230                                // INVALIDATION: we don't need to invalidate when the span name
231                                // changes
232                                Some(module.ident_string().untracked().await?)
233                            } else {
234                                None
235                            },
236                        ));
237                    }
238
239                    Ok(FindServerEntriesNode::Internal(
240                        *module,
241                        if emit_spans {
242                            // INVALIDATION: we don't need to invalidate when the span name changes
243                            Some(module.ident_string().untracked().await?)
244                        } else {
245                            None
246                        },
247                    ))
248                });
249
250            let assets = referenced_modules.try_join().await?;
251
252            Ok(assets)
253        }
254    }
255
256    fn span(&mut self, node: &FindServerEntriesNode) -> tracing::Span {
257        if !self.emit_spans {
258            return Span::current();
259        }
260        match node {
261            FindServerEntriesNode::ClientReference => {
262                tracing::info_span!("client reference")
263            }
264            FindServerEntriesNode::Internal(_, name) => {
265                tracing::info_span!("module", name = display(name.as_ref().unwrap()))
266            }
267            FindServerEntriesNode::ServerUtilEntry(_, name) => {
268                tracing::info_span!("server util", name = display(name.as_ref().unwrap()))
269            }
270            FindServerEntriesNode::ServerComponentEntry(_, name) => {
271                tracing::info_span!("layout segment", name = display(name.as_ref().unwrap()))
272            }
273        }
274    }
275}