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;
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    #[turbo_tasks::function]
457    fn ty(self: Vc<Self>) -> Vc<Box<dyn ChunkType>>;
458
459    /// A temporary method to retrieve the module associated with this
460    /// ChunkItem. TODO: Remove this as part of the chunk refactoring.
461    #[turbo_tasks::function]
462    fn module(self: Vc<Self>) -> Vc<Box<dyn Module>>;
463
464    #[turbo_tasks::function]
465    fn chunking_context(self: Vc<Self>) -> Vc<Box<dyn ChunkingContext>>;
466}
467
468#[turbo_tasks::value_trait]
469pub trait ChunkType: ValueToString {
470    /// Whether the source (reference) order of items needs to be retained during chunking.
471    #[turbo_tasks::function]
472    fn is_style(self: Vc<Self>) -> Vc<bool>;
473
474    /// Create a new chunk for the given chunk items
475    #[turbo_tasks::function]
476    fn chunk(
477        &self,
478        chunking_context: Vc<Box<dyn ChunkingContext>>,
479        chunk_items: Vec<ChunkItemOrBatchWithAsyncModuleInfo>,
480        batch_groups: Vec<ResolvedVc<ChunkItemBatchGroup>>,
481    ) -> Vc<Box<dyn Chunk>>;
482
483    #[turbo_tasks::function]
484    fn chunk_item_size(
485        &self,
486        chunking_context: Vc<Box<dyn ChunkingContext>>,
487        chunk_item: Vc<Box<dyn ChunkItem>>,
488        async_module_info: Option<Vc<AsyncModuleInfo>>,
489    ) -> Vc<usize>;
490}
491
492pub fn round_chunk_item_size(size: usize) -> usize {
493    let a = size.next_power_of_two();
494    size & (a | (a >> 1) | (a >> 2))
495}
496
497#[turbo_tasks::value(transparent)]
498pub struct ChunkItems(pub Vec<ResolvedVc<Box<dyn ChunkItem>>>);
499
500#[turbo_tasks::value]
501pub struct AsyncModuleInfo {
502    pub referenced_async_modules: AutoSet<ResolvedVc<Box<dyn Module>>>,
503}
504
505#[turbo_tasks::value_impl]
506impl AsyncModuleInfo {
507    #[turbo_tasks::function]
508    pub fn new(referenced_async_modules: Vec<ResolvedVc<Box<dyn Module>>>) -> Result<Vc<Self>> {
509        Ok(Self {
510            referenced_async_modules: referenced_async_modules.into_iter().collect(),
511        }
512        .cell())
513    }
514}
515
516#[derive(
517    Debug, Clone, PartialEq, Eq, Hash, TraceRawVcs, TaskInput, NonLocalValue, Encode, Decode,
518)]
519pub struct ChunkItemWithAsyncModuleInfo {
520    pub chunk_item: ResolvedVc<Box<dyn ChunkItem>>,
521    pub module: Option<ResolvedVc<Box<dyn ChunkableModule>>>,
522    pub async_info: Option<ResolvedVc<AsyncModuleInfo>>,
523}
524
525pub trait ChunkItemExt {
526    /// Returns the module id of this chunk item.
527    fn id(self: Vc<Self>) -> impl Future<Output = Result<ModuleId>> + Send;
528}
529
530impl<T> ChunkItemExt for T
531where
532    T: Upcast<Box<dyn ChunkItem>> + Send,
533{
534    /// Returns the module id of this chunk item.
535    async fn id(self: Vc<Self>) -> Result<ModuleId> {
536        let chunk_item = Vc::upcast_non_strict(self);
537        chunk_item
538            .chunking_context()
539            .chunk_item_id_strategy()
540            .await?
541            .get_id(chunk_item)
542            .await
543    }
544}
545
546pub trait ModuleChunkItemIdExt {
547    /// Returns the chunk item id of this module.
548    fn chunk_item_id(
549        self: Vc<Self>,
550        chunking_context: Vc<Box<dyn ChunkingContext>>,
551    ) -> impl Future<Output = Result<ModuleId>> + Send;
552}
553impl<T> ModuleChunkItemIdExt for T
554where
555    T: Upcast<Box<dyn Module>> + Send,
556{
557    async fn chunk_item_id(
558        self: Vc<Self>,
559        chunking_context: Vc<Box<dyn ChunkingContext>>,
560    ) -> Result<ModuleId> {
561        chunking_context
562            .chunk_item_id_strategy()
563            .await?
564            .get_id_from_module(Vc::upcast_non_strict(self))
565            .await
566    }
567}
568
569#[cfg(test)]
570mod tests {
571    use super::*;
572
573    #[test]
574    fn test_round_chunk_item_size() {
575        assert_eq!(round_chunk_item_size(0), 0);
576        assert_eq!(round_chunk_item_size(1), 1);
577        assert_eq!(round_chunk_item_size(2), 2);
578        assert_eq!(round_chunk_item_size(3), 3);
579        assert_eq!(round_chunk_item_size(4), 4);
580        assert_eq!(round_chunk_item_size(5), 4);
581        assert_eq!(round_chunk_item_size(6), 6);
582        assert_eq!(round_chunk_item_size(7), 6);
583        assert_eq!(round_chunk_item_size(8), 8);
584        assert_eq!(round_chunk_item_size(49000), 32_768);
585        assert_eq!(round_chunk_item_size(50000), 49_152);
586
587        assert_eq!(changes_in_range(0..1000), 19);
588        assert_eq!(changes_in_range(1000..2000), 2);
589        assert_eq!(changes_in_range(2000..3000), 1);
590
591        assert_eq!(changes_in_range(3000..10000), 4);
592
593        fn changes_in_range(range: std::ops::Range<usize>) -> usize {
594            let len = range.len();
595            let mut count = 0;
596            for i in range {
597                let a = round_chunk_item_size(i);
598                assert!(a >= i * 2 / 3);
599                assert!(a <= i);
600                let b = round_chunk_item_size(i + 1);
601
602                if a == b {
603                    count += 1;
604                }
605            }
606            len - count
607        }
608    }
609}