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, trace::TraceRawVcs,
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, MangleType, MinifyType,
32 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(
53 Debug, Clone, Copy, PartialEq, Eq, Hash, TraceRawVcs, DeterministicHash, Encode, Decode,
54)]
55pub enum ContentHashing {
56 Direct {
60 length: u8,
63 },
64}
65
66#[turbo_tasks::value(shared)]
67#[derive(Debug, Default, Clone, Copy, Hash, Serialize, Deserialize)]
68#[serde(rename_all = "kebab-case")]
69pub enum CrossOrigin {
70 #[default]
71 None,
72 Anonymous,
73 UseCredentials,
74}
75
76impl CrossOrigin {
77 pub fn as_str(self) -> Option<&'static str> {
78 match self {
79 Self::None => None,
80 Self::Anonymous => Some("anonymous"),
81 Self::UseCredentials => Some("use-credentials"),
82 }
83 }
84}
85
86impl TryFrom<Option<&str>> for CrossOrigin {
87 type Error = anyhow::Error;
88
89 fn try_from(value: Option<&str>) -> Result<Self> {
90 match value {
91 None => Ok(Self::None),
92 Some("anonymous") => Ok(Self::Anonymous),
93 Some("use-credentials") => Ok(Self::UseCredentials),
94 Some(value) => bail!(
95 "invalid crossOrigin value `{value}`; supported values are `anonymous` and \
96 `use-credentials`"
97 ),
98 }
99 }
100}
101
102#[turbo_tasks::value(shared)]
103#[derive(Debug, Clone, Copy, Hash, Serialize, Deserialize)]
104pub struct ChunkLoadRetry {
105 pub max_retry_attempts: u32,
107 pub base_delay_ms: u32,
109 pub max_jitter_ms: u32,
111}
112
113impl Default for ChunkLoadRetry {
114 fn default() -> Self {
115 Self {
119 max_retry_attempts: 1,
120 base_delay_ms: 200,
121 max_jitter_ms: 400,
122 }
123 }
124}
125
126#[turbo_tasks::value(shared, operation)]
128#[derive(Debug, Clone, Hash, Ord, PartialOrd, DeterministicHash, Serialize, ValueToString)]
129#[serde(untagged)]
130pub enum ModuleId {
131 Number(u64),
132 String(RcStr),
133}
134
135impl Display for ModuleId {
136 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137 match self {
138 ModuleId::Number(i) => write!(f, "{i}"),
139 ModuleId::String(s) => write!(f, "{s}"),
140 }
141 }
142}
143
144impl ModuleId {
145 pub fn parse(id: &str) -> Result<ModuleId> {
146 Ok(match id.parse::<u64>() {
147 Ok(i) => ModuleId::Number(i),
148 Err(_) => ModuleId::String(id.into()),
149 })
150 }
151}
152
153#[turbo_tasks::value(transparent, shared)]
155pub struct ModuleIds(Vec<ModuleId>);
156
157#[turbo_tasks::value_trait]
159pub trait ChunkableModule: Module {
160 #[turbo_tasks::function]
161 fn as_chunk_item(
162 self: Vc<Self>,
163 module_graph: Vc<ModuleGraph>,
164 chunking_context: Vc<Box<dyn ChunkingContext>>,
165 ) -> Vc<Box<dyn ChunkItem>>;
166}
167
168#[turbo_tasks::value_trait]
173pub trait MergeableModule: Module {
174 #[turbo_tasks::function]
177 fn is_mergeable(self: Vc<Self>) -> Vc<bool> {
178 Vc::cell(true)
179 }
180
181 #[turbo_tasks::function]
187 fn merge(
188 self: Vc<Self>,
189 modules: Vc<MergeableModulesExposed>,
190 entry_points: Vc<MergeableModules>,
191 ) -> Vc<Box<dyn ChunkableModule>>;
192}
193#[turbo_tasks::value(transparent)]
194pub struct MergeableModules(Vec<ResolvedVc<Box<dyn MergeableModule>>>);
195
196#[turbo_tasks::value_impl]
197impl MergeableModules {
198 #[turbo_tasks::function]
199 pub fn interned(modules: Vec<ResolvedVc<Box<dyn MergeableModule>>>) -> Vc<Self> {
200 Vc::cell(modules)
201 }
202}
203
204#[turbo_tasks::task_input]
206#[derive(Copy, Clone, Debug, PartialEq, Eq, TraceRawVcs, Hash, Encode, Decode)]
207pub enum MergeableModuleExposure {
208 None,
211 Internal,
214 External,
217}
218
219#[turbo_tasks::value(transparent)]
220pub struct MergeableModulesExposed(
221 Vec<(
222 ResolvedVc<Box<dyn MergeableModule>>,
223 MergeableModuleExposure,
224 )>,
225);
226
227#[turbo_tasks::value_impl]
228impl MergeableModulesExposed {
229 #[turbo_tasks::function]
230 pub fn interned(
231 modules: Vec<(
232 ResolvedVc<Box<dyn MergeableModule>>,
233 MergeableModuleExposure,
234 )>,
235 ) -> Vc<Self> {
236 Vc::cell(modules)
237 }
238}
239
240#[turbo_tasks::value(transparent)]
241pub struct Chunks(Vec<ResolvedVc<Box<dyn Chunk>>>);
242
243#[turbo_tasks::value_impl]
244impl Chunks {
245 #[turbo_tasks::function]
246 pub fn empty() -> Vc<Self> {
247 Vc::cell(vec![])
248 }
249}
250
251#[turbo_tasks::value_trait]
258pub trait Chunk: OutputAssetsReference {
259 #[turbo_tasks::function]
260 fn ident(self: Vc<Self>) -> Vc<AssetIdent>;
261
262 #[turbo_tasks::function]
263 fn chunking_context(self: Vc<Self>) -> Vc<Box<dyn ChunkingContext>>;
264
265 #[turbo_tasks::function]
266 fn chunk_items(self: Vc<Self>) -> Vc<ChunkItems> {
267 ChunkItems(vec![]).cell()
268 }
269}
270
271#[turbo_tasks::value(shared)]
274#[derive(Default)]
275pub struct OutputChunkRuntimeInfo {
276 pub included_ids: Option<ResolvedVc<ModuleIds>>,
277 pub excluded_ids: Option<ResolvedVc<ModuleIds>>,
278 pub module_chunks: Option<ResolvedVc<OutputAssets>>,
282 pub placeholder_for_future_extensions: (),
283}
284
285#[turbo_tasks::value_impl]
286impl OutputChunkRuntimeInfo {
287 #[turbo_tasks::function]
288 pub fn empty() -> Vc<Self> {
289 Self::default().cell()
290 }
291}
292
293#[turbo_tasks::value_trait]
294pub trait OutputChunk: Asset {
295 #[turbo_tasks::function]
296 fn runtime_info(self: Vc<Self>) -> Vc<OutputChunkRuntimeInfo>;
297}
298
299#[derive(
301 Debug,
302 Clone,
303 Copy,
304 Hash,
305 TraceRawVcs,
306 Serialize,
307 Deserialize,
308 Eq,
309 PartialEq,
310 ValueDebugFormat,
311 Encode,
312 Decode,
313)]
314#[turbo_tasks::task_input]
315pub enum TracedMode {
316 Entry,
318 Transitive,
321}
322
323impl Display for TracedMode {
324 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
325 match self {
326 TracedMode::Entry => write!(f, "Entry"),
327 TracedMode::Transitive => write!(f, "Transitive"),
328 }
329 }
330}
331
332#[derive(
335 Debug,
336 Clone,
337 Hash,
338 TraceRawVcs,
339 Serialize,
340 Deserialize,
341 Eq,
342 PartialEq,
343 ValueDebugFormat,
344 NonLocalValue,
345 Encode,
346 Decode,
347)]
348pub enum ChunkingType {
349 Parallel {
351 inherit_async: bool,
354 hoisted: bool,
357 },
358 Async,
361 Isolated {
365 _ty: ChunkGroupType,
366 merge_tag: Option<RcStr>,
367 },
368 Emitted {
370 namespace: RcStr,
371 emit_to_all_entries: bool,
373 },
374 Collected { namespace: RcStr },
377 PerEntry,
379 Shared {
383 inherit_async: bool,
384 merge_tag: Option<RcStr>,
385 },
386 Traced {
390 mode: TracedMode,
392 },
393}
394
395impl Display for ChunkingType {
396 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
397 match self {
398 ChunkingType::Parallel {
399 inherit_async,
400 hoisted,
401 } => {
402 write!(
403 f,
404 "Parallel(inherit_async: {inherit_async}, hoisted: {hoisted})",
405 )
406 }
407 ChunkingType::Async => write!(f, "Async"),
408 ChunkingType::PerEntry => write!(f, "PerEntry"),
409 ChunkingType::Isolated {
410 _ty,
411 merge_tag: Some(merge_tag),
412 } => {
413 write!(f, "Isolated(merge_tag: {merge_tag})")
414 }
415 ChunkingType::Isolated {
416 _ty,
417 merge_tag: None,
418 } => {
419 write!(f, "Isolated")
420 }
421 ChunkingType::Emitted {
422 namespace,
423 emit_to_all_entries,
424 } => {
425 write!(
426 f,
427 "Emitted(namespace: {namespace}, emit_to_all_entries: {emit_to_all_entries})"
428 )
429 }
430 ChunkingType::Collected { namespace } => {
431 write!(f, "Collected(namespace: {namespace})")
432 }
433 ChunkingType::Shared {
434 inherit_async,
435 merge_tag: Some(merge_tag),
436 } => {
437 write!(
438 f,
439 "Shared(inherit_async: {inherit_async}, merge_tag: {merge_tag})"
440 )
441 }
442 ChunkingType::Shared {
443 inherit_async,
444 merge_tag: None,
445 } => {
446 write!(f, "Shared(inherit_async: {inherit_async})")
447 }
448 ChunkingType::Traced { mode } => write!(f, "Traced(mode: {mode})"),
449 }
450 }
451}
452
453impl ChunkingType {
454 pub fn is_inherit_async(&self) -> bool {
455 matches!(
456 self,
457 ChunkingType::Parallel {
458 inherit_async: true,
459 ..
460 } | ChunkingType::Shared {
461 inherit_async: true,
462 ..
463 }
464 )
465 }
466
467 pub fn is_parallel(&self) -> bool {
468 matches!(self, ChunkingType::Parallel { .. })
469 }
470
471 pub fn is_traced(&self) -> bool {
472 matches!(self, ChunkingType::Traced { .. })
473 }
474
475 pub fn is_merged(&self) -> bool {
476 matches!(
477 self,
478 ChunkingType::Isolated {
479 merge_tag: Some(_),
480 ..
481 } | ChunkingType::Shared {
482 merge_tag: Some(_),
483 ..
484 }
485 )
486 }
487
488 pub fn without_inherit_async(&self) -> Self {
489 match self {
490 ChunkingType::Parallel { hoisted, .. } => ChunkingType::Parallel {
491 hoisted: *hoisted,
492 inherit_async: false,
493 },
494 ChunkingType::Async => ChunkingType::Async,
495 ChunkingType::PerEntry => ChunkingType::PerEntry,
496 ChunkingType::Isolated { _ty, merge_tag } => ChunkingType::Isolated {
497 _ty: *_ty,
498 merge_tag: merge_tag.clone(),
499 },
500 ChunkingType::Emitted {
501 namespace,
502 emit_to_all_entries,
503 } => ChunkingType::Emitted {
504 namespace: namespace.clone(),
505 emit_to_all_entries: *emit_to_all_entries,
506 },
507 ChunkingType::Collected { namespace } => ChunkingType::Collected {
508 namespace: namespace.clone(),
509 },
510 ChunkingType::Shared {
511 inherit_async: _,
512 merge_tag,
513 } => ChunkingType::Shared {
514 inherit_async: false,
515 merge_tag: merge_tag.clone(),
516 },
517 ChunkingType::Traced { mode } => ChunkingType::Traced { mode: *mode },
518 }
519 }
520}
521
522#[turbo_tasks::value(cell = "new")]
524pub struct ChunkGroupContentInner {
525 pub chunkable_items: Vec<ChunkableModuleOrBatch>,
527 pub batch_groups: Vec<ResolvedVc<ModuleBatchGroup>>,
529 #[bincode(with = "turbo_bincode::indexset")]
531 pub async_modules: FxIndexSet<ResolvedVc<Box<dyn ChunkableModule>>>,
532 #[bincode(with = "turbo_bincode::indexset")]
534 pub collecting_modules: FxIndexSet<ResolvedVc<Box<dyn CollectingModule>>>,
535 pub available_modules: ResolvedVc<AvailableModulesSet>,
536}
537
538pub struct ChunkGroupContent {
539 pub inner: ReadRef<ChunkGroupContentInner>,
540 pub availability_info: AvailabilityInfo,
541}
542
543#[turbo_tasks::value_trait]
544pub trait ChunkItem: OutputAssetsReference {
545 #[turbo_tasks::function]
549 fn asset_ident(self: Vc<Self>) -> Vc<AssetIdent>;
550
551 #[turbo_tasks::function]
556 fn content_ident(self: Vc<Self>) -> Vc<AssetIdent> {
557 self.asset_ident()
558 }
559
560 fn ty(&self) -> Vc<Box<dyn ChunkType>>;
562
563 #[turbo_tasks::function]
566 fn module(self: Vc<Self>) -> Vc<Box<dyn Module>>;
567
568 fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>>;
569}
570
571#[turbo_tasks::value_trait]
572pub trait ChunkType: ValueToString {
573 #[turbo_tasks::function]
575 fn is_style(self: Vc<Self>) -> Vc<bool>;
576
577 #[turbo_tasks::function]
579 fn chunk(
580 &self,
581 chunking_context: Vc<Box<dyn ChunkingContext>>,
582 chunk_items: Vec<ChunkItemOrBatchWithAsyncModuleInfo>,
583 batch_groups: Vec<ResolvedVc<ChunkItemBatchGroup>>,
584 component_chunks: Vec<ResolvedVc<Box<dyn Chunk>>>,
585 ) -> Vc<Box<dyn Chunk>>;
586
587 #[turbo_tasks::function]
588 fn chunk_item_size(
589 &self,
590 chunking_context: Vc<Box<dyn ChunkingContext>>,
591 chunk_item: Vc<Box<dyn ChunkItem>>,
592 async_module_info: Option<Vc<AsyncModuleInfo>>,
593 ) -> Vc<usize>;
594}
595
596pub fn round_chunk_item_size(size: usize) -> usize {
597 let a = size.next_power_of_two();
598 size & (a | (a >> 1) | (a >> 2))
599}
600
601#[turbo_tasks::value(transparent)]
602pub struct ChunkItems(pub Vec<ResolvedVc<Box<dyn ChunkItem>>>);
603
604#[turbo_tasks::value]
605pub struct AsyncModuleInfo {
606 pub referenced_async_modules: AutoSet<ResolvedVc<Box<dyn Module>>>,
607}
608
609#[turbo_tasks::value_impl]
610impl AsyncModuleInfo {
611 #[turbo_tasks::function]
612 pub fn new(referenced_async_modules: Vec<ResolvedVc<Box<dyn Module>>>) -> Result<Vc<Self>> {
613 Ok(Self {
614 referenced_async_modules: referenced_async_modules.into_iter().collect(),
615 }
616 .cell())
617 }
618}
619
620#[turbo_tasks::task_input]
621#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TraceRawVcs, Encode, Decode)]
622pub struct ChunkItemWithAsyncModuleInfo {
623 pub chunk_item: ResolvedVc<Box<dyn ChunkItem>>,
624 pub chunk_type: ResolvedVc<Box<dyn ChunkType>>,
625 pub module: Option<ResolvedVc<Box<dyn Module>>>,
626 pub async_info: Option<ResolvedVc<AsyncModuleInfo>>,
627}
628
629pub trait ChunkItemExt {
630 fn id(self: Vc<Self>) -> impl Future<Output = Result<ModuleId>> + Send;
632}
633
634impl<T> ChunkItemExt for T
635where
636 T: Upcast<Box<dyn ChunkItem>> + Send,
637{
638 async fn id(self: Vc<Self>) -> Result<ModuleId> {
640 let chunk_item = Vc::upcast_non_strict(self);
641 chunk_item
642 .into_trait_ref()
643 .await?
644 .chunking_context()
645 .chunk_item_id_strategy()
646 .await?
647 .get_id(chunk_item)
648 .await
649 }
650}
651
652pub trait ModuleChunkItemIdExt {
653 fn chunk_item_id(
655 self: Vc<Self>,
656 chunking_context: Vc<Box<dyn ChunkingContext>>,
657 ) -> impl Future<Output = Result<ModuleId>> + Send;
658}
659impl<T> ModuleChunkItemIdExt for T
660where
661 T: Upcast<Box<dyn Module>> + Send,
662{
663 async fn chunk_item_id(
664 self: Vc<Self>,
665 chunking_context: Vc<Box<dyn ChunkingContext>>,
666 ) -> Result<ModuleId> {
667 chunking_context
668 .chunk_item_id_strategy()
669 .await?
670 .get_id_from_module(Vc::upcast_non_strict(self))
671 .await
672 }
673}
674
675#[cfg(test)]
676mod tests {
677 use super::*;
678
679 #[test]
680 fn test_round_chunk_item_size() {
681 assert_eq!(round_chunk_item_size(0), 0);
682 assert_eq!(round_chunk_item_size(1), 1);
683 assert_eq!(round_chunk_item_size(2), 2);
684 assert_eq!(round_chunk_item_size(3), 3);
685 assert_eq!(round_chunk_item_size(4), 4);
686 assert_eq!(round_chunk_item_size(5), 4);
687 assert_eq!(round_chunk_item_size(6), 6);
688 assert_eq!(round_chunk_item_size(7), 6);
689 assert_eq!(round_chunk_item_size(8), 8);
690 assert_eq!(round_chunk_item_size(49000), 32_768);
691 assert_eq!(round_chunk_item_size(50000), 49_152);
692
693 assert_eq!(changes_in_range(0..1000), 19);
694 assert_eq!(changes_in_range(1000..2000), 2);
695 assert_eq!(changes_in_range(2000..3000), 1);
696
697 assert_eq!(changes_in_range(3000..10000), 4);
698
699 fn changes_in_range(range: std::ops::Range<usize>) -> usize {
700 let len = range.len();
701 let mut count = 0;
702 for i in range {
703 let a = round_chunk_item_size(i);
704 assert!(a >= i * 2 / 3);
705 assert!(a <= i);
706 let b = round_chunk_item_size(i + 1);
707
708 if a == b {
709 count += 1;
710 }
711 }
712 len - count
713 }
714 }
715}