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;
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        ChunkGroupResult, ChunkGroupType, ChunkingConfig, ChunkingConfigs, ChunkingContext,
31        ChunkingContextExt, EntryChunkGroupResult, MangleType, MinifyType, SourceMapSourceType,
32        SourceMapsType, UnusedReferences,
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    reference::ModuleReference,
48    resolve::BindingUsage,
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, Serialize)]
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<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, Clone, Debug, PartialEq, Eq, TraceRawVcs, NonLocalValue, TaskInput, Hash, Encode, Decode,
151)]
152pub enum MergeableModuleExposure {
153    // This module is only used from within the current group, and only individual exports are
154    // used (and no namespace object is required).
155    None,
156    // This module is only used from within the current group, and but the namespace object is
157    // needed.
158    Internal,
159    // The exports of this module are read from outside this group (necessitating a namespace
160    // object anyway).
161    External,
162}
163
164#[turbo_tasks::value(transparent)]
165pub struct MergeableModulesExposed(
166    Vec<(
167        ResolvedVc<Box<dyn MergeableModule>>,
168        MergeableModuleExposure,
169    )>,
170);
171
172#[turbo_tasks::value_impl]
173impl MergeableModulesExposed {
174    #[turbo_tasks::function]
175    pub fn interned(
176        modules: Vec<(
177            ResolvedVc<Box<dyn MergeableModule>>,
178            MergeableModuleExposure,
179        )>,
180    ) -> Vc<Self> {
181        Vc::cell(modules)
182    }
183}
184
185#[turbo_tasks::value(transparent)]
186pub struct Chunks(Vec<ResolvedVc<Box<dyn Chunk>>>);
187
188#[turbo_tasks::value_impl]
189impl Chunks {
190    /// Creates a new empty [Vc<Chunks>].
191    #[turbo_tasks::function]
192    pub fn empty() -> Vc<Self> {
193        Vc::cell(vec![])
194    }
195}
196
197/// A [Chunk] group chunk items together into something that will become an [OutputAsset].
198/// It usually contains multiple chunk items.
199// TODO This could be simplified to and merged with [OutputChunk]
200#[turbo_tasks::value_trait]
201pub trait Chunk: OutputAssetsReference {
202    #[turbo_tasks::function]
203    fn ident(self: Vc<Self>) -> Vc<AssetIdent>;
204    #[turbo_tasks::function]
205    fn chunking_context(self: Vc<Self>) -> Vc<Box<dyn ChunkingContext>>;
206    // fn path(self: Vc<Self>) -> Vc<FileSystemPath> {
207    //     self.ident().path()
208    // }
209
210    #[turbo_tasks::function]
211    fn chunk_items(self: Vc<Self>) -> Vc<ChunkItems> {
212        ChunkItems(vec![]).cell()
213    }
214}
215
216/// Aggregated information about a chunk content that can be used by the runtime
217/// code to optimize chunk loading.
218#[turbo_tasks::value(shared)]
219#[derive(Default)]
220pub struct OutputChunkRuntimeInfo {
221    pub included_ids: Option<ResolvedVc<ModuleIds>>,
222    pub excluded_ids: Option<ResolvedVc<ModuleIds>>,
223    /// List of paths of chunks containing individual modules that are part of
224    /// this chunk. This is useful for selectively loading modules from a chunk
225    /// without loading the whole chunk.
226    pub module_chunks: Option<ResolvedVc<OutputAssets>>,
227    pub placeholder_for_future_extensions: (),
228}
229
230#[turbo_tasks::value_impl]
231impl OutputChunkRuntimeInfo {
232    #[turbo_tasks::function]
233    pub fn empty() -> Vc<Self> {
234        Self::default().cell()
235    }
236}
237
238#[turbo_tasks::value_trait]
239pub trait OutputChunk: Asset {
240    #[turbo_tasks::function]
241    fn runtime_info(self: Vc<Self>) -> Vc<OutputChunkRuntimeInfo>;
242}
243
244/// Specifies how a chunk interacts with other chunks when building a chunk
245/// group
246#[derive(
247    Debug,
248    Clone,
249    Hash,
250    TraceRawVcs,
251    Serialize,
252    Deserialize,
253    Eq,
254    PartialEq,
255    ValueDebugFormat,
256    NonLocalValue,
257    Encode,
258    Decode,
259)]
260pub enum ChunkingType {
261    /// The referenced module is placed in the same chunk group and is loaded in parallel.
262    Parallel {
263        /// Whether the parent module becomes an async module when the referenced module is async.
264        /// This should happen for e.g. ESM imports, but not for CommonJS requires.
265        inherit_async: bool,
266        /// Whether the referenced module is executed always immediately before the parent module
267        /// (corresponding to ESM import semantics).
268        hoisted: bool,
269    },
270    /// An async loader is placed into the referencing chunk and loads the
271    /// separate chunk group in which the module is placed.
272    Async,
273    /// Create a new chunk group in a separate context, merging references with the same tag into a
274    /// single chunk group. It does not inherit the available modules from the parent.
275    // TODO this is currently skipped in chunking
276    Isolated {
277        _ty: ChunkGroupType,
278        merge_tag: Option<RcStr>,
279    },
280    /// Create a new chunk group in a separate context, merging references with the same tag into a
281    /// single chunk group. It provides available modules to the current chunk group. It's assumed
282    /// to be loaded before the current chunk group.
283    Shared {
284        inherit_async: bool,
285        merge_tag: Option<RcStr>,
286    },
287    // Module not placed in chunk group, but its references are still followed.
288    Traced,
289}
290
291impl Display for ChunkingType {
292    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
293        match self {
294            ChunkingType::Parallel {
295                inherit_async,
296                hoisted,
297            } => {
298                write!(
299                    f,
300                    "Parallel(inherit_async: {inherit_async}, hoisted: {hoisted})",
301                )
302            }
303            ChunkingType::Async => write!(f, "Async"),
304            ChunkingType::Isolated {
305                _ty,
306                merge_tag: Some(merge_tag),
307            } => {
308                write!(f, "Isolated(merge_tag: {merge_tag})")
309            }
310            ChunkingType::Isolated {
311                _ty,
312                merge_tag: None,
313            } => {
314                write!(f, "Isolated")
315            }
316            ChunkingType::Shared {
317                inherit_async,
318                merge_tag: Some(merge_tag),
319            } => {
320                write!(
321                    f,
322                    "Shared(inherit_async: {inherit_async}, merge_tag: {merge_tag})"
323                )
324            }
325            ChunkingType::Shared {
326                inherit_async,
327                merge_tag: None,
328            } => {
329                write!(f, "Shared(inherit_async: {inherit_async})")
330            }
331            ChunkingType::Traced => write!(f, "Traced"),
332        }
333    }
334}
335
336impl ChunkingType {
337    pub fn is_inherit_async(&self) -> bool {
338        matches!(
339            self,
340            ChunkingType::Parallel {
341                inherit_async: true,
342                ..
343            } | ChunkingType::Shared {
344                inherit_async: true,
345                ..
346            }
347        )
348    }
349
350    pub fn is_parallel(&self) -> bool {
351        matches!(self, ChunkingType::Parallel { .. })
352    }
353
354    pub fn is_merged(&self) -> bool {
355        matches!(
356            self,
357            ChunkingType::Isolated {
358                merge_tag: Some(_),
359                ..
360            } | ChunkingType::Shared {
361                merge_tag: Some(_),
362                ..
363            }
364        )
365    }
366
367    pub fn without_inherit_async(&self) -> Self {
368        match self {
369            ChunkingType::Parallel { hoisted, .. } => ChunkingType::Parallel {
370                hoisted: *hoisted,
371                inherit_async: false,
372            },
373            ChunkingType::Async => ChunkingType::Async,
374            ChunkingType::Isolated { _ty, merge_tag } => ChunkingType::Isolated {
375                _ty: *_ty,
376                merge_tag: merge_tag.clone(),
377            },
378            ChunkingType::Shared {
379                inherit_async: _,
380                merge_tag,
381            } => ChunkingType::Shared {
382                inherit_async: false,
383                merge_tag: merge_tag.clone(),
384            },
385            ChunkingType::Traced => ChunkingType::Traced,
386        }
387    }
388}
389
390#[turbo_tasks::value(transparent)]
391pub struct ChunkingTypeOption(Option<ChunkingType>);
392
393/// A [ModuleReference] implementing this trait and returning Some(_) for
394/// [ChunkableModuleReference::chunking_type] are considered as potentially
395/// chunkable references. When all [Module]s of such a reference implement
396/// [ChunkableModule] they are placed in [Chunk]s during chunking.
397/// They are even potentially placed in the same [Chunk] when a chunk type
398/// specific interface is implemented.
399#[turbo_tasks::value_trait]
400pub trait ChunkableModuleReference: ModuleReference + ValueToString {
401    #[turbo_tasks::function]
402    fn chunking_type(self: Vc<Self>) -> Vc<ChunkingTypeOption> {
403        Vc::cell(Some(ChunkingType::Parallel {
404            inherit_async: false,
405            hoisted: false,
406        }))
407    }
408
409    #[turbo_tasks::function]
410    fn binding_usage(self: Vc<Self>) -> Vc<BindingUsage> {
411        BindingUsage::all()
412    }
413}
414
415pub struct ChunkGroupContent {
416    pub chunkable_items: Vec<ChunkableModuleOrBatch>,
417    pub batch_groups: Vec<ResolvedVc<ModuleBatchGroup>>,
418    pub async_modules: FxIndexSet<ResolvedVc<Box<dyn ChunkableModule>>>,
419    pub traced_modules: FxIndexSet<ResolvedVc<Box<dyn Module>>>,
420    pub availability_info: AvailabilityInfo,
421}
422
423#[turbo_tasks::value_trait]
424pub trait ChunkItem: OutputAssetsReference {
425    /// The [AssetIdent] of the [Module] that this [ChunkItem] was created from.
426    /// For most chunk types this must uniquely identify the chunk item at
427    /// runtime as it's the source of the module id used at runtime.
428    #[turbo_tasks::function]
429    fn asset_ident(self: Vc<Self>) -> Vc<AssetIdent>;
430
431    /// A [AssetIdent] that uniquely identifies the content of this [ChunkItem].
432    /// It is usually identical to [ChunkItem::asset_ident] but can be
433    /// different when the chunk item content depends on available modules e. g.
434    /// for chunk loaders.
435    #[turbo_tasks::function]
436    fn content_ident(self: Vc<Self>) -> Vc<AssetIdent> {
437        self.asset_ident()
438    }
439
440    /// The type of chunk this item should be assembled into.
441    #[turbo_tasks::function]
442    fn ty(self: Vc<Self>) -> Vc<Box<dyn ChunkType>>;
443
444    /// A temporary method to retrieve the module associated with this
445    /// ChunkItem. TODO: Remove this as part of the chunk refactoring.
446    #[turbo_tasks::function]
447    fn module(self: Vc<Self>) -> Vc<Box<dyn Module>>;
448
449    #[turbo_tasks::function]
450    fn chunking_context(self: Vc<Self>) -> Vc<Box<dyn ChunkingContext>>;
451}
452
453#[turbo_tasks::value_trait]
454pub trait ChunkType: ValueToString {
455    /// Whether the source (reference) order of items needs to be retained during chunking.
456    #[turbo_tasks::function]
457    fn is_style(self: Vc<Self>) -> Vc<bool>;
458
459    /// Create a new chunk for the given chunk items
460    #[turbo_tasks::function]
461    fn chunk(
462        &self,
463        chunking_context: Vc<Box<dyn ChunkingContext>>,
464        chunk_items: Vec<ChunkItemOrBatchWithAsyncModuleInfo>,
465        batch_groups: Vec<ResolvedVc<ChunkItemBatchGroup>>,
466    ) -> Vc<Box<dyn Chunk>>;
467
468    #[turbo_tasks::function]
469    fn chunk_item_size(
470        &self,
471        chunking_context: Vc<Box<dyn ChunkingContext>>,
472        chunk_item: Vc<Box<dyn ChunkItem>>,
473        async_module_info: Option<Vc<AsyncModuleInfo>>,
474    ) -> Vc<usize>;
475}
476
477pub fn round_chunk_item_size(size: usize) -> usize {
478    let a = size.next_power_of_two();
479    size & (a | (a >> 1) | (a >> 2))
480}
481
482#[turbo_tasks::value(transparent)]
483pub struct ChunkItems(pub Vec<ResolvedVc<Box<dyn ChunkItem>>>);
484
485#[turbo_tasks::value]
486pub struct AsyncModuleInfo {
487    pub referenced_async_modules: AutoSet<ResolvedVc<Box<dyn Module>>>,
488}
489
490#[turbo_tasks::value_impl]
491impl AsyncModuleInfo {
492    #[turbo_tasks::function]
493    pub fn new(referenced_async_modules: Vec<ResolvedVc<Box<dyn Module>>>) -> Result<Vc<Self>> {
494        Ok(Self {
495            referenced_async_modules: referenced_async_modules.into_iter().collect(),
496        }
497        .cell())
498    }
499}
500
501#[derive(
502    Debug, Clone, PartialEq, Eq, Hash, TraceRawVcs, TaskInput, NonLocalValue, Encode, Decode,
503)]
504pub struct ChunkItemWithAsyncModuleInfo {
505    pub chunk_item: ResolvedVc<Box<dyn ChunkItem>>,
506    pub module: Option<ResolvedVc<Box<dyn ChunkableModule>>>,
507    pub async_info: Option<ResolvedVc<AsyncModuleInfo>>,
508}
509
510#[turbo_tasks::value(transparent)]
511pub struct ChunkItemsWithAsyncModuleInfo(Vec<ChunkItemWithAsyncModuleInfo>);
512
513pub trait ChunkItemExt {
514    /// Returns the module id of this chunk item.
515    fn id(self: Vc<Self>) -> impl Future<Output = Result<ModuleId>> + Send;
516}
517
518impl<T> ChunkItemExt for T
519where
520    T: Upcast<Box<dyn ChunkItem>> + Send,
521{
522    /// Returns the module id of this chunk item.
523    async fn id(self: Vc<Self>) -> Result<ModuleId> {
524        let chunk_item = Vc::upcast_non_strict(self);
525        chunk_item
526            .chunking_context()
527            .chunk_item_id_strategy()
528            .await?
529            .get_id(chunk_item)
530            .await
531    }
532}
533
534pub trait ModuleChunkItemIdExt {
535    /// Returns the chunk item id of this module.
536    fn chunk_item_id(
537        self: Vc<Self>,
538        chunking_context: Vc<Box<dyn ChunkingContext>>,
539    ) -> impl Future<Output = Result<ModuleId>> + Send;
540}
541impl<T> ModuleChunkItemIdExt for T
542where
543    T: Upcast<Box<dyn Module>> + Send,
544{
545    async fn chunk_item_id(
546        self: Vc<Self>,
547        chunking_context: Vc<Box<dyn ChunkingContext>>,
548    ) -> Result<ModuleId> {
549        chunking_context
550            .chunk_item_id_strategy()
551            .await?
552            .get_id_from_module(Vc::upcast_non_strict(self))
553            .await
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}