1mod groups;
2mod membership;
3mod output_file;
4
5use std::io::Write;
6
7use anyhow::Result;
8use bincode::{Decode, Encode};
9use byteorder::{BE, WriteBytesExt};
10use next_core::app_structure::FileSystemPathVec;
11use output_file::{AnalyzedChunkPart, analyze_output_file, analyze_traced_file};
12use rustc_hash::{FxHashMap, FxHashSet};
13use serde::Serialize;
14use turbo_rcstr::RcStr;
15use turbo_tasks::{
16 FxIndexSet, JoinIterExt, NonLocalValue, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt,
17 ValueToString, ValueToStringRef, Vc,
18};
19use turbo_tasks_fs::{
20 File, FileContent, FileSystemPath,
21 rope::{Rope, RopeBuilder},
22};
23use turbo_tasks_hash::hash_xxh3_hash64;
24use turbopack_core::{
25 asset::{Asset, AssetContent},
26 chunk::{ChunkingType, TracedMode},
27 module::Module,
28 module_graph::{GraphTraversalAction, ModuleGraph},
29 output::{OutputAsset, OutputAssets, OutputAssetsReference},
30 reference::all_assets_from_entries,
31};
32use turbopack_ecmascript::references::service_worker::ServiceWorkerEntryModule;
33
34use crate::route::AnalyzeChunkGroups;
35
36const ANALYZE_SCHEMA_VERSION: u32 = 1;
37
38#[turbo_tasks::value]
39#[derive(Clone, Copy, Serialize)]
40pub struct BundleTotals {
41 pub size: u64,
42 pub compressed_size: u64,
43}
44
45#[turbo_tasks::value]
46#[derive(Clone, Copy, Serialize)]
47pub struct RouteBundleSummary {
48 pub size: u64,
49 pub compressed_size: u64,
50 pub client: BundleTotals,
51}
52
53#[derive(Serialize)]
54pub struct AnalyzeSource {
55 pub parent_source_index: Option<u32>,
56 pub path: RcStr,
60}
61
62#[derive(Clone, Debug, PartialEq, Eq, NonLocalValue, Encode, Decode, Serialize)]
63pub struct AnalyzeModule {
64 pub ident: RcStr,
65 pub path: RcStr,
66}
67
68#[derive(Serialize)]
69pub struct AnalyzeChunkPart {
70 pub source_index: u32,
71 pub output_file_index: u32,
72 pub size: u32,
73 pub compressed_size: u32,
74}
75
76#[derive(Serialize)]
77pub struct AnalyzeOutputFile {
78 pub filename: RcStr,
79}
80
81#[turbo_tasks::value(shared)]
84#[derive(Clone, Debug, Serialize)]
85pub struct AnalyzeRouteEntry {
86 pub route_entry_id: RcStr,
87 pub module_ident: RcStr,
88 pub module_path: RcStr,
89 pub role: RcStr,
90 #[serde(skip_serializing_if = "Option::is_none")]
91 pub entry_kind: Option<RcStr>,
92 #[serde(skip_serializing_if = "Vec::is_empty")]
93 pub client_references: Vec<AnalyzeClientReferenceEntry>,
94}
95
96#[derive(Clone, Debug, PartialEq, Eq, NonLocalValue, Encode, Decode, Serialize)]
97pub struct AnalyzeClientReferenceEntry {
98 pub module_ident: RcStr,
99 pub module_path: RcStr,
100 pub reference_kind: RcStr,
101}
102
103#[turbo_tasks::value(transparent)]
104pub struct AnalyzeRouteEntries(Vec<AnalyzeRouteEntry>);
105
106pub type AnalyzeGraphModule = ResolvedVc<Box<dyn Module>>;
107pub type AnalyzeSyncDependents = FxHashMap<AnalyzeGraphModule, Vec<AnalyzeGraphModule>>;
108pub type AnalyzeWorkerEntry = (AnalyzeGraphModule, ResolvedVc<ServiceWorkerEntryModule>);
109
110#[turbo_tasks::value(shared)]
113pub struct AnalyzeModuleIndex {
114 pub modules: Vec<AnalyzeModule>,
115 pub by_ident: FxHashMap<RcStr, u32>,
116 pub module_index_hash: RcStr,
118 pub sync_dependents: AnalyzeSyncDependents,
120 pub worker_entries: Vec<AnalyzeWorkerEntry>,
121}
122
123#[turbo_tasks::function]
124pub async fn analyze_module_index(module_graph: Vc<ModuleGraph>) -> Result<Vc<AnalyzeModuleIndex>> {
125 let mut all_modules = FxIndexSet::default();
126 let mut sync_dependents: AnalyzeSyncDependents = FxHashMap::default();
127 let mut registrations = FxIndexSet::default();
128 let graph = module_graph.await?;
129 graph.traverse_edges_dfs(
130 graph.all_entry_modules(),
131 &mut (),
132 |parent, node, _| {
133 all_modules.insert(node);
134 if let Some((importer, reference)) = parent {
135 if !matches!(
136 reference.chunking_type,
137 ChunkingType::Async | ChunkingType::Traced { .. }
138 ) {
139 sync_dependents.entry(node).or_default().push(importer);
140 }
141 if let Some(marker) =
142 ResolvedVc::try_downcast_type::<ServiceWorkerEntryModule>(node)
143 {
144 registrations.insert((importer, marker));
145 }
146 }
147 Ok(GraphTraversalAction::Continue)
148 },
149 |_, _, _| Ok(()),
150 true,
151 false,
152 )?;
153 let mut modules = Vec::with_capacity(all_modules.len());
154 let mut by_ident = FxHashMap::default();
155 let idents_and_paths = all_modules
156 .iter()
157 .copied()
158 .map(async |module| {
159 let ident = module.ident();
160 let ident_str = ident.to_string().owned().await?;
161 let path = ident.await?.path.to_string_ref().await?;
162 anyhow::Ok((ident_str, path))
163 })
164 .join()
165 .await;
166 for pair in idents_and_paths {
167 let (ident, path) = pair?;
168 if by_ident.contains_key(&ident) {
169 continue;
170 }
171 by_ident.insert(ident.clone(), modules.len() as u32);
172 modules.push(AnalyzeModule { ident, path });
173 }
174 let module_index_hash = format!(
175 "{:016x}",
176 hash_xxh3_hash64(
177 modules
178 .iter()
179 .map(|module| (module.ident.as_str(), module.path.as_str()))
180 .collect::<Vec<_>>()
181 )
182 )
183 .into();
184 Ok(AnalyzeModuleIndex {
185 modules,
186 by_ident,
187 module_index_hash,
188 sync_dependents,
189 worker_entries: registrations.into_iter().collect(),
190 }
191 .cell())
192}
193
194pub fn analyze_route_entry_id(
195 route: &str,
196 role: &str,
197 sub_name: &str,
198 module_ident: &str,
199) -> RcStr {
200 format!("{route}|{role}|{sub_name}|{module_ident}").into()
201}
202
203#[derive(Serialize)]
204struct EdgesDataReference {
205 pub offset: u32,
206 pub length: u32,
207}
208
209#[derive(Serialize)]
210struct AnalyzeDataHeader {
211 pub schema_version: u32,
213 pub module_index_hash: RcStr,
214 pub sources: Vec<AnalyzeSource>,
215 pub chunk_parts: Vec<AnalyzeChunkPart>,
216 pub output_files: Vec<AnalyzeOutputFile>,
217 pub output_file_modules: EdgesDataReference,
219 pub output_file_async_loaders: EdgesDataReference,
220 pub output_file_module_coverage: Vec<AnalyzeOutputFileCoverage>,
221 pub chunk_groups: Vec<AnalyzeChunkGroupData>,
222 pub route_entries: Vec<AnalyzeRouteEntry>,
224 pub output_file_chunk_parts: EdgesDataReference,
226 pub source_chunk_parts: EdgesDataReference,
228 pub source_children: EdgesDataReference,
230 pub source_roots: Vec<u32>,
232}
233
234#[derive(Serialize)]
235struct ModulesDataHeader {
236 pub schema_version: u32,
238 pub module_index_hash: RcStr,
239 pub modules: Vec<AnalyzeModule>,
240 pub module_dependents: EdgesDataReference,
242 pub async_module_dependents: EdgesDataReference,
244 pub traced_module_dependents: EdgesDataReference,
246 pub module_dependencies: EdgesDataReference,
248 pub async_module_dependencies: EdgesDataReference,
250 pub traced_module_dependencies: EdgesDataReference,
252}
253
254#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, NonLocalValue, Encode, Decode, Serialize)]
255#[serde(rename_all = "snake_case")]
256enum AnalyzeOutputFileCoverage {
257 Exact,
258 Unsupported,
259 #[default]
260 NotAChunk,
261}
262
263#[derive(Serialize)]
264struct AnalyzeChunkGroupData {
265 id: u32,
266 kind: RcStr,
267 #[serde(skip_serializing_if = "Option::is_none")]
270 trigger_module_index: Option<u32>,
271 output_file_indices: Vec<u32>,
274}
275
276struct ChunkLoadCandidate {
277 source: u32,
278 target: ResolvedVc<Box<dyn OutputAsset>>,
279 trigger_module_index: u32,
281}
282
283struct AnalyzeOutputFileBuilder {
284 output_file: AnalyzeOutputFile,
285 chunk_part_indices: Vec<u32>,
286 module_indices: Vec<u32>,
287 async_loader_indices: Vec<u32>,
288 module_coverage: AnalyzeOutputFileCoverage,
289}
290
291struct AnalyzeSourceBuilder {
292 source: AnalyzeSource,
293 child_source_indices: Vec<u32>,
294 chunk_part_indices: Vec<u32>,
295}
296
297struct AnalyzeModuleBuilder {
298 module: AnalyzeModule,
299 dependencies: FxIndexSet<u32>,
300 async_dependencies: FxIndexSet<u32>,
301 traced_dependencies: FxIndexSet<u32>,
302 dependents: FxIndexSet<u32>,
303 async_dependents: FxIndexSet<u32>,
304 traced_dependents: FxIndexSet<u32>,
305}
306
307struct AnalyzeDataBuilder {
308 sources: Vec<AnalyzeSourceBuilder>,
309 source_index_map: FxHashMap<RcStr, u32>,
310 chunk_parts: Vec<AnalyzeChunkPart>,
311 output_files: Vec<AnalyzeOutputFileBuilder>,
312 output_file_index_map: FxHashMap<RcStr, u32>,
313 route_entries: Vec<AnalyzeRouteEntry>,
314 module_index_hash: RcStr,
315 chunk_groups: Vec<AnalyzeChunkGroupData>,
316}
317
318struct ModulesDataBuilder {
319 modules: Vec<AnalyzeModuleBuilder>,
320 module_index_map: FxHashMap<RcStr, u32>,
321 module_index_hash: RcStr,
322}
323
324pub struct EdgesData {
326 pub offsets: Vec<u32>,
327 pub data: Vec<u32>,
328}
329
330impl EdgesData {
331 fn from_iterator<'a>(iterable: impl IntoIterator<Item = &'a Vec<u32>> + Clone) -> Self {
332 let mut current_offset = 0;
333 let sum: usize = iterable.clone().into_iter().map(|v| v.len()).sum();
334 let mut data = Vec::with_capacity(sum);
335 let offsets = iterable
336 .into_iter()
337 .map(|edges| {
338 current_offset += edges.len() as u32;
339 data.extend(edges);
340 current_offset
341 })
342 .collect();
343 Self { offsets, data }
344 }
345
346 fn write(&self, writer: &mut impl Write) -> Result<()> {
347 writer.write_u32::<BE>(self.offsets.len() as u32)?;
348 for &offset in &self.offsets {
349 writer.write_u32::<BE>(offset)?;
350 }
351 for &data in &self.data {
352 writer.write_u32::<BE>(data)?;
353 }
354 Ok(())
355 }
356}
357
358struct EdgesDataSectionBuilder {
359 data: Vec<u8>,
360}
361
362impl EdgesDataSectionBuilder {
363 fn new() -> Self {
364 Self { data: vec![] }
365 }
366
367 fn add_edges(&mut self, edges: &EdgesData) -> EdgesDataReference {
368 let offset = self.data.len().try_into().unwrap();
369 edges.write(&mut self.data).unwrap();
370 let length = (self.data.len() - offset as usize).try_into().unwrap();
371 EdgesDataReference { offset, length }
372 }
373}
374
375impl AnalyzeDataBuilder {
376 fn new(route_entries: Vec<AnalyzeRouteEntry>, module_index_hash: RcStr) -> Self {
377 Self {
378 module_index_hash,
379 sources: vec![],
380 source_index_map: FxHashMap::default(),
381 chunk_parts: vec![],
382 output_files: vec![],
383 output_file_index_map: FxHashMap::default(),
384 route_entries,
385 chunk_groups: vec![],
386 }
387 }
388
389 fn ensure_source(&mut self, path: &str) -> (&mut AnalyzeSourceBuilder, u32) {
390 if let Some(&index) = self.source_index_map.get(path) {
391 return (&mut self.sources[index as usize], index);
392 }
393 let index = self.sources.len() as u32;
394 let path = RcStr::from(path);
395 self.source_index_map.insert(path.clone(), index);
396 self.sources.push(AnalyzeSourceBuilder {
397 source: AnalyzeSource {
398 parent_source_index: None,
399 path,
400 },
401 child_source_indices: vec![],
402 chunk_part_indices: vec![],
403 });
404 (&mut self.sources[index as usize], index)
405 }
406
407 fn add_chunk_part(&mut self, chunk_part: AnalyzeChunkPart) -> u32 {
408 let i = self.chunk_parts.len() as u32;
409 self.chunk_parts.push(chunk_part);
410 i
411 }
412
413 fn add_output_file(&mut self, output_file: AnalyzeOutputFile) -> u32 {
414 if let Some(&index) = self.output_file_index_map.get(&output_file.filename) {
415 return index;
416 }
417 let i = self.output_files.len() as u32;
418 self.output_file_index_map
419 .insert(output_file.filename.clone(), i);
420 self.output_files.push(AnalyzeOutputFileBuilder {
421 output_file,
422 chunk_part_indices: vec![],
423 module_indices: vec![],
424 async_loader_indices: vec![],
425 module_coverage: AnalyzeOutputFileCoverage::NotAChunk,
426 });
427 i
428 }
429
430 fn add_chunk_part_to_output_file(&mut self, output_file_index: u32, chunk_part_index: u32) {
431 self.output_files[output_file_index as usize]
432 .chunk_part_indices
433 .push(chunk_part_index);
434 }
435
436 fn add_chunk_part_to_source(&mut self, source_index: u32, chunk_part_index: u32) {
437 self.sources[source_index as usize]
438 .chunk_part_indices
439 .push(chunk_part_index);
440 }
441
442 fn finish_sources(&mut self) {
445 let mut i: u32 = 0;
446 while i < self.sources.len().try_into().unwrap() {
447 let source = &self.sources[i as usize];
448 let path = source.source.path.as_str();
449 if !path.is_empty() {
450 let (parent_path, path) = if let Some(pos) = path.trim_end_matches('/').rfind('/') {
451 (&path[..pos + 1], &path[pos + 1..])
452 } else {
453 ("", path)
454 };
455 let parent_path = parent_path.to_string();
456 let path = path.into();
457 let (parent_source, parent_index) = self.ensure_source(&parent_path);
458 parent_source.child_source_indices.push(i);
459 self.sources[i as usize].source.parent_source_index = Some(parent_index);
460 self.sources[i as usize].source.path = path;
461 }
462 i += 1;
463 }
464 }
465
466 fn build(self) -> Rope {
467 let source_roots = self
468 .sources
469 .iter()
470 .enumerate()
471 .filter_map(|(i, s)| {
472 if s.source.parent_source_index.is_none() {
473 Some(i as u32)
474 } else {
475 None
476 }
477 })
478 .collect();
479
480 let source_children =
481 EdgesData::from_iterator(self.sources.iter().map(|s| &s.child_source_indices));
482
483 let source_chunk_parts =
484 EdgesData::from_iterator(self.sources.iter().map(|s| &s.chunk_part_indices));
485
486 let output_file_chunk_parts =
487 EdgesData::from_iterator(self.output_files.iter().map(|of| &of.chunk_part_indices));
488 let output_file_modules =
489 EdgesData::from_iterator(self.output_files.iter().map(|of| &of.module_indices));
490
491 let output_file_async_loaders =
492 EdgesData::from_iterator(self.output_files.iter().map(|of| &of.async_loader_indices));
493 let mut binary_section = EdgesDataSectionBuilder::new();
494
495 let header = AnalyzeDataHeader {
496 schema_version: ANALYZE_SCHEMA_VERSION,
497 module_index_hash: self.module_index_hash,
498 sources: self.sources.into_iter().map(|s| s.source).collect(),
499 chunk_parts: self.chunk_parts,
500 output_file_module_coverage: self
501 .output_files
502 .iter()
503 .map(|of| of.module_coverage)
504 .collect(),
505 output_file_modules: binary_section.add_edges(&output_file_modules),
506 output_file_async_loaders: binary_section.add_edges(&output_file_async_loaders),
507 output_files: self
508 .output_files
509 .into_iter()
510 .map(|of| of.output_file)
511 .collect(),
512 chunk_groups: self.chunk_groups,
513 route_entries: self.route_entries,
514 output_file_chunk_parts: binary_section.add_edges(&output_file_chunk_parts),
515 source_chunk_parts: binary_section.add_edges(&source_chunk_parts),
516 source_children: binary_section.add_edges(&source_children),
517 source_roots,
518 };
519
520 let header_json = serde_json::to_vec(&header).unwrap();
521
522 let mut rope = RopeBuilder::default();
523 rope.push_bytes(&(header_json.len() as u32).to_be_bytes());
524 rope.reserve_bytes(header_json.len() + binary_section.data.len());
525 rope.push_bytes(&header_json);
526 rope.push_bytes(&binary_section.data);
527 rope.build()
528 }
529}
530
531impl ModulesDataBuilder {
532 fn new(module_index_hash: RcStr) -> Self {
533 Self {
534 module_index_hash,
535 modules: vec![],
536 module_index_map: FxHashMap::default(),
537 }
538 }
539
540 fn get_module(&mut self, ident: &str) -> (&mut AnalyzeModuleBuilder, u32) {
541 if let Some(&index) = self.module_index_map.get(ident) {
542 return (&mut self.modules[index as usize], index);
543 }
544 panic!("Module with ident `{}` not found", ident);
545 }
546
547 fn ensure_module(&mut self, ident: &str, path: &str) -> (&mut AnalyzeModuleBuilder, u32) {
548 if let Some(&index) = self.module_index_map.get(ident) {
549 return (&mut self.modules[index as usize], index);
550 }
551 let index = self.modules.len() as u32;
552 let ident = RcStr::from(ident);
553 let path = RcStr::from(path);
554 self.module_index_map.insert(ident.clone(), index);
555 self.modules.push(AnalyzeModuleBuilder {
556 module: AnalyzeModule { ident, path },
557 dependencies: FxIndexSet::default(),
558 async_dependencies: FxIndexSet::default(),
559 traced_dependencies: FxIndexSet::default(),
560 dependents: FxIndexSet::default(),
561 async_dependents: FxIndexSet::default(),
562 traced_dependents: FxIndexSet::default(),
563 });
564 (&mut self.modules[index as usize], index)
565 }
566
567 fn build(self) -> Rope {
568 let module_dependencies_vecs: Vec<Vec<u32>> = self
569 .modules
570 .iter()
571 .map(|s| s.dependencies.iter().copied().collect())
572 .collect();
573 let async_module_dependencies_vecs: Vec<Vec<u32>> = self
574 .modules
575 .iter()
576 .map(|s| s.async_dependencies.iter().copied().collect())
577 .collect();
578 let traced_module_dependencies_vecs: Vec<Vec<u32>> = self
579 .modules
580 .iter()
581 .map(|s| s.traced_dependencies.iter().copied().collect())
582 .collect();
583 let module_dependents_vecs: Vec<Vec<u32>> = self
584 .modules
585 .iter()
586 .map(|s| s.dependents.iter().copied().collect())
587 .collect();
588 let async_module_dependents_vecs: Vec<Vec<u32>> = self
589 .modules
590 .iter()
591 .map(|s| s.async_dependents.iter().copied().collect())
592 .collect();
593 let traced_module_dependents_vecs: Vec<Vec<u32>> = self
594 .modules
595 .iter()
596 .map(|s| s.traced_dependents.iter().copied().collect())
597 .collect();
598
599 let module_dependencies = EdgesData::from_iterator(&module_dependencies_vecs);
600 let async_module_dependencies = EdgesData::from_iterator(&async_module_dependencies_vecs);
601 let traced_module_dependencies = EdgesData::from_iterator(&traced_module_dependencies_vecs);
602 let module_dependents = EdgesData::from_iterator(&module_dependents_vecs);
603 let async_module_dependents = EdgesData::from_iterator(&async_module_dependents_vecs);
604 let traced_module_dependents = EdgesData::from_iterator(&traced_module_dependents_vecs);
605
606 let mut binary_section = EdgesDataSectionBuilder::new();
607
608 let header = ModulesDataHeader {
609 schema_version: ANALYZE_SCHEMA_VERSION,
610 module_index_hash: self.module_index_hash,
611 modules: self.modules.into_iter().map(|s| s.module).collect(),
612 module_dependents: binary_section.add_edges(&module_dependents),
613 async_module_dependents: binary_section.add_edges(&async_module_dependents),
614 traced_module_dependents: binary_section.add_edges(&traced_module_dependents),
615 module_dependencies: binary_section.add_edges(&module_dependencies),
616 async_module_dependencies: binary_section.add_edges(&async_module_dependencies),
617 traced_module_dependencies: binary_section.add_edges(&traced_module_dependencies),
618 };
619
620 let header_json = serde_json::to_vec(&header).unwrap();
621
622 let mut rope = RopeBuilder::default();
623 rope.push_bytes(&(header_json.len() as u32).to_be_bytes());
624 rope.reserve_bytes(header_json.len() + binary_section.data.len());
625 rope.push_bytes(&header_json);
626 rope.push_bytes(&binary_section.data);
627 rope.build()
628 }
629}
630
631#[turbo_tasks::function]
634pub async fn combine_output_assets(
635 primary: Vc<OutputAssets>,
636 extra: Vc<OutputAssets>,
637) -> Result<Vc<OutputAssets>> {
638 let mut combined: Vec<ResolvedVc<Box<dyn OutputAsset>>> =
639 primary.await?.iter().copied().collect();
640 combined.extend(extra.await?.iter().copied());
641 Ok(Vc::cell(combined))
642}
643
644#[turbo_tasks::function]
647pub async fn combine_traced_files(
648 primary: Vc<FileSystemPathVec>,
649 extra: Vc<FileSystemPathVec>,
650) -> Result<Vc<FileSystemPathVec>> {
651 let mut combined: Vec<FileSystemPath> = primary.await?.iter().cloned().collect();
652 combined.extend(extra.await?.iter().cloned());
653 Ok(Vc::cell(combined))
654}
655
656#[turbo_tasks::value]
657pub struct AnalyzedRoute {
658 pub content: ResolvedVc<FileContent>,
659 pub summary: RouteBundleSummary,
660}
661
662#[turbo_tasks::value_impl]
663impl AnalyzedRoute {
664 #[turbo_tasks::function]
665 fn file_content(&self) -> Vc<FileContent> {
666 *self.content
667 }
668}
669
670#[turbo_tasks::function]
671pub async fn analyze_output_assets(
672 output_assets: Vc<OutputAssets>,
673 traced_files: Vc<FileSystemPathVec>,
674 route_entries: Vc<AnalyzeRouteEntries>,
675 chunk_groups: Vc<AnalyzeChunkGroups>,
676 module_graph: Vc<ModuleGraph>,
677) -> Result<Vc<AnalyzedRoute>> {
678 let output_assets = all_assets_from_entries(output_assets);
679 let mut summary = RouteBundleSummary {
680 size: 0,
681 compressed_size: 0,
682 client: BundleTotals {
683 size: 0,
684 compressed_size: 0,
685 },
686 };
687 let route_entries = route_entries.await?.iter().cloned().collect();
688 let module_index_vc = analyze_module_index(module_graph);
689 let module_index = module_index_vc.await?;
690
691 let mut builder =
692 AnalyzeDataBuilder::new(route_entries, module_index.module_index_hash.clone());
693 let mut asset_indices: FxHashMap<ResolvedVc<Box<dyn OutputAsset>>, Vec<u32>> =
694 FxHashMap::default();
695 let mut candidates = Vec::new();
696 let mut browser_chunks = FxHashSet::default();
697
698 let output_assets = output_assets.await?;
699 let traced_files = traced_files.await?;
700 let files = output_assets
703 .iter()
704 .map(|&asset| analyze_output_file(*asset, module_index_vc))
705 .chain(
706 traced_files
707 .iter()
708 .map(|path| analyze_traced_file(path.clone())),
709 )
710 .try_join()
711 .await?;
712
713 for file in files.iter().filter_map(|file| (**file).as_ref()) {
714 let is_client = file.filename.starts_with("[client-fs]/");
715 let output_file_index = builder.add_output_file(AnalyzeOutputFile {
716 filename: file.filename.clone(),
717 });
718 if let Some(emitted) = &file.emitted {
719 let membership = &emitted.membership;
720 asset_indices
721 .entry(emitted.asset)
722 .or_default()
723 .push(output_file_index);
724 if emitted.is_browser_chunk {
725 browser_chunks.insert(output_file_index);
726 }
727 candidates.extend(
728 membership
729 .load_targets
730 .iter()
731 .map(|load| ChunkLoadCandidate {
732 source: output_file_index,
733 target: load.target,
734 trigger_module_index: load.trigger_module_index,
735 }),
736 );
737 let output = &mut builder.output_files[output_file_index as usize];
738 output.module_indices.extend(&membership.module_indices);
739 output.module_indices.sort_unstable();
740 output.module_indices.dedup();
741 output
742 .async_loader_indices
743 .extend(&membership.async_loader_indices);
744 output.async_loader_indices.sort_unstable();
745 output.async_loader_indices.dedup();
746 output.module_coverage = membership.coverage;
747 }
748 for &AnalyzedChunkPart {
749 ref source,
750 size,
751 compressed_size,
752 } in &file.chunk_parts
753 {
754 let source_index = builder.ensure_source(source).1;
755 summary.size += u64::from(size);
756 summary.compressed_size += u64::from(compressed_size);
757 if is_client {
758 summary.client.size += u64::from(size);
759 summary.client.compressed_size += u64::from(compressed_size);
760 }
761 let chunk_part_index = builder.add_chunk_part(AnalyzeChunkPart {
762 source_index,
763 output_file_index,
764 size,
765 compressed_size,
766 });
767 builder.add_chunk_part_to_output_file(output_file_index, chunk_part_index);
768 builder.add_chunk_part_to_source(source_index, chunk_part_index);
769 }
770 }
771
772 groups::collect_groups(
773 &mut builder,
774 candidates,
775 &asset_indices,
776 chunk_groups,
777 &module_index,
778 &browser_chunks,
779 )
780 .await?;
781
782 builder.finish_sources();
783
784 let rope = builder.build();
785 Ok(AnalyzedRoute {
786 content: FileContent::Content(File::from(rope))
787 .cell()
788 .to_resolved()
789 .await?,
790 summary,
791 }
792 .cell())
793}
794
795#[turbo_tasks::function]
796pub async fn analyze_module_graphs(module_graph: Vc<ModuleGraph>) -> Result<Vc<FileContent>> {
797 let module_index = analyze_module_index(module_graph).await?;
798 let mut builder = ModulesDataBuilder::new(module_index.module_index_hash.clone());
799
800 let mut all_edges = FxIndexSet::default();
801 let mut all_async_edges = FxIndexSet::default();
802 let mut all_traced_edges = FxIndexSet::default();
803 let mut traced_modules = FxHashSet::default();
804
805 let module_graph = module_graph.await?;
806 module_graph.traverse_edges_dfs(
807 module_graph.all_entry_modules(),
808 &mut (),
809 |parent, node, _| {
810 let Some((parent_node, reference)) = parent else {
811 return Ok(GraphTraversalAction::Continue);
812 };
813
814 if matches!(
818 reference.chunking_type,
819 ChunkingType::Traced {
820 mode: TracedMode::Entry
821 }
822 ) || traced_modules.contains(&parent_node)
823 {
824 traced_modules.insert(node);
825 all_traced_edges.insert((parent_node, node));
826 return Ok(GraphTraversalAction::Continue);
827 };
828
829 match reference.chunking_type {
830 ChunkingType::Async => {
831 all_async_edges.insert((parent_node, node));
832 }
833 _ => {
834 all_edges.insert((parent_node, node));
835 }
836 }
837 Ok(GraphTraversalAction::Continue)
838 },
839 |_, _, _| Ok(()),
840 true,
841 false,
842 )?;
843
844 type ModulePair = (ResolvedVc<Box<dyn Module>>, ResolvedVc<Box<dyn Module>>);
845 async fn mapper((from, to): ModulePair) -> Result<Option<(RcStr, RcStr)>> {
846 if from == to {
847 return Ok(None);
848 }
849 let from_ident = from.ident().to_string().owned().await?;
850 let to_ident = to.ident().to_string().owned().await?;
851 Ok(Some((from_ident, to_ident)))
852 }
853
854 for module in &module_index.modules {
855 builder.ensure_module(&module.ident, &module.path);
856 }
857
858 let all_edges = all_edges
859 .iter()
860 .copied()
861 .map(mapper)
862 .try_flat_join()
863 .await?;
864 let all_async_edges = all_async_edges
865 .iter()
866 .copied()
867 .map(mapper)
868 .try_flat_join()
869 .await?;
870 let all_traced_edges = all_traced_edges
871 .iter()
872 .copied()
873 .map(mapper)
874 .try_flat_join()
875 .await?;
876 for (from_ident, to_ident) in all_edges {
877 let from_index = builder.get_module(&from_ident).1;
878 let to_index = builder.get_module(&to_ident).1;
879 if from_index == to_index {
880 continue;
881 }
882 builder.modules[from_index as usize]
883 .dependencies
884 .insert(to_index);
885 builder.modules[to_index as usize]
886 .dependents
887 .insert(from_index);
888 }
889 for (from_ident, to_ident) in all_async_edges {
890 let from_index = builder.get_module(&from_ident).1;
891 let to_index = builder.get_module(&to_ident).1;
892 if from_index == to_index {
893 continue;
894 }
895 builder.modules[from_index as usize]
896 .async_dependencies
897 .insert(to_index);
898 builder.modules[to_index as usize]
899 .async_dependents
900 .insert(from_index);
901 }
902 for (from_ident, to_ident) in all_traced_edges {
903 let from_index = builder.get_module(&from_ident).1;
904 let to_index = builder.get_module(&to_ident).1;
905 if from_index == to_index {
906 continue;
907 }
908 builder.modules[from_index as usize]
909 .traced_dependencies
910 .insert(to_index);
911 builder.modules[to_index as usize]
912 .traced_dependents
913 .insert(from_index);
914 }
915
916 let rope = builder.build();
917 Ok(FileContent::Content(File::from(rope)).cell())
918}
919
920#[turbo_tasks::value]
921pub struct AnalyzeDataOutputAsset {
922 pub path: FileSystemPath,
923 pub output_assets: ResolvedVc<OutputAssets>,
924 pub traced_files: ResolvedVc<FileSystemPathVec>,
925 pub route_entries: ResolvedVc<AnalyzeRouteEntries>,
926 pub chunk_groups: ResolvedVc<AnalyzeChunkGroups>,
927 pub module_graph: ResolvedVc<ModuleGraph>,
928}
929
930#[turbo_tasks::value_impl]
931impl AnalyzeDataOutputAsset {
932 #[turbo_tasks::function]
933 pub async fn new(
934 path: FileSystemPath,
935 output_assets: ResolvedVc<OutputAssets>,
936 traced_files: ResolvedVc<FileSystemPathVec>,
937 route_entries: ResolvedVc<AnalyzeRouteEntries>,
938 chunk_groups: ResolvedVc<AnalyzeChunkGroups>,
939 module_graph: ResolvedVc<ModuleGraph>,
940 ) -> Result<Vc<Self>> {
941 Ok(Self {
942 path,
943 output_assets,
944 traced_files,
945 route_entries,
946 chunk_groups,
947 module_graph,
948 }
949 .cell())
950 }
951}
952
953#[turbo_tasks::value_impl]
954impl Asset for AnalyzeDataOutputAsset {
955 #[turbo_tasks::function]
956 fn content(&self) -> Vc<AssetContent> {
957 let file_content = analyze_output_assets(
958 *self.output_assets,
959 *self.traced_files,
960 *self.route_entries,
961 *self.chunk_groups,
962 *self.module_graph,
963 )
964 .file_content();
965 AssetContent::file(file_content)
966 }
967}
968
969#[turbo_tasks::value_impl]
970impl OutputAssetsReference for AnalyzeDataOutputAsset {}
971
972#[turbo_tasks::value_impl]
973impl OutputAsset for AnalyzeDataOutputAsset {
974 #[turbo_tasks::function]
975 fn path(&self) -> Vc<FileSystemPath> {
976 self.path.clone().cell()
977 }
978}
979
980#[turbo_tasks::value]
981pub struct ModulesDataOutputAsset {
982 pub path: FileSystemPath,
983 pub module_graph: ResolvedVc<ModuleGraph>,
984}
985
986#[turbo_tasks::value_impl]
987impl ModulesDataOutputAsset {
988 #[turbo_tasks::function]
989 pub async fn new(
990 path: FileSystemPath,
991 module_graph: ResolvedVc<ModuleGraph>,
992 ) -> Result<Vc<Self>> {
993 Ok(Self { path, module_graph }.cell())
994 }
995}
996
997#[turbo_tasks::value_impl]
998impl Asset for ModulesDataOutputAsset {
999 #[turbo_tasks::function]
1000 fn content(&self) -> Vc<AssetContent> {
1001 let file_content = analyze_module_graphs(*self.module_graph);
1002 AssetContent::file(file_content)
1003 }
1004}
1005
1006#[turbo_tasks::value_impl]
1007impl OutputAssetsReference for ModulesDataOutputAsset {}
1008
1009#[turbo_tasks::value_impl]
1010impl OutputAsset for ModulesDataOutputAsset {
1011 #[turbo_tasks::function]
1012 fn path(&self) -> Vc<FileSystemPath> {
1013 self.path.clone().cell()
1014 }
1015}