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