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    FxIndexSet, NonLocalValue, ReadRef, ResolvedVc, TryJoinIterExt, ValueToString, 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#[derive(
57    Copy,
58    Clone,
59    Eq,
60    PartialEq,
61    Hash,
62    Serialize,
63    Deserialize,
64    Debug,
65    ValueDebugFormat,
66    TraceRawVcs,
67    NonLocalValue,
68)]
69pub enum ClientReferenceType {
70    EcmascriptClientReference(ResolvedVc<EcmascriptClientReferenceModule>),
71    CssClientReference(ResolvedVc<Box<dyn CssChunkPlaceable>>),
72}
73
74#[turbo_tasks::value(shared)]
75#[derive(Clone, Debug, Default)]
76pub struct ClientReferenceGraphResult {
77    pub client_references: Vec<ClientReference>,
78    pub server_component_entries: Vec<ResolvedVc<NextServerComponentModule>>,
79    pub server_utils: Vec<ResolvedVc<NextServerUtilityModule>>,
80}
81
82#[turbo_tasks::value(shared)]
83pub struct VisitedClientReferenceGraphNodes(FxHashSet<FindServerEntriesNode>);
84
85#[turbo_tasks::value_impl]
86impl VisitedClientReferenceGraphNodes {
87    #[turbo_tasks::function]
88    pub fn empty() -> Vc<Self> {
89        VisitedClientReferenceGraphNodes(Default::default()).cell()
90    }
91}
92
93#[turbo_tasks::value(transparent)]
94pub struct ClientReferenceTypes(FxIndexSet<ClientReferenceType>);
95
96#[turbo_tasks::value_impl]
97impl ClientReferenceGraphResult {
98    #[turbo_tasks::function]
99    pub fn types(&self) -> Vc<ClientReferenceTypes> {
100        Vc::cell(
101            self.client_references
102                .iter()
103                .map(|r| r.ty())
104                .collect::<FxIndexSet<_>>(),
105        )
106    }
107}
108
109#[turbo_tasks::value(shared)]
110#[derive(Clone, Debug)]
111pub struct ServerEntries {
112    pub server_component_entries: Vec<ResolvedVc<NextServerComponentModule>>,
113    pub server_utils: Vec<ResolvedVc<NextServerUtilityModule>>,
114}
115
116/// For a given RSC entry, finds all server components (i.e. layout segments) and server utils that
117/// are referenced by the entry.
118#[turbo_tasks::function]
119pub async fn find_server_entries(
120    entry: ResolvedVc<Box<dyn Module>>,
121    include_traced: bool,
122) -> Result<Vc<ServerEntries>> {
123    async move {
124        let graph = AdjacencyMap::new()
125            .skip_duplicates()
126            .visit(
127                vec![FindServerEntriesNode::Internal(
128                    entry,
129                    entry.ident().to_string().await?,
130                )],
131                FindServerEntries { include_traced },
132            )
133            .await
134            .completed()?
135            .into_inner();
136
137        let mut server_component_entries = vec![];
138        let mut server_utils = vec![];
139        for node in graph.postorder_topological() {
140            match node {
141                FindServerEntriesNode::ServerUtilEntry(server_util, _) => {
142                    server_utils.push(*server_util);
143                }
144                FindServerEntriesNode::ServerComponentEntry(server_component, _) => {
145                    server_component_entries.push(*server_component);
146                }
147                FindServerEntriesNode::Internal(_, _) | FindServerEntriesNode::ClientReference => {}
148            }
149        }
150
151        Ok(ServerEntries {
152            server_component_entries,
153            server_utils,
154        }
155        .cell())
156    }
157    .instrument(tracing::info_span!("find server entries"))
158    .await
159}
160
161struct FindServerEntries {
162    /// Whether to walk ChunkingType::Traced references
163    include_traced: bool,
164}
165
166#[derive(
167    Clone,
168    Eq,
169    PartialEq,
170    Hash,
171    Serialize,
172    Deserialize,
173    Debug,
174    ValueDebugFormat,
175    TraceRawVcs,
176    NonLocalValue,
177)]
178enum FindServerEntriesNode {
179    ClientReference,
180    ServerComponentEntry(ResolvedVc<NextServerComponentModule>, ReadRef<RcStr>),
181    ServerUtilEntry(ResolvedVc<NextServerUtilityModule>, ReadRef<RcStr>),
182    Internal(ResolvedVc<Box<dyn Module>>, ReadRef<RcStr>),
183}
184
185impl Visit<FindServerEntriesNode> for FindServerEntries {
186    type Edge = FindServerEntriesNode;
187    type EdgesIntoIter = Vec<Self::Edge>;
188    type EdgesFuture = impl Future<Output = Result<Self::EdgesIntoIter>>;
189
190    fn visit(&mut self, edge: Self::Edge) -> VisitControlFlow<FindServerEntriesNode> {
191        match edge {
192            FindServerEntriesNode::Internal(..) => VisitControlFlow::Continue(edge),
193            FindServerEntriesNode::ClientReference
194            | FindServerEntriesNode::ServerUtilEntry(..)
195            | FindServerEntriesNode::ServerComponentEntry(..) => VisitControlFlow::Skip(edge),
196        }
197    }
198
199    fn edges(&mut self, node: &FindServerEntriesNode) -> Self::EdgesFuture {
200        let include_traced = self.include_traced;
201        let parent_module = match node {
202            // This should never occur since we always skip visiting these
203            // nodes' edges.
204            FindServerEntriesNode::ClientReference => {
205                unreachable!("ClientReference node should not be visited")
206            }
207            FindServerEntriesNode::Internal(module, _) => **module,
208            FindServerEntriesNode::ServerUtilEntry(module, _) => Vc::upcast(**module),
209            FindServerEntriesNode::ServerComponentEntry(module, _) => Vc::upcast(**module),
210        };
211        async move {
212            // Pass include_traced to reuse the same cached `primary_chunkable_referenced_modules`
213            // task result, but the traced references will be filtered out again afterwards.
214            let referenced_modules =
215                primary_chunkable_referenced_modules(parent_module, include_traced).await?;
216
217            let referenced_modules = referenced_modules
218                .iter()
219                .flat_map(|(chunking_type, _, modules)| match chunking_type {
220                    ChunkingType::Traced => None,
221                    _ => Some(modules.iter()),
222                })
223                .flatten()
224                .map(async |module| {
225                    if ResolvedVc::try_downcast_type::<EcmascriptClientReferenceModule>(*module)
226                        .is_some()
227                        || ResolvedVc::try_downcast_type::<CssClientReferenceModule>(*module)
228                            .is_some()
229                    {
230                        return Ok(FindServerEntriesNode::ClientReference);
231                    }
232
233                    if let Some(server_component_asset) =
234                        ResolvedVc::try_downcast_type::<NextServerComponentModule>(*module)
235                    {
236                        return Ok(FindServerEntriesNode::ServerComponentEntry(
237                            server_component_asset,
238                            server_component_asset.ident().to_string().await?,
239                        ));
240                    }
241
242                    if let Some(server_util_module) =
243                        ResolvedVc::try_downcast_type::<NextServerUtilityModule>(*module)
244                    {
245                        return Ok(FindServerEntriesNode::ServerUtilEntry(
246                            server_util_module,
247                            module.ident().to_string().await?,
248                        ));
249                    }
250
251                    Ok(FindServerEntriesNode::Internal(
252                        *module,
253                        module.ident().to_string().await?,
254                    ))
255                });
256
257            let assets = referenced_modules.try_join().await?;
258
259            Ok(assets)
260        }
261    }
262
263    fn span(&mut self, node: &FindServerEntriesNode) -> tracing::Span {
264        match node {
265            FindServerEntriesNode::ClientReference => {
266                tracing::info_span!("client reference")
267            }
268            FindServerEntriesNode::Internal(_, name) => {
269                tracing::info_span!("module", name = name.to_string())
270            }
271            FindServerEntriesNode::ServerUtilEntry(_, name) => {
272                tracing::info_span!("server util", name = name.to_string())
273            }
274            FindServerEntriesNode::ServerComponentEntry(_, name) => {
275                tracing::info_span!("layout segment", name = name.to_string())
276            }
277        }
278    }
279}