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, OutputAssetsReference},
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: OutputAssetsReference {
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    #[turbo_tasks::function]
222    fn chunk_items(self: Vc<Self>) -> Vc<ChunkItems> {
223        ChunkItems(vec![]).cell()
224    }
225}
226
227/// Aggregated information about a chunk content that can be used by the runtime
228/// code to optimize chunk loading.
229#[turbo_tasks::value(shared)]
230#[derive(Default)]
231pub struct OutputChunkRuntimeInfo {
232    pub included_ids: Option<ResolvedVc<ModuleIds>>,
233    pub excluded_ids: Option<ResolvedVc<ModuleIds>>,
234    /// List of paths of chunks containing individual modules that are part of
235    /// this chunk. This is useful for selectively loading modules from a chunk
236    /// without loading the whole chunk.
237    pub module_chunks: Option<ResolvedVc<OutputAssets>>,
238    pub placeholder_for_future_extensions: (),
239}
240
241#[turbo_tasks::value_impl]
242impl OutputChunkRuntimeInfo {
243    #[turbo_tasks::function]
244    pub fn empty() -> Vc<Self> {
245        Self::default().cell()
246    }
247}
248
249#[turbo_tasks::value_trait]
250pub trait OutputChunk: Asset {
251    #[turbo_tasks::function]
252    fn runtime_info(self: Vc<Self>) -> Vc<OutputChunkRuntimeInfo>;
253}
254
255/// Specifies how a chunk interacts with other chunks when building a chunk
256/// group
257#[derive(
258    Debug,
259    Clone,
260    Hash,
261    TraceRawVcs,
262    Serialize,
263    Deserialize,
264    Eq,
265    PartialEq,
266    ValueDebugFormat,
267    NonLocalValue,
268)]
269pub enum ChunkingType {
270    /// The referenced module is placed in the same chunk group and is loaded in parallel.
271    Parallel {
272        /// Whether the parent module becomes an async module when the referenced module is async.
273        /// This should happen for e.g. ESM imports, but not for CommonJS requires.
274        inherit_async: bool,
275        /// Whether the referenced module is executed always immediately before the parent module
276        /// (corresponding to ESM import semantics).
277        hoisted: bool,
278    },
279    /// An async loader is placed into the referencing chunk and loads the
280    /// separate chunk group in which the module is placed.
281    Async,
282    /// Create a new chunk group in a separate context, merging references with the same tag into a
283    /// single chunk group. It does not inherit the available modules from the parent.
284    // TODO this is currently skipped in chunking
285    Isolated {
286        _ty: ChunkGroupType,
287        merge_tag: Option<RcStr>,
288    },
289    /// Create a new chunk group in a separate context, merging references with the same tag into a
290    /// single chunk group. It provides available modules to the current chunk group. It's assumed
291    /// to be loaded before the current chunk group.
292    Shared {
293        inherit_async: bool,
294        merge_tag: Option<RcStr>,
295    },
296    // Module not placed in chunk group, but its references are still followed.
297    Traced,
298}
299
300impl Display for ChunkingType {
301    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
302        match self {
303            ChunkingType::Parallel {
304                inherit_async,
305                hoisted,
306            } => {
307                write!(
308                    f,
309                    "Parallel(inherit_async: {inherit_async}, hoisted: {hoisted})",
310                )
311            }
312            ChunkingType::Async => write!(f, "Async"),
313            ChunkingType::Isolated {
314                _ty,
315                merge_tag: Some(merge_tag),
316            } => {
317                write!(f, "Isolated(merge_tag: {merge_tag})")
318            }
319            ChunkingType::Isolated {
320                _ty,
321                merge_tag: None,
322            } => {
323                write!(f, "Isolated")
324            }
325            ChunkingType::Shared {
326                inherit_async,
327                merge_tag: Some(merge_tag),
328            } => {
329                write!(
330                    f,
331                    "Shared(inherit_async: {inherit_async}, merge_tag: {merge_tag})"
332                )
333            }
334            ChunkingType::Shared {
335                inherit_async,
336                merge_tag: None,
337            } => {
338                write!(f, "Shared(inherit_async: {inherit_async})")
339            }
340            ChunkingType::Traced => write!(f, "Traced"),
341        }
342    }
343}
344
345impl ChunkingType {
346    pub fn is_inherit_async(&self) -> bool {
347        matches!(
348            self,
349            ChunkingType::Parallel {
350                inherit_async: true,
351                ..
352            } | ChunkingType::Shared {
353                inherit_async: true,
354                ..
355            }
356        )
357    }
358
359    pub fn is_parallel(&self) -> bool {
360        matches!(self, ChunkingType::Parallel { .. })
361    }
362
363    pub fn is_merged(&self) -> bool {
364        matches!(
365            self,
366            ChunkingType::Isolated {
367                merge_tag: Some(_),
368                ..
369            } | ChunkingType::Shared {
370                merge_tag: Some(_),
371                ..
372            }
373        )
374    }
375
376    pub fn without_inherit_async(&self) -> Self {
377        match self {
378            ChunkingType::Parallel { hoisted, .. } => ChunkingType::Parallel {
379                hoisted: *hoisted,
380                inherit_async: false,
381            },
382            ChunkingType::Async => ChunkingType::Async,
383            ChunkingType::Isolated { _ty, merge_tag } => ChunkingType::Isolated {
384                _ty: *_ty,
385                merge_tag: merge_tag.clone(),
386            },
387            ChunkingType::Shared {
388                inherit_async: _,
389                merge_tag,
390            } => ChunkingType::Shared {
391                inherit_async: false,
392                merge_tag: merge_tag.clone(),
393            },
394            ChunkingType::Traced => ChunkingType::Traced,
395        }
396    }
397}
398
399#[turbo_tasks::value(transparent)]
400pub struct ChunkingTypeOption(Option<ChunkingType>);
401
402/// A [ModuleReference] implementing this trait and returning Some(_) for
403/// [ChunkableModuleReference::chunking_type] are considered as potentially
404/// chunkable references. When all [Module]s of such a reference implement
405/// [ChunkableModule] they are placed in [Chunk]s during chunking.
406/// They are even potentially placed in the same [Chunk] when a chunk type
407/// specific interface is implemented.
408#[turbo_tasks::value_trait]
409pub trait ChunkableModuleReference: ModuleReference + ValueToString {
410    #[turbo_tasks::function]
411    fn chunking_type(self: Vc<Self>) -> Vc<ChunkingTypeOption> {
412        Vc::cell(Some(ChunkingType::Parallel {
413            inherit_async: false,
414            hoisted: false,
415        }))
416    }
417
418    #[turbo_tasks::function]
419    fn export_usage(self: Vc<Self>) -> Vc<ExportUsage> {
420        ExportUsage::all()
421    }
422}
423
424pub struct ChunkGroupContent {
425    pub chunkable_items: Vec<ChunkableModuleOrBatch>,
426    pub batch_groups: Vec<ResolvedVc<ModuleBatchGroup>>,
427    pub async_modules: FxIndexSet<ResolvedVc<Box<dyn ChunkableModule>>>,
428    pub traced_modules: FxIndexSet<ResolvedVc<Box<dyn Module>>>,
429    pub availability_info: AvailabilityInfo,
430}
431
432#[turbo_tasks::value_trait]
433pub trait ChunkItem: OutputAssetsReference {
434    /// The [AssetIdent] of the [Module] that this [ChunkItem] was created from.
435    /// For most chunk types this must uniquely identify the chunk item at
436    /// runtime as it's the source of the module id used at runtime.
437    #[turbo_tasks::function]
438    fn asset_ident(self: Vc<Self>) -> Vc<AssetIdent>;
439
440    /// A [AssetIdent] that uniquely identifies the content of this [ChunkItem].
441    /// It is usually identical to [ChunkItem::asset_ident] but can be
442    /// different when the chunk item content depends on available modules e. g.
443    /// for chunk loaders.
444    #[turbo_tasks::function]
445    fn content_ident(self: Vc<Self>) -> Vc<AssetIdent> {
446        self.asset_ident()
447    }
448
449    /// The type of chunk this item should be assembled into.
450    #[turbo_tasks::function]
451    fn ty(self: Vc<Self>) -> Vc<Box<dyn ChunkType>>;
452
453    /// A temporary method to retrieve the module associated with this
454    /// ChunkItem. TODO: Remove this as part of the chunk refactoring.
455    #[turbo_tasks::function]
456    fn module(self: Vc<Self>) -> Vc<Box<dyn Module>>;
457
458    #[turbo_tasks::function]
459    fn chunking_context(self: Vc<Self>) -> Vc<Box<dyn ChunkingContext>>;
460}
461
462#[turbo_tasks::value_trait]
463pub trait ChunkType: ValueToString {
464    /// Whether the source (reference) order of items needs to be retained during chunking.
465    #[turbo_tasks::function]
466    fn is_style(self: Vc<Self>) -> Vc<bool>;
467
468    /// Create a new chunk for the given chunk items
469    #[turbo_tasks::function]
470    fn chunk(
471        &self,
472        chunking_context: Vc<Box<dyn ChunkingContext>>,
473        chunk_items: Vec<ChunkItemOrBatchWithAsyncModuleInfo>,
474        batch_groups: Vec<ResolvedVc<ChunkItemBatchGroup>>,
475    ) -> Vc<Box<dyn Chunk>>;
476
477    #[turbo_tasks::function]
478    fn chunk_item_size(
479        &self,
480        chunking_context: Vc<Box<dyn ChunkingContext>>,
481        chunk_item: Vc<Box<dyn ChunkItem>>,
482        async_module_info: Option<Vc<AsyncModuleInfo>>,
483    ) -> Vc<usize>;
484}
485
486pub fn round_chunk_item_size(size: usize) -> usize {
487    let a = size.next_power_of_two();
488    size & (a | (a >> 1) | (a >> 2))
489}
490
491#[turbo_tasks::value(transparent)]
492pub struct ChunkItems(pub Vec<ResolvedVc<Box<dyn ChunkItem>>>);
493
494#[turbo_tasks::value]
495pub struct AsyncModuleInfo {
496    pub referenced_async_modules: AutoSet<ResolvedVc<Box<dyn Module>>>,
497}
498
499#[turbo_tasks::value_impl]
500impl AsyncModuleInfo {
501    #[turbo_tasks::function]
502    pub fn new(referenced_async_modules: Vec<ResolvedVc<Box<dyn Module>>>) -> Result<Vc<Self>> {
503        Ok(Self {
504            referenced_async_modules: referenced_async_modules.into_iter().collect(),
505        }
506        .cell())
507    }
508}
509
510#[derive(
511    Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, TraceRawVcs, TaskInput, NonLocalValue,
512)]
513pub struct ChunkItemWithAsyncModuleInfo {
514    pub chunk_item: ResolvedVc<Box<dyn ChunkItem>>,
515    pub module: Option<ResolvedVc<Box<dyn ChunkableModule>>>,
516    pub async_info: Option<ResolvedVc<AsyncModuleInfo>>,
517}
518
519#[turbo_tasks::value(transparent)]
520pub struct ChunkItemsWithAsyncModuleInfo(Vec<ChunkItemWithAsyncModuleInfo>);
521
522pub trait ChunkItemExt {
523    /// Returns the module id of this chunk item.
524    fn id(self: Vc<Self>) -> Vc<ModuleId>;
525}
526
527impl<T> ChunkItemExt for T
528where
529    T: Upcast<Box<dyn ChunkItem>>,
530{
531    /// Returns the module id of this chunk item.
532    fn id(self: Vc<Self>) -> Vc<ModuleId> {
533        let chunk_item = Vc::upcast_non_strict(self);
534        chunk_item.chunking_context().chunk_item_id(chunk_item)
535    }
536}
537
538pub trait ModuleChunkItemIdExt {
539    /// Returns the chunk item id of this module.
540    fn chunk_item_id(
541        self: Vc<Self>,
542        chunking_context: Vc<Box<dyn ChunkingContext>>,
543    ) -> Vc<ModuleId>;
544}
545impl<T> ModuleChunkItemIdExt for T
546where
547    T: Upcast<Box<dyn Module>>,
548{
549    fn chunk_item_id(
550        self: Vc<Self>,
551        chunking_context: Vc<Box<dyn ChunkingContext>>,
552    ) -> Vc<ModuleId> {
553        chunking_context.chunk_item_id_from_module(Vc::upcast_non_strict(self))
554    }
555}
556
557#[cfg(test)]
558mod tests {
559    use super::*;
560
561    #[test]
562    fn test_round_chunk_item_size() {
563        assert_eq!(round_chunk_item_size(0), 0);
564        assert_eq!(round_chunk_item_size(1), 1);
565        assert_eq!(round_chunk_item_size(2), 2);
566        assert_eq!(round_chunk_item_size(3), 3);
567        assert_eq!(round_chunk_item_size(4), 4);
568        assert_eq!(round_chunk_item_size(5), 4);
569        assert_eq!(round_chunk_item_size(6), 6);
570        assert_eq!(round_chunk_item_size(7), 6);
571        assert_eq!(round_chunk_item_size(8), 8);
572        assert_eq!(round_chunk_item_size(49000), 32_768);
573        assert_eq!(round_chunk_item_size(50000), 49_152);
574
575        assert_eq!(changes_in_range(0..1000), 19);
576        assert_eq!(changes_in_range(1000..2000), 2);
577        assert_eq!(changes_in_range(2000..3000), 1);
578
579        assert_eq!(changes_in_range(3000..10000), 4);
580
581        fn changes_in_range(range: std::ops::Range<usize>) -> usize {
582            let len = range.len();
583            let mut count = 0;
584            for i in range {
585                let a = round_chunk_item_size(i);
586                assert!(a >= i * 2 / 3);
587                assert!(a <= i);
588                let b = round_chunk_item_size(i + 1);
589
590                if a == b {
591                    count += 1;
592                }
593            }
594            len - count
595        }
596    }
597}