1pub mod availability_info;
2pub mod available_modules;
3pub mod chunk_group;
4pub mod chunk_id_strategy;
5pub(crate) mod chunk_item_batch;
6pub mod chunking;
7pub(crate) mod chunking_context;
8pub(crate) mod data;
9pub(crate) mod evaluate;
10
11use std::{fmt::Display, hash::Hash};
12
13use anyhow::{Result, bail};
14use auto_hash_map::AutoSet;
15use bincode::{Decode, Encode};
16use serde::{Deserialize, Serialize};
17use turbo_rcstr::RcStr;
18use turbo_tasks::{
19 FxIndexSet, NonLocalValue, ReadRef, ResolvedVc, Upcast, ValueToString, Vc,
20 debug::ValueDebugFormat,
21};
22use turbo_tasks_hash::DeterministicHash;
23
24pub use crate::chunk::{
25 chunk_item_batch::{
26 ChunkItemBatchGroup, ChunkItemBatchWithAsyncModuleInfo,
27 ChunkItemOrBatchWithAsyncModuleInfo, batch_info,
28 },
29 chunking_context::{
30 AssetSuffix, ChunkGroupResult, ChunkGroupType, ChunkingConfig, ChunkingConfigs,
31 ChunkingContext, ChunkingContextExt, EntryChunkGroupResult, HmrChunkListSource, MangleType,
32 MinifyType, SourceMapSourceType, SourceMapsType, UnusedReferences, UrlBehavior,
33 WorkerConfigurationOptions,
34 },
35 data::{ChunkData, ChunkDataOption, ChunksData},
36 evaluate::{EvaluatableAsset, EvaluatableAssetExt, EvaluatableAssets},
37};
38use crate::{
39 asset::Asset,
40 chunk::{availability_info::AvailabilityInfo, available_modules::AvailableModulesSet},
41 emit_collect::CollectingModule,
42 ident::AssetIdent,
43 module::Module,
44 module_graph::{
45 ModuleGraph,
46 module_batch::{ChunkableModuleOrBatch, ModuleBatchGroup},
47 },
48 output::{OutputAssets, OutputAssetsReference},
49};
50
51#[turbo_tasks::task_input]
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DeterministicHash, Encode, Decode)]
53pub enum ContentHashing {
54 Direct {
58 length: u8,
61 },
62}
63
64#[turbo_tasks::value(shared)]
65#[derive(Debug, Default, Clone, Copy, Hash, Serialize, Deserialize)]
66#[serde(rename_all = "kebab-case")]
67pub enum CrossOrigin {
68 #[default]
69 None,
70 Anonymous,
71 UseCredentials,
72}
73
74impl CrossOrigin {
75 pub fn as_str(self) -> Option<&'static str> {
76 match self {
77 Self::None => None,
78 Self::Anonymous => Some("anonymous"),
79 Self::UseCredentials => Some("use-credentials"),
80 }
81 }
82}
83
84impl TryFrom<Option<&str>> for CrossOrigin {
85 type Error = anyhow::Error;
86
87 fn try_from(value: Option<&str>) -> Result<Self> {
88 match value {
89 None => Ok(Self::None),
90 Some("anonymous") => Ok(Self::Anonymous),
91 Some("use-credentials") => Ok(Self::UseCredentials),
92 Some(value) => bail!(
93 "invalid crossOrigin value `{value}`; supported values are `anonymous` and \
94 `use-credentials`"
95 ),
96 }
97 }
98}
99
100#[turbo_tasks::value(shared)]
101#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)]
102pub struct ChunkLoadRetry {
103 pub max_retry_attempts: u32,
105 pub base_delay_ms: u32,
107 pub max_jitter_ms: u32,
109}
110
111impl Default for ChunkLoadRetry {
112 fn default() -> Self {
113 Self {
117 max_retry_attempts: 1,
118 base_delay_ms: 200,
119 max_jitter_ms: 400,
120 }
121 }
122}
123
124#[turbo_tasks::value(shared, operation)]
126#[derive(Debug, Clone, Hash, Ord, PartialOrd, DeterministicHash, Serialize, ValueToString)]
127#[serde(untagged)]
128pub enum ModuleId {
129 Number(u64),
130 String(RcStr),
131}
132
133impl Display for ModuleId {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 match self {
136 ModuleId::Number(i) => write!(f, "{i}"),
137 ModuleId::String(s) => write!(f, "{s}"),
138 }
139 }
140}
141
142impl ModuleId {
143 pub fn parse(id: &str) -> Result<ModuleId> {
144 Ok(match id.parse::<u64>() {
145 Ok(i) => ModuleId::Number(i),
146 Err(_) => ModuleId::String(id.into()),
147 })
148 }
149}
150
151#[turbo_tasks::value(transparent, shared)]
153pub struct ModuleIds(Vec<ModuleId>);
154
155#[turbo_tasks::value_trait]
157pub trait ChunkableModule: Module {
158 #[turbo_tasks::function]
159 fn as_chunk_item(
160 self: Vc<Self>,
161 module_graph: Vc<ModuleGraph>,
162 chunking_context: Vc<Box<dyn ChunkingContext>>,
163 ) -> Vc<Box<dyn ChunkItem>>;
164}
165
166#[turbo_tasks::value_trait]
171pub trait MergeableModule: Module {
172 #[turbo_tasks::function]
175 fn is_mergeable(self: Vc<Self>) -> Vc<bool> {
176 Vc::cell(true)
177 }
178
179 #[turbo_tasks::function]
185 fn merge(
186 self: Vc<Self>,
187 modules: Vc<MergeableModulesExposed>,
188 entry_points: Vc<MergeableModules>,
189 ) -> Vc<Box<dyn ChunkableModule>>;
190}
191#[turbo_tasks::value(transparent)]
192pub struct MergeableModules(Vec<ResolvedVc<Box<dyn MergeableModule>>>);
193
194#[turbo_tasks::value_impl]
195impl MergeableModules {
196 #[turbo_tasks::function]
197 pub fn interned(modules: Vec<ResolvedVc<Box<dyn MergeableModule>>>) -> Vc<Self> {
198 Vc::cell(modules)
199 }
200}
201
202#[turbo_tasks::task_input]
204#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Encode, Decode)]
205pub enum MergeableModuleExposure {
206 None,
209 Internal,
212 External,
215}
216
217#[turbo_tasks::value(transparent)]
218pub struct MergeableModulesExposed(
219 Vec<(
220 ResolvedVc<Box<dyn MergeableModule>>,
221 MergeableModuleExposure,
222 )>,
223);
224
225#[turbo_tasks::value_impl]
226impl MergeableModulesExposed {
227 #[turbo_tasks::function]
228 pub fn interned(
229 modules: Vec<(
230 ResolvedVc<Box<dyn MergeableModule>>,
231 MergeableModuleExposure,
232 )>,
233 ) -> Vc<Self> {
234 Vc::cell(modules)
235 }
236}
237
238#[turbo_tasks::value(transparent)]
239pub struct Chunks(Vec<ResolvedVc<Box<dyn Chunk>>>);
240
241#[turbo_tasks::value_impl]
242impl Chunks {
243 #[turbo_tasks::function]
244 pub fn empty() -> Vc<Self> {
245 Vc::cell(vec![])
246 }
247}
248
249#[turbo_tasks::value_trait]
256pub trait Chunk: OutputAssetsReference {
257 #[turbo_tasks::function]
258 fn ident(self: Vc<Self>) -> Vc<AssetIdent>;
259
260 #[turbo_tasks::function]
261 fn chunking_context(self: Vc<Self>) -> Vc<Box<dyn ChunkingContext>>;
262
263 #[turbo_tasks::function]
264 fn chunk_items(self: Vc<Self>) -> Vc<ChunkItems> {
265 ChunkItems(vec![]).cell()
266 }
267}
268
269#[turbo_tasks::value(shared)]
272#[derive(Default)]
273pub struct OutputChunkRuntimeInfo {
274 pub included_ids: Option<ResolvedVc<ModuleIds>>,
275 pub excluded_ids: Option<ResolvedVc<ModuleIds>>,
276 pub module_chunks: Option<ResolvedVc<OutputAssets>>,
280 pub placeholder_for_future_extensions: (),
281}
282
283#[turbo_tasks::value_impl]
284impl OutputChunkRuntimeInfo {
285 #[turbo_tasks::function]
286 pub fn empty() -> Vc<Self> {
287 Self::default().cell()
288 }
289}
290
291#[turbo_tasks::value_trait]
292pub trait OutputChunk: Asset {
293 #[turbo_tasks::function]
294 fn runtime_info(self: Vc<Self>) -> Vc<OutputChunkRuntimeInfo>;
295}
296
297#[derive(
299 Debug,
300 Clone,
301 Copy,
302 Hash,
303 Serialize,
304 Deserialize,
305 Eq,
306 PartialEq,
307 ValueDebugFormat,
308 Encode,
309 Decode,
310)]
311#[turbo_tasks::task_input]
312pub enum TracedMode {
313 Entry,
315 Transitive,
318}
319
320impl Display for TracedMode {
321 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
322 match self {
323 TracedMode::Entry => write!(f, "Entry"),
324 TracedMode::Transitive => write!(f, "Transitive"),
325 }
326 }
327}
328
329#[derive(
332 Debug,
333 Clone,
334 Hash,
335 Serialize,
336 Deserialize,
337 Eq,
338 PartialEq,
339 ValueDebugFormat,
340 NonLocalValue,
341 Encode,
342 Decode,
343)]
344pub enum ChunkingType {
345 Parallel {
347 inherit_async: bool,
350 hoisted: bool,
353 },
354 Async,
357 Isolated {
361 _ty: ChunkGroupType,
362 merge_tag: Option<RcStr>,
363 },
364 Emitted {
366 namespace: RcStr,
367 emit_to_all_entries: bool,
369 },
370 Collected { namespace: RcStr },
373 PerEntry,
375 Shared {
379 inherit_async: bool,
380 merge_tag: Option<RcStr>,
381 },
382 Traced {
386 mode: TracedMode,
388 },
389}
390
391impl Display for ChunkingType {
392 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
393 match self {
394 ChunkingType::Parallel {
395 inherit_async,
396 hoisted,
397 } => {
398 write!(
399 f,
400 "Parallel(inherit_async: {inherit_async}, hoisted: {hoisted})",
401 )
402 }
403 ChunkingType::Async => write!(f, "Async"),
404 ChunkingType::PerEntry => write!(f, "PerEntry"),
405 ChunkingType::Isolated {
406 _ty,
407 merge_tag: Some(merge_tag),
408 } => {
409 write!(f, "Isolated(merge_tag: {merge_tag})")
410 }
411 ChunkingType::Isolated {
412 _ty,
413 merge_tag: None,
414 } => {
415 write!(f, "Isolated")
416 }
417 ChunkingType::Emitted {
418 namespace,
419 emit_to_all_entries,
420 } => {
421 write!(
422 f,
423 "Emitted(namespace: {namespace}, emit_to_all_entries: {emit_to_all_entries})"
424 )
425 }
426 ChunkingType::Collected { namespace } => {
427 write!(f, "Collected(namespace: {namespace})")
428 }
429 ChunkingType::Shared {
430 inherit_async,
431 merge_tag: Some(merge_tag),
432 } => {
433 write!(
434 f,
435 "Shared(inherit_async: {inherit_async}, merge_tag: {merge_tag})"
436 )
437 }
438 ChunkingType::Shared {
439 inherit_async,
440 merge_tag: None,
441 } => {
442 write!(f, "Shared(inherit_async: {inherit_async})")
443 }
444 ChunkingType::Traced { mode } => write!(f, "Traced(mode: {mode})"),
445 }
446 }
447}
448
449impl ChunkingType {
450 pub fn is_inherit_async(&self) -> bool {
451 matches!(
452 self,
453 ChunkingType::Parallel {
454 inherit_async: true,
455 ..
456 } | ChunkingType::Shared {
457 inherit_async: true,
458 ..
459 }
460 )
461 }
462
463 pub fn is_parallel(&self) -> bool {
464 matches!(self, ChunkingType::Parallel { .. })
465 }
466
467 pub fn is_traced(&self) -> bool {
468 matches!(self, ChunkingType::Traced { .. })
469 }
470
471 pub fn is_merged(&self) -> bool {
472 matches!(
473 self,
474 ChunkingType::Isolated {
475 merge_tag: Some(_),
476 ..
477 } | ChunkingType::Shared {
478 merge_tag: Some(_),
479 ..
480 }
481 )
482 }
483
484 pub fn without_inherit_async(&self) -> Self {
485 match self {
486 ChunkingType::Parallel { hoisted, .. } => ChunkingType::Parallel {
487 hoisted: *hoisted,
488 inherit_async: false,
489 },
490 ChunkingType::Async => ChunkingType::Async,
491 ChunkingType::PerEntry => ChunkingType::PerEntry,
492 ChunkingType::Isolated { _ty, merge_tag } => ChunkingType::Isolated {
493 _ty: *_ty,
494 merge_tag: merge_tag.clone(),
495 },
496 ChunkingType::Emitted {
497 namespace,
498 emit_to_all_entries,
499 } => ChunkingType::Emitted {
500 namespace: namespace.clone(),
501 emit_to_all_entries: *emit_to_all_entries,
502 },
503 ChunkingType::Collected { namespace } => ChunkingType::Collected {
504 namespace: namespace.clone(),
505 },
506 ChunkingType::Shared {
507 inherit_async: _,
508 merge_tag,
509 } => ChunkingType::Shared {
510 inherit_async: false,
511 merge_tag: merge_tag.clone(),
512 },
513 ChunkingType::Traced { mode } => ChunkingType::Traced { mode: *mode },
514 }
515 }
516}
517
518#[turbo_tasks::value(cell = "new")]
520pub struct ChunkGroupContentInner {
521 pub chunkable_items: Vec<ChunkableModuleOrBatch>,
523 pub batch_groups: Vec<ResolvedVc<ModuleBatchGroup>>,
525 #[bincode(with = "turbo_bincode::indexset")]
527 pub async_modules: FxIndexSet<ResolvedVc<Box<dyn ChunkableModule>>>,
528 #[bincode(with = "turbo_bincode::indexset")]
530 pub collecting_modules: FxIndexSet<ResolvedVc<Box<dyn CollectingModule>>>,
531 pub available_modules: ResolvedVc<AvailableModulesSet>,
532}
533
534pub struct ChunkGroupContent {
535 pub inner: ReadRef<ChunkGroupContentInner>,
536 pub availability_info: AvailabilityInfo,
537}
538
539#[turbo_tasks::value_trait]
540pub trait ChunkItem: OutputAssetsReference {
541 #[turbo_tasks::function]
545 fn asset_ident(self: Vc<Self>) -> Vc<AssetIdent>;
546
547 #[turbo_tasks::function]
552 fn content_ident(self: Vc<Self>) -> Vc<AssetIdent> {
553 self.asset_ident()
554 }
555
556 fn ty(&self) -> Vc<Box<dyn ChunkType>>;
558
559 #[turbo_tasks::function]
562 fn module(self: Vc<Self>) -> Vc<Box<dyn Module>>;
563
564 fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>>;
565}
566
567#[turbo_tasks::value_trait]
568pub trait ChunkType: ValueToString {
569 #[turbo_tasks::function]
571 fn is_style(self: Vc<Self>) -> Vc<bool>;
572
573 #[turbo_tasks::function]
575 fn chunk(
576 &self,
577 chunking_context: Vc<Box<dyn ChunkingContext>>,
578 chunk_items: Vec<ChunkItemOrBatchWithAsyncModuleInfo>,
579 batch_groups: Vec<ResolvedVc<ChunkItemBatchGroup>>,
580 component_chunks: Vec<ResolvedVc<Box<dyn Chunk>>>,
581 ) -> Vc<Box<dyn Chunk>>;
582
583 #[turbo_tasks::function]
584 fn chunk_item_size(
585 &self,
586 chunking_context: Vc<Box<dyn ChunkingContext>>,
587 chunk_item: Vc<Box<dyn ChunkItem>>,
588 async_module_info: Option<Vc<AsyncModuleInfo>>,
589 ) -> Vc<usize>;
590}
591
592pub fn round_chunk_item_size(size: usize) -> usize {
593 let a = size.next_power_of_two();
594 size & (a | (a >> 1) | (a >> 2))
595}
596
597#[turbo_tasks::value(transparent)]
598pub struct ChunkItems(pub Vec<ResolvedVc<Box<dyn ChunkItem>>>);
599
600#[turbo_tasks::value]
601pub struct AsyncModuleInfo {
602 pub referenced_async_modules: AutoSet<ResolvedVc<Box<dyn Module>>>,
603}
604
605#[turbo_tasks::value_impl]
606impl AsyncModuleInfo {
607 #[turbo_tasks::function]
608 pub fn new(referenced_async_modules: Vec<ResolvedVc<Box<dyn Module>>>) -> Result<Vc<Self>> {
609 Ok(Self {
610 referenced_async_modules: referenced_async_modules.into_iter().collect(),
611 }
612 .cell())
613 }
614}
615
616#[turbo_tasks::task_input]
617#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Encode, Decode)]
618pub struct ChunkItemWithAsyncModuleInfo {
619 pub chunk_item: ResolvedVc<Box<dyn ChunkItem>>,
620 pub chunk_type: ResolvedVc<Box<dyn ChunkType>>,
621 pub module: Option<ResolvedVc<Box<dyn Module>>>,
622 pub async_info: Option<ResolvedVc<AsyncModuleInfo>>,
623}
624
625pub trait ChunkItemExt {
626 fn id(self: Vc<Self>) -> impl Future<Output = Result<ModuleId>> + Send;
628}
629
630impl<T> ChunkItemExt for T
631where
632 T: Upcast<Box<dyn ChunkItem>> + Send,
633{
634 async fn id(self: Vc<Self>) -> Result<ModuleId> {
636 let chunk_item = Vc::upcast_non_strict(self);
637 chunk_item
638 .into_trait_ref()
639 .await?
640 .chunking_context()
641 .chunk_item_id_strategy()
642 .await?
643 .get_id(chunk_item)
644 .await
645 }
646}
647
648pub trait ModuleChunkItemIdExt {
649 fn chunk_item_id(
651 self: Vc<Self>,
652 chunking_context: Vc<Box<dyn ChunkingContext>>,
653 ) -> impl Future<Output = Result<ModuleId>> + Send;
654}
655impl<T> ModuleChunkItemIdExt for T
656where
657 T: Upcast<Box<dyn Module>> + Send,
658{
659 async fn chunk_item_id(
660 self: Vc<Self>,
661 chunking_context: Vc<Box<dyn ChunkingContext>>,
662 ) -> Result<ModuleId> {
663 chunking_context
664 .chunk_item_id_strategy()
665 .await?
666 .get_id_from_module(Vc::upcast_non_strict(self))
667 .await
668 }
669}
670
671#[cfg(test)]
672mod tests {
673 use super::*;
674
675 #[test]
676 fn test_round_chunk_item_size() {
677 assert_eq!(round_chunk_item_size(0), 0);
678 assert_eq!(round_chunk_item_size(1), 1);
679 assert_eq!(round_chunk_item_size(2), 2);
680 assert_eq!(round_chunk_item_size(3), 3);
681 assert_eq!(round_chunk_item_size(4), 4);
682 assert_eq!(round_chunk_item_size(5), 4);
683 assert_eq!(round_chunk_item_size(6), 6);
684 assert_eq!(round_chunk_item_size(7), 6);
685 assert_eq!(round_chunk_item_size(8), 8);
686 assert_eq!(round_chunk_item_size(49000), 32_768);
687 assert_eq!(round_chunk_item_size(50000), 49_152);
688
689 assert_eq!(changes_in_range(0..1000), 19);
690 assert_eq!(changes_in_range(1000..2000), 2);
691 assert_eq!(changes_in_range(2000..3000), 1);
692
693 assert_eq!(changes_in_range(3000..10000), 4);
694
695 fn changes_in_range(range: std::ops::Range<usize>) -> usize {
696 let len = range.len();
697 let mut count = 0;
698 for i in range {
699 let a = round_chunk_item_size(i);
700 assert!(a >= i * 2 / 3);
701 assert!(a <= i);
702 let b = round_chunk_item_size(i + 1);
703
704 if a == b {
705 count += 1;
706 }
707 }
708 len - count
709 }
710 }
711}