Skip to main content

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