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    ident::AssetIdent,
40    module::Module,
41    module_graph::{
42        ModuleGraph,
43        module_batch::{ChunkableModuleOrBatch, ModuleBatchGroup},
44    },
45    output::OutputAssets,
46    reference::ModuleReference,
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)]
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
67#[turbo_tasks::value_impl]
68impl ValueToString for ModuleId {
69    #[turbo_tasks::function]
70    fn to_string(&self) -> Vc<RcStr> {
71        Vc::cell(self.to_string().into())
72    }
73}
74
75impl ModuleId {
76    pub fn parse(id: &str) -> Result<ModuleId> {
77        Ok(match id.parse::<u64>() {
78            Ok(i) => ModuleId::Number(i),
79            Err(_) => ModuleId::String(id.into()),
80        })
81    }
82}
83
84/// A list of module ids.
85#[turbo_tasks::value(transparent, shared)]
86pub struct ModuleIds(Vec<ResolvedVc<ModuleId>>);
87
88/// A [Module] that can be converted into a [Chunk].
89#[turbo_tasks::value_trait]
90pub trait ChunkableModule: Module + Asset {
91    fn as_chunk_item(
92        self: Vc<Self>,
93        module_graph: Vc<ModuleGraph>,
94        chunking_context: Vc<Box<dyn ChunkingContext>>,
95    ) -> Vc<Box<dyn ChunkItem>>;
96}
97
98#[turbo_tasks::value(transparent)]
99pub struct ChunkableModules(Vec<ResolvedVc<Box<dyn ChunkableModule>>>);
100
101#[turbo_tasks::value_impl]
102impl ChunkableModules {
103    #[turbo_tasks::function]
104    pub fn interned(modules: Vec<ResolvedVc<Box<dyn ChunkableModule>>>) -> Vc<Self> {
105        Vc::cell(modules)
106    }
107}
108
109#[turbo_tasks::value(transparent)]
110pub struct Chunks(Vec<ResolvedVc<Box<dyn Chunk>>>);
111
112#[turbo_tasks::value_impl]
113impl Chunks {
114    /// Creates a new empty [Vc<Chunks>].
115    #[turbo_tasks::function]
116    pub fn empty() -> Vc<Self> {
117        Vc::cell(vec![])
118    }
119}
120
121/// A [Chunk] group chunk items together into something that will become an [OutputAsset].
122/// It usually contains multiple chunk items.
123// TODO This could be simplified to and merged with [OutputChunk]
124#[turbo_tasks::value_trait]
125pub trait Chunk {
126    fn ident(self: Vc<Self>) -> Vc<AssetIdent>;
127    fn chunking_context(self: Vc<Self>) -> Vc<Box<dyn ChunkingContext>>;
128    // fn path(self: Vc<Self>) -> Vc<FileSystemPath> {
129    //     self.ident().path()
130    // }
131
132    /// Other [OutputAsset]s referenced from this [Chunk].
133    fn references(self: Vc<Self>) -> Vc<OutputAssets> {
134        OutputAssets::empty()
135    }
136
137    fn chunk_items(self: Vc<Self>) -> Vc<ChunkItems> {
138        ChunkItems(vec![]).cell()
139    }
140}
141
142/// Aggregated information about a chunk content that can be used by the runtime
143/// code to optimize chunk loading.
144#[turbo_tasks::value(shared)]
145#[derive(Default)]
146pub struct OutputChunkRuntimeInfo {
147    pub included_ids: Option<ResolvedVc<ModuleIds>>,
148    pub excluded_ids: Option<ResolvedVc<ModuleIds>>,
149    /// List of paths of chunks containing individual modules that are part of
150    /// this chunk. This is useful for selectively loading modules from a chunk
151    /// without loading the whole chunk.
152    pub module_chunks: Option<ResolvedVc<OutputAssets>>,
153    pub placeholder_for_future_extensions: (),
154}
155
156#[turbo_tasks::value_trait]
157pub trait OutputChunk: Asset {
158    fn runtime_info(self: Vc<Self>) -> Vc<OutputChunkRuntimeInfo>;
159}
160
161/// Specifies how a chunk interacts with other chunks when building a chunk
162/// group
163#[derive(
164    Debug,
165    Clone,
166    Hash,
167    TraceRawVcs,
168    Serialize,
169    Deserialize,
170    Eq,
171    PartialEq,
172    ValueDebugFormat,
173    NonLocalValue,
174)]
175pub enum ChunkingType {
176    /// The referenced module is placed in the same chunk group and is loaded in parallel.
177    Parallel {
178        /// Whether the parent module becomes an async module when the referenced module is async.
179        /// This should happen for e.g. ESM imports, but not for CommonJS requires.
180        inherit_async: bool,
181        /// Whether the referenced module is executed always immediately before the parent module
182        /// (corresponding to ESM import semantics).
183        hoisted: bool,
184    },
185    /// An async loader is placed into the referencing chunk and loads the
186    /// separate chunk group in which the module is placed.
187    Async,
188    /// Create a new chunk group in a separate context, merging references with the same tag into a
189    /// single chunk group. It does not inherit the available modules from the parent.
190    // TODO this is currently skipped in chunking
191    Isolated {
192        _ty: ChunkGroupType,
193        merge_tag: Option<RcStr>,
194    },
195    /// Create a new chunk group in a separate context, merging references with the same tag into a
196    /// single chunk group. It provides available modules to the current chunk group. It's assumed
197    /// to be loaded before the current chunk group.
198    Shared {
199        inherit_async: bool,
200        merge_tag: Option<RcStr>,
201    },
202    // Module not placed in chunk group, but its references are still followed.
203    Traced,
204}
205
206impl ChunkingType {
207    pub fn is_inherit_async(&self) -> bool {
208        matches!(
209            self,
210            ChunkingType::Parallel {
211                inherit_async: true,
212                ..
213            } | ChunkingType::Shared {
214                inherit_async: true,
215                ..
216            }
217        )
218    }
219
220    pub fn is_parallel(&self) -> bool {
221        matches!(self, ChunkingType::Parallel { .. })
222    }
223
224    pub fn is_merged(&self) -> bool {
225        matches!(
226            self,
227            ChunkingType::Isolated {
228                merge_tag: Some(_),
229                ..
230            } | ChunkingType::Shared {
231                merge_tag: Some(_),
232                ..
233            }
234        )
235    }
236
237    pub fn without_inherit_async(&self) -> Self {
238        match self {
239            ChunkingType::Parallel { hoisted, .. } => ChunkingType::Parallel {
240                hoisted: *hoisted,
241                inherit_async: false,
242            },
243            ChunkingType::Async => ChunkingType::Async,
244            ChunkingType::Isolated { _ty, merge_tag } => ChunkingType::Isolated {
245                _ty: *_ty,
246                merge_tag: merge_tag.clone(),
247            },
248            ChunkingType::Shared {
249                inherit_async: _,
250                merge_tag,
251            } => ChunkingType::Shared {
252                inherit_async: false,
253                merge_tag: merge_tag.clone(),
254            },
255            ChunkingType::Traced => ChunkingType::Traced,
256        }
257    }
258}
259
260#[turbo_tasks::value(transparent)]
261pub struct ChunkingTypeOption(Option<ChunkingType>);
262
263/// A [ModuleReference] implementing this trait and returning Some(_) for
264/// [ChunkableModuleReference::chunking_type] are considered as potentially
265/// chunkable references. When all [Module]s of such a reference implement
266/// [ChunkableModule] they are placed in [Chunk]s during chunking.
267/// They are even potentially placed in the same [Chunk] when a chunk type
268/// specific interface is implemented.
269#[turbo_tasks::value_trait]
270pub trait ChunkableModuleReference: ModuleReference + ValueToString {
271    fn chunking_type(self: Vc<Self>) -> Vc<ChunkingTypeOption> {
272        Vc::cell(Some(ChunkingType::Parallel {
273            inherit_async: false,
274            hoisted: false,
275        }))
276    }
277}
278
279#[derive(Default)]
280pub struct ChunkGroupContent {
281    pub chunkable_items: FxIndexSet<ChunkableModuleOrBatch>,
282    pub batch_groups: FxIndexSet<ResolvedVc<ModuleBatchGroup>>,
283    pub async_modules: FxIndexSet<ResolvedVc<Box<dyn ChunkableModule>>>,
284    pub traced_modules: FxIndexSet<ResolvedVc<Box<dyn Module>>>,
285}
286
287#[turbo_tasks::value_trait]
288pub trait ChunkItem {
289    /// The [AssetIdent] of the [Module] that this [ChunkItem] was created from.
290    /// For most chunk types this must uniquely identify the chunk item at
291    /// runtime as it's the source of the module id used at runtime.
292    fn asset_ident(self: Vc<Self>) -> Vc<AssetIdent>;
293    /// A [AssetIdent] that uniquely identifies the content of this [ChunkItem].
294    /// It is unusally identical to [ChunkItem::asset_ident] but can be
295    /// different when the chunk item content depends on available modules e. g.
296    /// for chunk loaders.
297    fn content_ident(self: Vc<Self>) -> Vc<AssetIdent> {
298        self.asset_ident()
299    }
300    /// A [ChunkItem] can reference OutputAssets, unlike [Module]s referencing other [Module]s.
301    fn references(self: Vc<Self>) -> Vc<OutputAssets> {
302        OutputAssets::empty()
303    }
304
305    /// The type of chunk this item should be assembled into.
306    fn ty(self: Vc<Self>) -> Vc<Box<dyn ChunkType>>;
307
308    /// A temporary method to retrieve the module associated with this
309    /// ChunkItem. TODO: Remove this as part of the chunk refactoring.
310    fn module(self: Vc<Self>) -> Vc<Box<dyn Module>>;
311
312    fn chunking_context(self: Vc<Self>) -> Vc<Box<dyn ChunkingContext>>;
313}
314
315#[turbo_tasks::value_trait]
316pub trait ChunkType: ValueToString {
317    /// Whether the source (reference) order of items needs to be retained during chunking.
318    fn is_style(self: Vc<Self>) -> Vc<bool>;
319
320    /// Create a new chunk for the given chunk items
321    fn chunk(
322        &self,
323        chunking_context: Vc<Box<dyn ChunkingContext>>,
324        chunk_items: Vec<ChunkItemOrBatchWithAsyncModuleInfo>,
325        batch_groups: Vec<ResolvedVc<ChunkItemBatchGroup>>,
326        referenced_output_assets: Vc<OutputAssets>,
327    ) -> Vc<Box<dyn Chunk>>;
328
329    fn chunk_item_size(
330        &self,
331        chunking_context: Vc<Box<dyn ChunkingContext>>,
332        chunk_item: Vc<Box<dyn ChunkItem>>,
333        async_module_info: Option<Vc<AsyncModuleInfo>>,
334    ) -> Vc<usize>;
335}
336
337pub fn round_chunk_item_size(size: usize) -> usize {
338    let a = size.next_power_of_two();
339    size & (a | (a >> 1) | (a >> 2))
340}
341
342#[turbo_tasks::value(transparent)]
343pub struct ChunkItems(pub Vec<ResolvedVc<Box<dyn ChunkItem>>>);
344
345#[turbo_tasks::value]
346pub struct AsyncModuleInfo {
347    pub referenced_async_modules: AutoSet<ResolvedVc<Box<dyn Module>>>,
348}
349
350#[turbo_tasks::value_impl]
351impl AsyncModuleInfo {
352    #[turbo_tasks::function]
353    pub async fn new(
354        referenced_async_modules: Vec<ResolvedVc<Box<dyn Module>>>,
355    ) -> Result<Vc<Self>> {
356        Ok(Self {
357            referenced_async_modules: referenced_async_modules.into_iter().collect(),
358        }
359        .cell())
360    }
361}
362
363#[derive(
364    Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, TraceRawVcs, TaskInput, NonLocalValue,
365)]
366pub struct ChunkItemWithAsyncModuleInfo {
367    pub chunk_item: ResolvedVc<Box<dyn ChunkItem>>,
368    pub module: Option<ResolvedVc<Box<dyn ChunkableModule>>>,
369    pub async_info: Option<ResolvedVc<AsyncModuleInfo>>,
370}
371
372#[turbo_tasks::value(transparent)]
373pub struct ChunkItemsWithAsyncModuleInfo(Vec<ChunkItemWithAsyncModuleInfo>);
374
375pub trait ChunkItemExt {
376    /// Returns the module id of this chunk item.
377    fn id(self: Vc<Self>) -> Vc<ModuleId>;
378}
379
380impl<T> ChunkItemExt for T
381where
382    T: Upcast<Box<dyn ChunkItem>>,
383{
384    /// Returns the module id of this chunk item.
385    fn id(self: Vc<Self>) -> Vc<ModuleId> {
386        let chunk_item = Vc::upcast(self);
387        chunk_item.chunking_context().chunk_item_id(chunk_item)
388    }
389}
390
391pub trait ModuleChunkItemIdExt {
392    /// Returns the chunk item id of this module.
393    fn chunk_item_id(
394        self: Vc<Self>,
395        chunking_context: Vc<Box<dyn ChunkingContext>>,
396    ) -> Vc<ModuleId>;
397}
398impl<T> ModuleChunkItemIdExt for T
399where
400    T: Upcast<Box<dyn Module>>,
401{
402    fn chunk_item_id(
403        self: Vc<Self>,
404        chunking_context: Vc<Box<dyn ChunkingContext>>,
405    ) -> Vc<ModuleId> {
406        chunking_context.chunk_item_id_from_module(Vc::upcast(self))
407    }
408}
409
410#[cfg(test)]
411mod tests {
412    use super::*;
413
414    #[test]
415    fn test_round_chunk_item_size() {
416        assert_eq!(round_chunk_item_size(0), 0);
417        assert_eq!(round_chunk_item_size(1), 1);
418        assert_eq!(round_chunk_item_size(2), 2);
419        assert_eq!(round_chunk_item_size(3), 3);
420        assert_eq!(round_chunk_item_size(4), 4);
421        assert_eq!(round_chunk_item_size(5), 4);
422        assert_eq!(round_chunk_item_size(6), 6);
423        assert_eq!(round_chunk_item_size(7), 6);
424        assert_eq!(round_chunk_item_size(8), 8);
425        assert_eq!(round_chunk_item_size(49000), 32_768);
426        assert_eq!(round_chunk_item_size(50000), 49_152);
427
428        assert_eq!(changes_in_range(0..1000), 19);
429        assert_eq!(changes_in_range(1000..2000), 2);
430        assert_eq!(changes_in_range(2000..3000), 1);
431
432        assert_eq!(changes_in_range(3000..10000), 4);
433
434        fn changes_in_range(range: std::ops::Range<usize>) -> usize {
435            let len = range.len();
436            let mut count = 0;
437            for i in range {
438                let a = round_chunk_item_size(i);
439                assert!(a >= i * 2 / 3);
440                assert!(a <= i);
441                let b = round_chunk_item_size(i + 1);
442
443                if a == b {
444                    count += 1;
445                }
446            }
447            len - count
448        }
449    }
450}