Skip to main content

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