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