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