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, ResolvedVc, TaskInput, 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    },
34    data::{ChunkData, ChunkDataOption, ChunksData},
35    evaluate::{EvaluatableAsset, EvaluatableAssetExt, EvaluatableAssets},
36};
37use crate::{
38    asset::Asset,
39    chunk::availability_info::AvailabilityInfo,
40    ident::AssetIdent,
41    module::Module,
42    module_graph::{
43        ModuleGraph,
44        module_batch::{ChunkableModuleOrBatch, ModuleBatchGroup},
45    },
46    output::{OutputAssets, OutputAssetsReference},
47};
48
49#[derive(
50    Debug,
51    TaskInput,
52    Clone,
53    Copy,
54    PartialEq,
55    Eq,
56    Hash,
57    TraceRawVcs,
58    DeterministicHash,
59    NonLocalValue,
60    Encode,
61    Decode,
62)]
63pub enum ContentHashing {
64    /// Direct content hashing: Embeds the chunk content hash directly into the referencing chunk.
65    /// Benefit: No hash manifest needed.
66    /// Downside: Causes cascading hash invalidation.
67    Direct {
68        /// The length of the content hash in base38 chars. Anything lower than 7 is not
69        /// recommended due to the high risk of collisions.
70        length: u8,
71    },
72}
73
74#[turbo_tasks::value(shared)]
75#[derive(Debug, Default, Clone, Copy, Hash, Serialize, Deserialize, TaskInput)]
76#[serde(rename_all = "kebab-case")]
77pub enum CrossOrigin {
78    #[default]
79    None,
80    Anonymous,
81    UseCredentials,
82}
83
84impl CrossOrigin {
85    pub fn as_str(self) -> Option<&'static str> {
86        match self {
87            Self::None => None,
88            Self::Anonymous => Some("anonymous"),
89            Self::UseCredentials => Some("use-credentials"),
90        }
91    }
92}
93
94impl TryFrom<Option<&str>> for CrossOrigin {
95    type Error = anyhow::Error;
96
97    fn try_from(value: Option<&str>) -> Result<Self> {
98        match value {
99            None => Ok(Self::None),
100            Some("anonymous") => Ok(Self::Anonymous),
101            Some("use-credentials") => Ok(Self::UseCredentials),
102            Some(value) => bail!(
103                "invalid crossOrigin value `{value}`; supported values are `anonymous` and \
104                 `use-credentials`"
105            ),
106        }
107    }
108}
109
110/// A module id, which can be a number or string
111#[turbo_tasks::value(shared, operation)]
112#[derive(Debug, Clone, Hash, Ord, PartialOrd, DeterministicHash, Serialize, ValueToString)]
113#[serde(untagged)]
114pub enum ModuleId {
115    Number(u64),
116    String(RcStr),
117}
118
119impl Display for ModuleId {
120    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121        match self {
122            ModuleId::Number(i) => write!(f, "{i}"),
123            ModuleId::String(s) => write!(f, "{s}"),
124        }
125    }
126}
127
128impl ModuleId {
129    pub fn parse(id: &str) -> Result<ModuleId> {
130        Ok(match id.parse::<u64>() {
131            Ok(i) => ModuleId::Number(i),
132            Err(_) => ModuleId::String(id.into()),
133        })
134    }
135}
136
137/// A list of module ids.
138#[turbo_tasks::value(transparent, shared)]
139pub struct ModuleIds(Vec<ModuleId>);
140
141/// A [Module] that can be converted into a [ChunkItem].
142#[turbo_tasks::value_trait]
143pub trait ChunkableModule: Module {
144    #[turbo_tasks::function]
145    fn as_chunk_item(
146        self: Vc<Self>,
147        module_graph: Vc<ModuleGraph>,
148        chunking_context: Vc<Box<dyn ChunkingContext>>,
149    ) -> Vc<Box<dyn ChunkItem>>;
150}
151
152/// A [Module] that can be merged with other [Module]s (to perform scope hoisting)
153// TODO currently this is only used for ecmascript modules, and with the current API cannot be used
154// with other module types (as a MergeableModule cannot prevent itself from being merged with other
155// module types)
156#[turbo_tasks::value_trait]
157pub trait MergeableModule: Module {
158    /// Even though MergeableModule is implemented, this allows a dynamic condition to determine
159    /// mergeability
160    #[turbo_tasks::function]
161    fn is_mergeable(self: Vc<Self>) -> Vc<bool> {
162        Vc::cell(true)
163    }
164
165    /// Create a new module representing the merged content of the given `modules`.
166    ///
167    /// Group entry points are not referenced by any other module in the group. This list is needed
168    /// because the merged module is created by recursively inlining modules when they are imported,
169    /// but this process has to start somewhere (= with these entry points).
170    #[turbo_tasks::function]
171    fn merge(
172        self: Vc<Self>,
173        modules: Vc<MergeableModulesExposed>,
174        entry_points: Vc<MergeableModules>,
175    ) -> Vc<Box<dyn ChunkableModule>>;
176}
177#[turbo_tasks::value(transparent)]
178pub struct MergeableModules(Vec<ResolvedVc<Box<dyn MergeableModule>>>);
179
180#[turbo_tasks::value_impl]
181impl MergeableModules {
182    #[turbo_tasks::function]
183    pub fn interned(modules: Vec<ResolvedVc<Box<dyn MergeableModule>>>) -> Vc<Self> {
184        Vc::cell(modules)
185    }
186}
187
188/// Whether a given module needs to be exposed (depending on how it is imported by other modules)
189#[derive(
190    Copy, Clone, Debug, PartialEq, Eq, TraceRawVcs, NonLocalValue, TaskInput, Hash, Encode, Decode,
191)]
192pub enum MergeableModuleExposure {
193    // This module is only used from within the current group, and only individual exports are
194    // used (and no namespace object is required).
195    None,
196    // This module is only used from within the current group, and but the namespace object is
197    // needed.
198    Internal,
199    // The exports of this module are read from outside this group (necessitating a namespace
200    // object anyway).
201    External,
202}
203
204#[turbo_tasks::value(transparent)]
205pub struct MergeableModulesExposed(
206    Vec<(
207        ResolvedVc<Box<dyn MergeableModule>>,
208        MergeableModuleExposure,
209    )>,
210);
211
212#[turbo_tasks::value_impl]
213impl MergeableModulesExposed {
214    #[turbo_tasks::function]
215    pub fn interned(
216        modules: Vec<(
217            ResolvedVc<Box<dyn MergeableModule>>,
218            MergeableModuleExposure,
219        )>,
220    ) -> Vc<Self> {
221        Vc::cell(modules)
222    }
223}
224
225#[turbo_tasks::value(transparent)]
226pub struct Chunks(Vec<ResolvedVc<Box<dyn Chunk>>>);
227
228#[turbo_tasks::value_impl]
229impl Chunks {
230    #[turbo_tasks::function]
231    pub fn empty() -> Vc<Self> {
232        Vc::cell(vec![])
233    }
234}
235
236/// Groups chunk items together into something that will become an [`OutputAsset`]. It usually
237/// contains multiple chunk items.
238///
239/// [`OutputAsset`]: crate::output::OutputAsset
240//
241// TODO: This could be simplified to and merged with OutputChunk
242#[turbo_tasks::value_trait]
243pub trait Chunk: OutputAssetsReference {
244    #[turbo_tasks::function]
245    fn ident(self: Vc<Self>) -> Vc<AssetIdent>;
246
247    #[turbo_tasks::function]
248    fn chunking_context(self: Vc<Self>) -> Vc<Box<dyn ChunkingContext>>;
249
250    #[turbo_tasks::function]
251    fn chunk_items(self: Vc<Self>) -> Vc<ChunkItems> {
252        ChunkItems(vec![]).cell()
253    }
254}
255
256/// Aggregated information about a chunk content that can be used by the runtime
257/// code to optimize chunk loading.
258#[turbo_tasks::value(shared)]
259#[derive(Default)]
260pub struct OutputChunkRuntimeInfo {
261    pub included_ids: Option<ResolvedVc<ModuleIds>>,
262    pub excluded_ids: Option<ResolvedVc<ModuleIds>>,
263    /// List of paths of chunks containing individual modules that are part of
264    /// this chunk. This is useful for selectively loading modules from a chunk
265    /// without loading the whole chunk.
266    pub module_chunks: Option<ResolvedVc<OutputAssets>>,
267    pub placeholder_for_future_extensions: (),
268}
269
270#[turbo_tasks::value_impl]
271impl OutputChunkRuntimeInfo {
272    #[turbo_tasks::function]
273    pub fn empty() -> Vc<Self> {
274        Self::default().cell()
275    }
276}
277
278#[turbo_tasks::value_trait]
279pub trait OutputChunk: Asset {
280    #[turbo_tasks::function]
281    fn runtime_info(self: Vc<Self>) -> Vc<OutputChunkRuntimeInfo>;
282}
283
284/// Specifies how a chunk interacts with other chunks when building a chunk
285/// group
286#[derive(
287    Debug,
288    Clone,
289    Hash,
290    TraceRawVcs,
291    Serialize,
292    Deserialize,
293    Eq,
294    PartialEq,
295    ValueDebugFormat,
296    NonLocalValue,
297    Encode,
298    Decode,
299)]
300pub enum ChunkingType {
301    /// The referenced module is placed in the same chunk group and is loaded in parallel.
302    Parallel {
303        /// Whether the parent module becomes an async module when the referenced module is async.
304        /// This should happen for e.g. ESM imports, but not for CommonJS requires.
305        inherit_async: bool,
306        /// Whether the referenced module is executed always immediately before the parent module
307        /// (corresponding to ESM import semantics).
308        hoisted: bool,
309    },
310    /// An async loader is placed into the referencing chunk and loads the
311    /// separate chunk group in which the module is placed.
312    Async,
313    /// Create a new chunk group in a separate context, merging references with the same tag into a
314    /// single chunk group. It does not inherit the available modules from the parent.
315    // TODO this is currently skipped in chunking
316    Isolated {
317        _ty: ChunkGroupType,
318        merge_tag: Option<RcStr>,
319    },
320    /// Create a new chunk group in a separate context, merging references with the same tag into a
321    /// single chunk group. It provides available modules to the current chunk group. It's assumed
322    /// to be loaded before the current chunk group.
323    Shared {
324        inherit_async: bool,
325        merge_tag: Option<RcStr>,
326    },
327    // Module not placed in chunk group, but its references are still followed.
328    Traced,
329}
330
331impl Display for ChunkingType {
332    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
333        match self {
334            ChunkingType::Parallel {
335                inherit_async,
336                hoisted,
337            } => {
338                write!(
339                    f,
340                    "Parallel(inherit_async: {inherit_async}, hoisted: {hoisted})",
341                )
342            }
343            ChunkingType::Async => write!(f, "Async"),
344            ChunkingType::Isolated {
345                _ty,
346                merge_tag: Some(merge_tag),
347            } => {
348                write!(f, "Isolated(merge_tag: {merge_tag})")
349            }
350            ChunkingType::Isolated {
351                _ty,
352                merge_tag: None,
353            } => {
354                write!(f, "Isolated")
355            }
356            ChunkingType::Shared {
357                inherit_async,
358                merge_tag: Some(merge_tag),
359            } => {
360                write!(
361                    f,
362                    "Shared(inherit_async: {inherit_async}, merge_tag: {merge_tag})"
363                )
364            }
365            ChunkingType::Shared {
366                inherit_async,
367                merge_tag: None,
368            } => {
369                write!(f, "Shared(inherit_async: {inherit_async})")
370            }
371            ChunkingType::Traced => write!(f, "Traced"),
372        }
373    }
374}
375
376impl ChunkingType {
377    pub fn is_inherit_async(&self) -> bool {
378        matches!(
379            self,
380            ChunkingType::Parallel {
381                inherit_async: true,
382                ..
383            } | ChunkingType::Shared {
384                inherit_async: true,
385                ..
386            }
387        )
388    }
389
390    pub fn is_parallel(&self) -> bool {
391        matches!(self, ChunkingType::Parallel { .. })
392    }
393
394    pub fn is_merged(&self) -> bool {
395        matches!(
396            self,
397            ChunkingType::Isolated {
398                merge_tag: Some(_),
399                ..
400            } | ChunkingType::Shared {
401                merge_tag: Some(_),
402                ..
403            }
404        )
405    }
406
407    pub fn without_inherit_async(&self) -> Self {
408        match self {
409            ChunkingType::Parallel { hoisted, .. } => ChunkingType::Parallel {
410                hoisted: *hoisted,
411                inherit_async: false,
412            },
413            ChunkingType::Async => ChunkingType::Async,
414            ChunkingType::Isolated { _ty, merge_tag } => ChunkingType::Isolated {
415                _ty: *_ty,
416                merge_tag: merge_tag.clone(),
417            },
418            ChunkingType::Shared {
419                inherit_async: _,
420                merge_tag,
421            } => ChunkingType::Shared {
422                inherit_async: false,
423                merge_tag: merge_tag.clone(),
424            },
425            ChunkingType::Traced => ChunkingType::Traced,
426        }
427    }
428}
429
430pub struct ChunkGroupContent {
431    pub chunkable_items: Vec<ChunkableModuleOrBatch>,
432    pub batch_groups: Vec<ResolvedVc<ModuleBatchGroup>>,
433    pub async_modules: FxIndexSet<ResolvedVc<Box<dyn ChunkableModule>>>,
434    pub traced_modules: FxIndexSet<ResolvedVc<Box<dyn Module>>>,
435    pub availability_info: AvailabilityInfo,
436}
437
438#[turbo_tasks::value_trait]
439pub trait ChunkItem: OutputAssetsReference {
440    /// The [AssetIdent] of the [Module] that this [ChunkItem] was created from.
441    /// For most chunk types this must uniquely identify the chunk item at
442    /// runtime as it's the source of the module id used at runtime.
443    #[turbo_tasks::function]
444    fn asset_ident(self: Vc<Self>) -> Vc<AssetIdent>;
445
446    /// A [AssetIdent] that uniquely identifies the content of this [ChunkItem].
447    /// It is usually identical to [ChunkItem::asset_ident] but can be
448    /// different when the chunk item content depends on available modules e. g.
449    /// for chunk loaders.
450    #[turbo_tasks::function]
451    fn content_ident(self: Vc<Self>) -> Vc<AssetIdent> {
452        self.asset_ident()
453    }
454
455    /// The type of chunk this item should be assembled into.
456    fn ty(&self) -> Vc<Box<dyn ChunkType>>;
457
458    /// A temporary method to retrieve the module associated with this
459    /// ChunkItem. TODO: Remove this as part of the chunk refactoring.
460    #[turbo_tasks::function]
461    fn module(self: Vc<Self>) -> Vc<Box<dyn Module>>;
462
463    fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>>;
464}
465
466#[turbo_tasks::value_trait]
467pub trait ChunkType: ValueToString {
468    /// Whether the source (reference) order of items needs to be retained during chunking.
469    #[turbo_tasks::function]
470    fn is_style(self: Vc<Self>) -> Vc<bool>;
471
472    /// Create a new chunk for the given chunk items
473    #[turbo_tasks::function]
474    fn chunk(
475        &self,
476        chunking_context: Vc<Box<dyn ChunkingContext>>,
477        chunk_items: Vec<ChunkItemOrBatchWithAsyncModuleInfo>,
478        batch_groups: Vec<ResolvedVc<ChunkItemBatchGroup>>,
479    ) -> Vc<Box<dyn Chunk>>;
480
481    #[turbo_tasks::function]
482    fn chunk_item_size(
483        &self,
484        chunking_context: Vc<Box<dyn ChunkingContext>>,
485        chunk_item: Vc<Box<dyn ChunkItem>>,
486        async_module_info: Option<Vc<AsyncModuleInfo>>,
487    ) -> Vc<usize>;
488}
489
490pub fn round_chunk_item_size(size: usize) -> usize {
491    let a = size.next_power_of_two();
492    size & (a | (a >> 1) | (a >> 2))
493}
494
495#[turbo_tasks::value(transparent)]
496pub struct ChunkItems(pub Vec<ResolvedVc<Box<dyn ChunkItem>>>);
497
498#[turbo_tasks::value]
499pub struct AsyncModuleInfo {
500    pub referenced_async_modules: AutoSet<ResolvedVc<Box<dyn Module>>>,
501}
502
503#[turbo_tasks::value_impl]
504impl AsyncModuleInfo {
505    #[turbo_tasks::function]
506    pub fn new(referenced_async_modules: Vec<ResolvedVc<Box<dyn Module>>>) -> Result<Vc<Self>> {
507        Ok(Self {
508            referenced_async_modules: referenced_async_modules.into_iter().collect(),
509        }
510        .cell())
511    }
512}
513
514#[derive(
515    Debug, Clone, Copy, PartialEq, Eq, Hash, TraceRawVcs, TaskInput, NonLocalValue, Encode, Decode,
516)]
517pub struct ChunkItemWithAsyncModuleInfo {
518    pub chunk_item: ResolvedVc<Box<dyn ChunkItem>>,
519    pub chunk_type: ResolvedVc<Box<dyn ChunkType>>,
520    pub module: Option<ResolvedVc<Box<dyn ChunkableModule>>>,
521    pub async_info: Option<ResolvedVc<AsyncModuleInfo>>,
522}
523
524pub trait ChunkItemExt {
525    /// Returns the module id of this chunk item.
526    fn id(self: Vc<Self>) -> impl Future<Output = Result<ModuleId>> + Send;
527}
528
529impl<T> ChunkItemExt for T
530where
531    T: Upcast<Box<dyn ChunkItem>> + Send,
532{
533    /// Returns the module id of this chunk item.
534    async fn id(self: Vc<Self>) -> Result<ModuleId> {
535        let chunk_item = Vc::upcast_non_strict(self);
536        chunk_item
537            .into_trait_ref()
538            .await?
539            .chunking_context()
540            .chunk_item_id_strategy()
541            .await?
542            .get_id(chunk_item)
543            .await
544    }
545}
546
547pub trait ModuleChunkItemIdExt {
548    /// Returns the chunk item id of this module.
549    fn chunk_item_id(
550        self: Vc<Self>,
551        chunking_context: Vc<Box<dyn ChunkingContext>>,
552    ) -> impl Future<Output = Result<ModuleId>> + Send;
553}
554impl<T> ModuleChunkItemIdExt for T
555where
556    T: Upcast<Box<dyn Module>> + Send,
557{
558    async fn chunk_item_id(
559        self: Vc<Self>,
560        chunking_context: Vc<Box<dyn ChunkingContext>>,
561    ) -> Result<ModuleId> {
562        chunking_context
563            .chunk_item_id_strategy()
564            .await?
565            .get_id_from_module(Vc::upcast_non_strict(self))
566            .await
567    }
568}
569
570#[cfg(test)]
571mod tests {
572    use super::*;
573
574    #[test]
575    fn test_round_chunk_item_size() {
576        assert_eq!(round_chunk_item_size(0), 0);
577        assert_eq!(round_chunk_item_size(1), 1);
578        assert_eq!(round_chunk_item_size(2), 2);
579        assert_eq!(round_chunk_item_size(3), 3);
580        assert_eq!(round_chunk_item_size(4), 4);
581        assert_eq!(round_chunk_item_size(5), 4);
582        assert_eq!(round_chunk_item_size(6), 6);
583        assert_eq!(round_chunk_item_size(7), 6);
584        assert_eq!(round_chunk_item_size(8), 8);
585        assert_eq!(round_chunk_item_size(49000), 32_768);
586        assert_eq!(round_chunk_item_size(50000), 49_152);
587
588        assert_eq!(changes_in_range(0..1000), 19);
589        assert_eq!(changes_in_range(1000..2000), 2);
590        assert_eq!(changes_in_range(2000..3000), 1);
591
592        assert_eq!(changes_in_range(3000..10000), 4);
593
594        fn changes_in_range(range: std::ops::Range<usize>) -> usize {
595            let len = range.len();
596            let mut count = 0;
597            for i in range {
598                let a = round_chunk_item_size(i);
599                assert!(a >= i * 2 / 3);
600                assert!(a <= i);
601                let b = round_chunk_item_size(i + 1);
602
603                if a == b {
604                    count += 1;
605                }
606            }
607            len - count
608        }
609    }
610}