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 turbo_tasks_fs::FileSystemPath;
15use turbopack::css::chunk::CssChunkPlaceable;
16use turbopack_core::{
17 chunk::ChunkingType, module::Module, reference::primary_chunkable_referenced_modules,
18};
19
20use crate::{
21 next_client_reference::{
22 CssClientReferenceModule,
23 ecmascript_client_reference::ecmascript_client_reference_module::EcmascriptClientReferenceModule,
24 },
25 next_server_component::server_component_module::NextServerComponentModule,
26 next_server_utility::server_utility_module::NextServerUtilityModule,
27};
28
29#[derive(
30 Copy,
31 Clone,
32 Eq,
33 PartialEq,
34 Hash,
35 Serialize,
36 Deserialize,
37 Debug,
38 ValueDebugFormat,
39 TraceRawVcs,
40 NonLocalValue,
41)]
42pub struct ClientReference {
43 pub server_component: Option<ResolvedVc<NextServerComponentModule>>,
44 pub ty: ClientReferenceType,
45}
46
47impl ClientReference {
48 pub fn server_component(&self) -> Option<ResolvedVc<NextServerComponentModule>> {
49 self.server_component
50 }
51
52 pub fn ty(&self) -> ClientReferenceType {
53 self.ty
54 }
55}
56
57#[derive(
58 Copy,
59 Clone,
60 Eq,
61 PartialEq,
62 Hash,
63 Serialize,
64 Deserialize,
65 Debug,
66 ValueDebugFormat,
67 TraceRawVcs,
68 NonLocalValue,
69)]
70pub enum ClientReferenceType {
71 EcmascriptClientReference(ResolvedVc<EcmascriptClientReferenceModule>),
72 CssClientReference(ResolvedVc<Box<dyn CssChunkPlaceable>>),
73}
74
75#[turbo_tasks::value(shared)]
76#[derive(Clone, Debug)]
77pub struct ClientReferenceGraphResult {
78 pub client_references: Vec<ClientReference>,
79 #[allow(clippy::type_complexity)]
81 pub client_references_by_server_component:
82 FxIndexMap<Option<ResolvedVc<NextServerComponentModule>>, Vec<ResolvedVc<Box<dyn Module>>>>,
83 pub server_component_entries: Vec<ResolvedVc<NextServerComponentModule>>,
84 pub server_utils: Vec<ResolvedVc<NextServerUtilityModule>>,
85 pub visited_nodes: ResolvedVc<VisitedClientReferenceGraphNodes>,
86}
87
88impl Default for ClientReferenceGraphResult {
89 fn default() -> Self {
90 ClientReferenceGraphResult {
91 client_references: Default::default(),
92 client_references_by_server_component: Default::default(),
93 server_component_entries: Default::default(),
94 server_utils: Default::default(),
95 visited_nodes: VisitedClientReferenceGraphNodes(Default::default()).resolved_cell(),
96 }
97 }
98}
99
100#[turbo_tasks::value(shared)]
101pub struct VisitedClientReferenceGraphNodes(FxHashSet<VisitClientReferenceNode>);
102
103#[turbo_tasks::value_impl]
104impl VisitedClientReferenceGraphNodes {
105 #[turbo_tasks::function]
106 pub fn empty() -> Vc<Self> {
107 VisitedClientReferenceGraphNodes(Default::default()).cell()
108 }
109}
110
111#[turbo_tasks::value(transparent)]
112pub struct ClientReferenceTypes(FxIndexSet<ClientReferenceType>);
113
114#[turbo_tasks::value_impl]
115impl ClientReferenceGraphResult {
116 #[turbo_tasks::function]
117 pub fn types(&self) -> Vc<ClientReferenceTypes> {
118 Vc::cell(
119 self.client_references
120 .iter()
121 .map(|r| r.ty())
122 .collect::<FxIndexSet<_>>(),
123 )
124 }
125}
126
127impl ClientReferenceGraphResult {
128 pub fn extend(&mut self, other: &Self) {
130 self.client_references
131 .extend(other.client_references.iter().copied());
132 for (k, v) in other.client_references_by_server_component.iter() {
133 self.client_references_by_server_component
134 .entry(*k)
135 .or_insert_with(Vec::new)
136 .extend(v);
137 }
138 self.server_component_entries
139 .extend(other.server_component_entries.iter().copied());
140 self.server_utils.extend(other.server_utils.iter().copied());
141 self.visited_nodes = other.visited_nodes;
143 }
144}
145
146#[turbo_tasks::value(shared)]
147#[derive(Clone, Debug)]
148pub struct ServerEntries {
149 pub server_component_entries: Vec<ResolvedVc<NextServerComponentModule>>,
150 pub server_utils: Vec<ResolvedVc<NextServerUtilityModule>>,
151}
152
153#[turbo_tasks::function]
154pub async fn find_server_entries(
155 entry: ResolvedVc<Box<dyn Module>>,
156 include_traced: bool,
157) -> Result<Vc<ServerEntries>> {
158 async move {
159 let entry_path = entry.ident().path().to_resolved().await?;
160 let graph = AdjacencyMap::new()
161 .skip_duplicates()
162 .visit(
163 vec![VisitClientReferenceNode {
164 state: { VisitClientReferenceNodeState::Entry { entry_path } },
165 ty: VisitClientReferenceNodeType::Internal(
166 entry,
167 entry.ident().to_string().await?,
168 ),
169 }],
170 VisitClientReference {
171 stop_at_server_entries: true,
172 include_traced,
173 },
174 )
175 .await
176 .completed()?
177 .into_inner();
178
179 let mut server_component_entries = vec![];
180 let mut server_utils = vec![];
181 for node in graph.postorder_topological() {
182 match &node.ty {
183 VisitClientReferenceNodeType::ServerUtilEntry(server_util, _) => {
184 server_utils.push(*server_util);
185 }
186 VisitClientReferenceNodeType::ServerComponentEntry(server_component, _) => {
187 server_component_entries.push(*server_component);
188 }
189 VisitClientReferenceNodeType::Internal(_, _)
190 | VisitClientReferenceNodeType::ClientReference(_, _) => {}
191 }
192 }
193
194 Ok(ServerEntries {
195 server_component_entries,
196 server_utils,
197 }
198 .cell())
199 }
200 .instrument(tracing::info_span!("find server entries"))
201 .await
202}
203
204struct VisitClientReference {
205 include_traced: bool,
207 stop_at_server_entries: bool,
209}
210
211#[derive(
212 Clone,
213 Eq,
214 PartialEq,
215 Hash,
216 Serialize,
217 Deserialize,
218 Debug,
219 ValueDebugFormat,
220 TraceRawVcs,
221 NonLocalValue,
222)]
223struct VisitClientReferenceNode {
224 state: VisitClientReferenceNodeState,
225 ty: VisitClientReferenceNodeType,
226}
227
228#[derive(
229 Clone,
230 Copy,
231 Eq,
232 PartialEq,
233 Hash,
234 Serialize,
235 Deserialize,
236 Debug,
237 ValueDebugFormat,
238 TraceRawVcs,
239 NonLocalValue,
240)]
241enum VisitClientReferenceNodeState {
242 Entry {
243 entry_path: ResolvedVc<FileSystemPath>,
244 },
245 InServerComponent {
246 server_component: ResolvedVc<NextServerComponentModule>,
247 },
248 InServerUtil,
249}
250impl VisitClientReferenceNodeState {
251 fn server_component(&self) -> Option<ResolvedVc<NextServerComponentModule>> {
252 match self {
253 VisitClientReferenceNodeState::Entry { .. } => None,
254 VisitClientReferenceNodeState::InServerComponent { server_component } => {
255 Some(*server_component)
256 }
257 VisitClientReferenceNodeState::InServerUtil => None,
258 }
259 }
260}
261
262#[derive(
263 Clone,
264 Eq,
265 PartialEq,
266 Hash,
267 Serialize,
268 Deserialize,
269 Debug,
270 ValueDebugFormat,
271 TraceRawVcs,
272 NonLocalValue,
273)]
274enum VisitClientReferenceNodeType {
275 ClientReference(ClientReference, ReadRef<RcStr>),
276 ServerComponentEntry(ResolvedVc<NextServerComponentModule>, ReadRef<RcStr>),
277 ServerUtilEntry(ResolvedVc<NextServerUtilityModule>, ReadRef<RcStr>),
278 Internal(ResolvedVc<Box<dyn Module>>, ReadRef<RcStr>),
279}
280
281impl Visit<VisitClientReferenceNode> for VisitClientReference {
282 type Edge = VisitClientReferenceNode;
283 type EdgesIntoIter = Vec<Self::Edge>;
284 type EdgesFuture = impl Future<Output = Result<Self::EdgesIntoIter>>;
285
286 fn visit(&mut self, edge: Self::Edge) -> VisitControlFlow<VisitClientReferenceNode> {
287 if self.stop_at_server_entries
288 && matches!(
289 edge.ty,
290 VisitClientReferenceNodeType::ServerUtilEntry(..)
291 | VisitClientReferenceNodeType::ServerComponentEntry(..)
292 )
293 {
294 return VisitControlFlow::Skip(edge);
295 }
296
297 match edge.ty {
298 VisitClientReferenceNodeType::ClientReference(..) => VisitControlFlow::Skip(edge),
299 VisitClientReferenceNodeType::Internal(..)
300 | VisitClientReferenceNodeType::ServerUtilEntry(..)
301 | VisitClientReferenceNodeType::ServerComponentEntry(..) => {
302 VisitControlFlow::Continue(edge)
303 }
304 }
305 }
306
307 fn edges(&mut self, node: &VisitClientReferenceNode) -> Self::EdgesFuture {
308 let node = node.clone();
309 let include_traced = self.include_traced;
310 async move {
311 let parent_module = match node.ty {
312 VisitClientReferenceNodeType::ClientReference(..) => return Ok(vec![]),
315 VisitClientReferenceNodeType::Internal(module, _) => module,
316 VisitClientReferenceNodeType::ServerUtilEntry(module, _) => {
317 ResolvedVc::upcast(module)
318 }
319 VisitClientReferenceNodeType::ServerComponentEntry(module, _) => {
320 ResolvedVc::upcast(module)
321 }
322 };
323
324 let referenced_modules =
325 primary_chunkable_referenced_modules(*parent_module, include_traced).await?;
326
327 let referenced_modules = referenced_modules
328 .iter()
329 .flat_map(|(chunking_type, modules)| match chunking_type {
330 ChunkingType::Traced => None,
331 _ => Some(modules.iter()),
332 })
333 .flatten()
334 .map(|module| async move {
335 if let Some(client_reference_module) =
336 ResolvedVc::try_downcast_type::<EcmascriptClientReferenceModule>(*module)
337 {
338 return Ok(VisitClientReferenceNode {
339 state: node.state,
340 ty: VisitClientReferenceNodeType::ClientReference(
341 ClientReference {
342 server_component: node.state.server_component(),
343 ty: ClientReferenceType::EcmascriptClientReference(
344 client_reference_module,
345 ),
346 },
347 client_reference_module.ident().to_string().await?,
348 ),
349 });
350 }
351
352 if let Some(client_reference_module) =
353 ResolvedVc::try_downcast_type::<CssClientReferenceModule>(*module)
354 {
355 return Ok(VisitClientReferenceNode {
356 state: node.state,
357 ty: VisitClientReferenceNodeType::ClientReference(
358 ClientReference {
359 server_component: node.state.server_component(),
360 ty: ClientReferenceType::CssClientReference(
361 client_reference_module.await?.client_module,
362 ),
363 },
364 client_reference_module.ident().to_string().await?,
365 ),
366 });
367 }
368
369 if let Some(server_component_asset) =
370 ResolvedVc::try_downcast_type::<NextServerComponentModule>(*module)
371 {
372 return Ok(VisitClientReferenceNode {
373 state: VisitClientReferenceNodeState::InServerComponent {
374 server_component: server_component_asset,
375 },
376 ty: VisitClientReferenceNodeType::ServerComponentEntry(
377 server_component_asset,
378 server_component_asset.ident().to_string().await?,
379 ),
380 });
381 }
382
383 if let Some(server_util_module) =
384 ResolvedVc::try_downcast_type::<NextServerUtilityModule>(*module)
385 {
386 return Ok(VisitClientReferenceNode {
387 state: VisitClientReferenceNodeState::InServerUtil,
388 ty: VisitClientReferenceNodeType::ServerUtilEntry(
389 server_util_module,
390 module.ident().to_string().await?,
391 ),
392 });
393 }
394
395 Ok(VisitClientReferenceNode {
396 state: node.state,
397 ty: VisitClientReferenceNodeType::Internal(
398 *module,
399 module.ident().to_string().await?,
400 ),
401 })
402 });
403
404 let assets = referenced_modules.try_join().await?;
405
406 Ok(assets)
407 }
408 }
409
410 fn span(&mut self, node: &VisitClientReferenceNode) -> tracing::Span {
411 match &node.ty {
412 VisitClientReferenceNodeType::ClientReference(_, name) => {
413 tracing::info_span!("client reference", name = name.to_string())
414 }
415 VisitClientReferenceNodeType::Internal(_, name) => {
416 tracing::info_span!("module", name = name.to_string())
417 }
418 VisitClientReferenceNodeType::ServerUtilEntry(_, name) => {
419 tracing::info_span!("server util", name = name.to_string())
420 }
421 VisitClientReferenceNodeType::ServerComponentEntry(_, name) => {
422 tracing::info_span!("layout segment", name = name.to_string())
423 }
424 }
425 }
426}