turbopack_core/chunk/
mod.rs

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