next_core/next_client_reference/
visit_client_reference.rs

1use std::future::Future;
2
3use anyhow::Result;
4use bincode::{Decode, Encode};
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_core::{
14    chunk::ChunkingType, module::Module, reference::primary_chunkable_referenced_modules,
15};
16use turbopack_css::chunk::CssChunkPlaceable;
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    Debug,
34    ValueDebugFormat,
35    TraceRawVcs,
36    NonLocalValue,
37    Encode,
38    Decode,
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    Debug,
52    ValueDebugFormat,
53    TraceRawVcs,
54    NonLocalValue,
55    Encode,
56    Decode,
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    include_binding_usage: bool,
85) -> Result<Vc<ServerEntries>> {
86    async move {
87        let emit_spans = tracing::enabled!(Level::INFO);
88        let graph = AdjacencyMap::new()
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                    emit_spans,
101                    include_traced,
102                    include_binding_usage,
103                },
104            )
105            .await
106            .completed()?;
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    emit_spans: bool,
134    /// Whether to walk ChunkingType::Traced references
135    include_traced: bool,
136    /// Whether to read the binding usage information from modules
137    include_binding_usage: bool,
138}
139
140#[derive(Clone, Eq, PartialEq, Hash, Debug, ValueDebugFormat, TraceRawVcs, NonLocalValue)]
141enum FindServerEntriesNode {
142    ClientReference,
143    ServerComponentEntry(
144        ResolvedVc<NextServerComponentModule>,
145        Option<ReadRef<RcStr>>,
146    ),
147    ServerUtilEntry(ResolvedVc<NextServerUtilityModule>, Option<ReadRef<RcStr>>),
148    Internal(ResolvedVc<Box<dyn Module>>, Option<ReadRef<RcStr>>),
149}
150
151impl Visit<FindServerEntriesNode> for FindServerEntries {
152    type EdgesIntoIter = Vec<(FindServerEntriesNode, ())>;
153    type EdgesFuture = impl Future<Output = Result<Self::EdgesIntoIter>>;
154
155    fn visit(&mut self, node: &FindServerEntriesNode, _edge: Option<&()>) -> VisitControlFlow {
156        match node {
157            FindServerEntriesNode::Internal(..) => VisitControlFlow::Continue,
158            FindServerEntriesNode::ClientReference
159            | FindServerEntriesNode::ServerUtilEntry(..)
160            | FindServerEntriesNode::ServerComponentEntry(..) => VisitControlFlow::Skip,
161        }
162    }
163
164    fn edges(&mut self, node: &FindServerEntriesNode) -> Self::EdgesFuture {
165        let include_traced = self.include_traced;
166        let include_binding_usage = self.include_binding_usage;
167        let parent_module = match node {
168            // This should never occur since we always skip visiting these
169            // nodes' edges.
170            FindServerEntriesNode::ClientReference => {
171                unreachable!("ClientReference node should not be visited")
172            }
173            FindServerEntriesNode::Internal(module, _) => **module,
174            FindServerEntriesNode::ServerUtilEntry(module, _) => Vc::upcast(**module),
175            FindServerEntriesNode::ServerComponentEntry(module, _) => Vc::upcast(**module),
176        };
177        let emit_spans = self.emit_spans;
178        async move {
179            // Pass include_traced and include_binding_usage to reuse the same cached
180            // `primary_chunkable_referenced_modules` task result, but the traced references will be
181            // filtered out again afterwards.
182            let referenced_modules = primary_chunkable_referenced_modules(
183                parent_module,
184                include_traced,
185                include_binding_usage,
186            )
187            .await?;
188
189            let referenced_modules = referenced_modules
190                .iter()
191                .flat_map(|(_, resolved)| match resolved.chunking_type {
192                    ChunkingType::Traced => None,
193                    _ => Some(resolved.modules.iter()),
194                })
195                .flatten()
196                .map(async |module| {
197                    if ResolvedVc::try_downcast_type::<EcmascriptClientReferenceModule>(*module)
198                        .is_some()
199                        || ResolvedVc::try_downcast_type::<CssClientReferenceModule>(*module)
200                            .is_some()
201                    {
202                        return Ok((FindServerEntriesNode::ClientReference, ()));
203                    }
204
205                    if let Some(server_component_asset) =
206                        ResolvedVc::try_downcast_type::<NextServerComponentModule>(*module)
207                    {
208                        return Ok((
209                            FindServerEntriesNode::ServerComponentEntry(
210                                server_component_asset,
211                                if emit_spans {
212                                    // INVALIDATION: we don't need to invalidate when the span name
213                                    // changes
214                                    Some(server_component_asset.ident_string().untracked().await?)
215                                } else {
216                                    None
217                                },
218                            ),
219                            (),
220                        ));
221                    }
222
223                    if let Some(server_util_module) =
224                        ResolvedVc::try_downcast_type::<NextServerUtilityModule>(*module)
225                    {
226                        return Ok((
227                            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                    }
240
241                    Ok((
242                        FindServerEntriesNode::Internal(
243                            *module,
244                            if emit_spans {
245                                // INVALIDATION: we don't need to invalidate when the span name
246                                // changes
247                                Some(module.ident_string().untracked().await?)
248                            } else {
249                                None
250                            },
251                        ),
252                        (),
253                    ))
254                });
255
256            let assets = referenced_modules.try_join().await?;
257
258            Ok(assets)
259        }
260    }
261
262    fn span(&mut self, node: &FindServerEntriesNode, _edge: Option<&()>) -> tracing::Span {
263        if !self.emit_spans {
264            return Span::none();
265        }
266        match node {
267            FindServerEntriesNode::ClientReference => {
268                tracing::info_span!("client reference")
269            }
270            FindServerEntriesNode::Internal(_, name) => {
271                tracing::info_span!("module", name = display(name.as_ref().unwrap()))
272            }
273            FindServerEntriesNode::ServerUtilEntry(_, name) => {
274                tracing::info_span!("server util", name = display(name.as_ref().unwrap()))
275            }
276            FindServerEntriesNode::ServerComponentEntry(_, name) => {
277                tracing::info_span!("layout segment", name = display(name.as_ref().unwrap()))
278            }
279        }
280    }
281}