next_core/next_client_reference/
visit_client_reference.rs1use 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 #[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 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#[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 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 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 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}