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