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