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