Skip to main content

turbopack_core/chunk/
mod.rs

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 content hashing: Embeds the chunk content hash directly into the referencing chunk.
56    /// Benefit: No hash manifest needed.
57    /// Downside: Causes cascading hash invalidation.
58    Direct {
59        /// The length of the content hash in base38 chars. Anything lower than 7 is not
60        /// recommended due to the high risk of collisions.
61        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    /// Number of retry attempts after the initial load fails. `0` disables retries.
105    pub max_retry_attempts: u32,
106    /// Base delay before a retry, in milliseconds.
107    pub base_delay_ms: u32,
108    /// Maximum random jitter added to the base delay, in milliseconds.
109    pub max_jitter_ms: u32,
110}
111
112impl Default for ChunkLoadRetry {
113    fn default() -> Self {
114        // Retry a transient failure once after a short jittered delay. Network
115        // blips (a brief connection reset, a short CDN hiccup) often succeed on
116        // a second try.
117        Self {
118            max_retry_attempts: 1,
119            base_delay_ms: 200,
120            max_jitter_ms: 400,
121        }
122    }
123}
124
125/// A module id, which can be a number or string
126#[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/// A list of module ids.
153#[turbo_tasks::value(transparent, shared)]
154pub struct ModuleIds(Vec<ModuleId>);
155
156/// A [Module] that can be converted into a [ChunkItem].
157#[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/// A [Module] that can be merged with other [Module]s (to perform scope hoisting)
168// TODO currently this is only used for ecmascript modules, and with the current API cannot be used
169// with other module types (as a MergeableModule cannot prevent itself from being merged with other
170// module types)
171#[turbo_tasks::value_trait]
172pub trait MergeableModule: Module {
173    /// Even though MergeableModule is implemented, this allows a dynamic condition to determine
174    /// mergeability
175    #[turbo_tasks::function]
176    fn is_mergeable(self: Vc<Self>) -> Vc<bool> {
177        Vc::cell(true)
178    }
179
180    /// Create a new module representing the merged content of the given `modules`.
181    ///
182    /// Group entry points are not referenced by any other module in the group. This list is needed
183    /// because the merged module is created by recursively inlining modules when they are imported,
184    /// but this process has to start somewhere (= with these entry points).
185    #[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/// Whether a given module needs to be exposed (depending on how it is imported by other modules)
204#[turbo_tasks::task_input]
205#[derive(Copy, Clone, Debug, PartialEq, Eq, TraceRawVcs, Hash, Encode, Decode)]
206pub enum MergeableModuleExposure {
207    // This module is only used from within the current group, and only individual exports are
208    // used (and no namespace object is required).
209    None,
210    // This module is only used from within the current group, and but the namespace object is
211    // needed.
212    Internal,
213    // The exports of this module are read from outside this group (necessitating a namespace
214    // object anyway).
215    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/// Groups chunk items together into something that will become an [`OutputAsset`]. It usually
251/// contains multiple chunk items.
252///
253/// [`OutputAsset`]: crate::output::OutputAsset
254//
255// TODO: This could be simplified to and merged with OutputChunk
256#[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/// Aggregated information about a chunk content that can be used by the runtime
271/// code to optimize chunk loading.
272#[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    /// List of paths of chunks containing individual modules that are part of
278    /// this chunk. This is useful for selectively loading modules from a chunk
279    /// without loading the whole chunk.
280    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/// Whether this reference is an entry point for a traced subgraph.
299#[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    /// Going from bundled to unbundled code, i.e. an external dependency or readFile static assets.
316    Entry,
317    /// This reference should only be respected from unbundled code (e.g. for package.json needed by
318    /// externals (sort of affecting_sources)
319    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/// Specifies how a chunk interacts with other chunks when building a chunk
332/// group
333#[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    /// The referenced module is placed in the same chunk group and is loaded in parallel.
349    Parallel {
350        /// Whether the parent module becomes an async module when the referenced module is async.
351        /// This should happen for e.g. ESM imports, but not for CommonJS requires.
352        inherit_async: bool,
353        /// Whether the referenced module is executed always immediately before the parent module
354        /// (corresponding to ESM import semantics).
355        hoisted: bool,
356    },
357    /// An async loader is placed into the referencing chunk and loads the
358    /// separate chunk group in which the module is placed.
359    Async,
360    /// Create a new chunk group in a separate context, merging references with the same tag into a
361    /// single chunk group. It does not inherit the available modules from the parent.
362    // TODO this is currently skipped in chunking
363    Isolated {
364        _ty: ChunkGroupType,
365        merge_tag: Option<RcStr>,
366    },
367    /// Create a new chunk group in a separate context, merging references with the same tag into a
368    /// single chunk group. It provides available modules to the current chunk group. It's assumed
369    /// to be loaded before the current chunk group.
370    Shared {
371        inherit_async: bool,
372        merge_tag: Option<RcStr>,
373    },
374    /// The module not placed in chunk group, but its references are still followed. This is used
375    /// for NFT, to list all unbundled files that are still needed at runtime (some static assets,
376    /// or externals and their transitive dependencies).
377    Traced {
378        /// Whether this reference is an entry point for a traced subgraph.
379        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    /// The [AssetIdent] of the [Module] that this [ChunkItem] was created from.
503    /// For most chunk types this must uniquely identify the chunk item at
504    /// runtime as it's the source of the module id used at runtime.
505    #[turbo_tasks::function]
506    fn asset_ident(self: Vc<Self>) -> Vc<AssetIdent>;
507
508    /// A [AssetIdent] that uniquely identifies the content of this [ChunkItem].
509    /// It is usually identical to [ChunkItem::asset_ident] but can be
510    /// different when the chunk item content depends on available modules e. g.
511    /// for chunk loaders.
512    #[turbo_tasks::function]
513    fn content_ident(self: Vc<Self>) -> Vc<AssetIdent> {
514        self.asset_ident()
515    }
516
517    /// The type of chunk this item should be assembled into.
518    fn ty(&self) -> Vc<Box<dyn ChunkType>>;
519
520    /// A temporary method to retrieve the module associated with this
521    /// ChunkItem. TODO: Remove this as part of the chunk refactoring.
522    #[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    /// Whether the source (reference) order of items needs to be retained during chunking.
531    #[turbo_tasks::function]
532    fn is_style(self: Vc<Self>) -> Vc<bool>;
533
534    /// Create a new chunk for the given chunk items
535    #[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    /// Returns the module id of this chunk item.
587    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    /// Returns the module id of this chunk item.
595    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    /// Returns the chunk item id of this module.
610    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}