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