turbopack_core/chunk/
chunking_context.rs

1use anyhow::{Result, bail};
2use rustc_hash::FxHashMap;
3use serde::{Deserialize, Serialize};
4use turbo_rcstr::RcStr;
5use turbo_tasks::{NonLocalValue, ResolvedVc, TaskInput, Upcast, Vc, trace::TraceRawVcs};
6use turbo_tasks_fs::FileSystemPath;
7use turbo_tasks_hash::DeterministicHash;
8
9use super::{ChunkableModule, EvaluatableAssets, availability_info::AvailabilityInfo};
10use crate::{
11    asset::Asset,
12    chunk::{ChunkItem, ChunkType, ModuleId},
13    environment::Environment,
14    ident::AssetIdent,
15    module::Module,
16    module_graph::{
17        ModuleGraph, chunk_group_info::ChunkGroup, export_usage::ModuleExportUsage,
18        module_batches::BatchingConfig,
19    },
20    output::{OutputAsset, OutputAssets, OutputAssetsWithReferenced},
21};
22
23#[derive(
24    Debug,
25    TaskInput,
26    Clone,
27    Copy,
28    PartialEq,
29    Eq,
30    Hash,
31    Serialize,
32    Deserialize,
33    TraceRawVcs,
34    DeterministicHash,
35    NonLocalValue,
36)]
37#[serde(rename_all = "kebab-case")]
38pub enum MangleType {
39    OptimalSize,
40    Deterministic,
41}
42
43#[turbo_tasks::value(shared)]
44#[derive(Debug, TaskInput, Clone, Copy, Hash, DeterministicHash)]
45pub enum MinifyType {
46    // TODO instead of adding a new property here,
47    // refactor that to Minify(MinifyOptions) to allow defaults on MinifyOptions
48    Minify { mangle: Option<MangleType> },
49    NoMinify,
50}
51
52impl Default for MinifyType {
53    fn default() -> Self {
54        Self::Minify {
55            mangle: Some(MangleType::OptimalSize),
56        }
57    }
58}
59
60#[turbo_tasks::value(shared)]
61#[derive(Debug, Default, TaskInput, Clone, Copy, Hash, DeterministicHash)]
62pub enum SourceMapsType {
63    /// Extracts source maps from input files and writes source maps for output files.
64    #[default]
65    Full,
66    /// Ignores the existence of source maps and does not write source maps for output files.
67    None,
68}
69
70#[derive(
71    Debug,
72    TaskInput,
73    Clone,
74    Copy,
75    PartialEq,
76    Eq,
77    Hash,
78    Serialize,
79    Deserialize,
80    TraceRawVcs,
81    DeterministicHash,
82    NonLocalValue,
83)]
84pub enum ChunkGroupType {
85    Entry,
86    Evaluated,
87}
88
89#[turbo_tasks::value(shared)]
90#[derive(Clone, Copy)]
91pub struct ChunkGroupResult {
92    pub assets: ResolvedVc<OutputAssets>,
93    pub referenced_assets: ResolvedVc<OutputAssets>,
94    pub availability_info: AvailabilityInfo,
95}
96
97#[turbo_tasks::value(shared)]
98pub struct EntryChunkGroupResult {
99    pub asset: ResolvedVc<Box<dyn OutputAsset>>,
100    pub availability_info: AvailabilityInfo,
101}
102
103#[derive(
104    Default,
105    Debug,
106    Clone,
107    PartialEq,
108    Eq,
109    Hash,
110    Serialize,
111    Deserialize,
112    TraceRawVcs,
113    NonLocalValue,
114    TaskInput,
115)]
116pub struct ChunkingConfig {
117    /// Try to avoid creating more than 1 chunk smaller than this size.
118    /// It merges multiple small chunks into bigger ones to avoid that.
119    pub min_chunk_size: usize,
120
121    /// Try to avoid creating more than this number of chunks per group.
122    /// It merges multiple chunks into bigger ones to avoid that.
123    pub max_chunk_count_per_group: usize,
124
125    /// Never merges chunks bigger than this size with other chunks.
126    /// This makes sure that code in big chunks is not duplicated in multiple chunks.
127    pub max_merge_chunk_size: usize,
128
129    #[allow(dead_code)]
130    pub placeholder_for_future_extensions: (),
131}
132
133#[turbo_tasks::value(transparent)]
134pub struct ChunkingConfigs(FxHashMap<ResolvedVc<Box<dyn ChunkType>>, ChunkingConfig>);
135
136#[turbo_tasks::value(shared)]
137#[derive(Debug, Clone, Copy, Hash, TaskInput, Default)]
138pub enum SourceMapSourceType {
139    AbsoluteFileUri,
140    RelativeUri,
141    #[default]
142    TurbopackUri,
143}
144
145/// A context for the chunking that influences the way chunks are created
146#[turbo_tasks::value_trait]
147pub trait ChunkingContext {
148    #[turbo_tasks::function]
149    fn name(self: Vc<Self>) -> Vc<RcStr>;
150    #[turbo_tasks::function]
151    fn source_map_source_type(self: Vc<Self>) -> Vc<SourceMapSourceType>;
152    /// The root path of the project
153    #[turbo_tasks::function]
154    fn root_path(self: Vc<Self>) -> Vc<FileSystemPath>;
155    /// The output root path in the output filesystem
156    #[turbo_tasks::function]
157    fn output_root(self: Vc<Self>) -> Vc<FileSystemPath>;
158    /// A relative path how to reach the root path from the output root. This is used to compute
159    /// original paths at runtime relative to the output files. e. g. import.meta.url needs that.
160    #[turbo_tasks::function]
161    fn output_root_to_root_path(self: Vc<Self>) -> Vc<RcStr>;
162
163    // TODO remove this, a chunking context should not be bound to a specific
164    // environment since this can change due to transitions in the module graph
165    #[turbo_tasks::function]
166    fn environment(self: Vc<Self>) -> Vc<Environment>;
167
168    /// The path to the folder where all chunks are placed. This can be used to compute relative
169    /// paths.
170    #[turbo_tasks::function]
171    fn chunk_root_path(self: Vc<Self>) -> Vc<FileSystemPath>;
172
173    // TODO(alexkirsz) Remove this from the chunking context. This should be at the
174    // discretion of chunking context implementors. However, we currently use this
175    // in a couple of places in `turbopack-css`, so we need to remove that
176    // dependency first.
177    #[turbo_tasks::function]
178    fn chunk_path(
179        self: Vc<Self>,
180        asset: Option<Vc<Box<dyn Asset>>>,
181        ident: Vc<AssetIdent>,
182        content_hashing_prefix: Option<RcStr>,
183        extension: RcStr,
184    ) -> Vc<FileSystemPath>;
185
186    /// Reference Source Map Assets for chunks
187    #[turbo_tasks::function]
188    fn reference_chunk_source_maps(self: Vc<Self>, chunk: Vc<Box<dyn OutputAsset>>) -> Vc<bool>;
189
190    /// Include Source Maps for modules
191    #[turbo_tasks::function]
192    fn reference_module_source_maps(self: Vc<Self>, module: Vc<Box<dyn Module>>) -> Vc<bool>;
193
194    /// Returns a URL (relative or absolute, depending on the asset prefix) to
195    /// the static asset based on its `ident`.
196    /// The `tag` is an arbitrary string that can be used to distinguish
197    /// different usages of the same asset (e.g. different base paths).
198    #[turbo_tasks::function]
199    fn asset_url(self: Vc<Self>, ident: FileSystemPath, tag: Option<RcStr>) -> Result<Vc<RcStr>>;
200
201    #[turbo_tasks::function]
202    fn asset_path(
203        self: Vc<Self>,
204        content_hash: RcStr,
205        original_asset_ident: Vc<AssetIdent>,
206        tag: Option<RcStr>,
207    ) -> Vc<FileSystemPath>;
208
209    #[turbo_tasks::function]
210    fn is_hot_module_replacement_enabled(self: Vc<Self>) -> Vc<bool> {
211        Vc::cell(false)
212    }
213
214    #[turbo_tasks::function]
215    fn chunking_configs(self: Vc<Self>) -> Vc<ChunkingConfigs> {
216        Vc::cell(Default::default())
217    }
218
219    #[turbo_tasks::function]
220    fn batching_config(self: Vc<Self>) -> Vc<BatchingConfig> {
221        BatchingConfig::new(BatchingConfig {
222            ..Default::default()
223        })
224    }
225
226    /// Whether `ChunkingType::Traced` are used to create corresponding output assets for each
227    /// traced module.
228    #[turbo_tasks::function]
229    fn is_tracing_enabled(self: Vc<Self>) -> Vc<bool> {
230        Vc::cell(false)
231    }
232
233    /// Whether to use `MergeableModule` to merge modules if possible.
234    #[turbo_tasks::function]
235    fn is_module_merging_enabled(self: Vc<Self>) -> Vc<bool> {
236        Vc::cell(false)
237    }
238
239    /// Whether to include information about the content of the chunk into the runtime, to allow
240    /// more incremental loading of individual chunk items.
241    #[turbo_tasks::function]
242    fn is_dynamic_chunk_content_loading_enabled(self: Vc<Self>) -> Vc<bool> {
243        Vc::cell(false)
244    }
245
246    #[turbo_tasks::function]
247    fn minify_type(self: Vc<Self>) -> Vc<MinifyType> {
248        MinifyType::NoMinify.cell()
249    }
250
251    #[turbo_tasks::function]
252    fn should_use_absolute_url_references(self: Vc<Self>) -> Vc<bool> {
253        Vc::cell(false)
254    }
255
256    #[turbo_tasks::function]
257    fn async_loader_chunk_item(
258        &self,
259        module: Vc<Box<dyn ChunkableModule>>,
260        module_graph: Vc<ModuleGraph>,
261        availability_info: AvailabilityInfo,
262    ) -> Vc<Box<dyn ChunkItem>>;
263    #[turbo_tasks::function]
264    fn async_loader_chunk_item_id(&self, module: Vc<Box<dyn ChunkableModule>>) -> Vc<ModuleId>;
265
266    #[turbo_tasks::function]
267    fn chunk_group(
268        self: Vc<Self>,
269        ident: Vc<AssetIdent>,
270        chunk_group: ChunkGroup,
271        module_graph: Vc<ModuleGraph>,
272        availability_info: AvailabilityInfo,
273    ) -> Vc<ChunkGroupResult>;
274
275    #[turbo_tasks::function]
276    fn evaluated_chunk_group(
277        self: Vc<Self>,
278        ident: Vc<AssetIdent>,
279        chunk_group: ChunkGroup,
280        module_graph: Vc<ModuleGraph>,
281        availability_info: AvailabilityInfo,
282    ) -> Vc<ChunkGroupResult>;
283
284    /// Generates an output chunk that:
285    /// * loads the given extra_chunks in addition to the generated chunks; and
286    /// * evaluates the given assets; and
287    /// * exports the result of evaluating the last module as a CommonJS default export.
288    #[turbo_tasks::function]
289    fn entry_chunk_group(
290        self: Vc<Self>,
291        path: FileSystemPath,
292        evaluatable_assets: Vc<EvaluatableAssets>,
293        module_graph: Vc<ModuleGraph>,
294        extra_chunks: Vc<OutputAssets>,
295        extra_referenced_assets: Vc<OutputAssets>,
296        availability_info: AvailabilityInfo,
297    ) -> Result<Vc<EntryChunkGroupResult>>;
298
299    #[turbo_tasks::function]
300    async fn chunk_item_id_from_ident(
301        self: Vc<Self>,
302        ident: Vc<AssetIdent>,
303    ) -> Result<Vc<ModuleId>>;
304
305    #[turbo_tasks::function]
306    fn chunk_item_id(self: Vc<Self>, module: Vc<Box<dyn ChunkItem>>) -> Vc<ModuleId> {
307        self.chunk_item_id_from_ident(module.asset_ident())
308    }
309    #[turbo_tasks::function]
310    fn chunk_item_id_from_module(self: Vc<Self>, module: Vc<Box<dyn Module>>) -> Vc<ModuleId> {
311        self.chunk_item_id_from_ident(module.ident())
312    }
313
314    #[turbo_tasks::function]
315    async fn module_export_usage(
316        self: Vc<Self>,
317        module: Vc<Box<dyn Module>>,
318    ) -> Result<Vc<ModuleExportUsage>>;
319
320    /// Returns whether debug IDs are enabled for this chunking context.
321    #[turbo_tasks::function]
322    fn debug_ids_enabled(self: Vc<Self>) -> Vc<bool>;
323}
324
325pub trait ChunkingContextExt {
326    fn root_chunk_group(
327        self: Vc<Self>,
328        ident: Vc<AssetIdent>,
329        chunk_group: ChunkGroup,
330        module_graph: Vc<ModuleGraph>,
331    ) -> Vc<ChunkGroupResult>
332    where
333        Self: Send;
334
335    fn root_chunk_group_assets(
336        self: Vc<Self>,
337        ident: Vc<AssetIdent>,
338        chunk_group: ChunkGroup,
339        module_graph: Vc<ModuleGraph>,
340    ) -> Vc<OutputAssetsWithReferenced>
341    where
342        Self: Send;
343
344    fn evaluated_chunk_group_assets(
345        self: Vc<Self>,
346        ident: Vc<AssetIdent>,
347        chunk_group: ChunkGroup,
348        module_graph: Vc<ModuleGraph>,
349        availability_info: AvailabilityInfo,
350    ) -> Vc<OutputAssetsWithReferenced>
351    where
352        Self: Send;
353
354    fn entry_chunk_group_asset(
355        self: Vc<Self>,
356        path: FileSystemPath,
357        evaluatable_assets: Vc<EvaluatableAssets>,
358        module_graph: Vc<ModuleGraph>,
359        extra_chunks: Vc<OutputAssets>,
360        extra_referenced_assets: Vc<OutputAssets>,
361        availability_info: AvailabilityInfo,
362    ) -> Vc<Box<dyn OutputAsset>>
363    where
364        Self: Send;
365
366    fn root_entry_chunk_group(
367        self: Vc<Self>,
368        path: FileSystemPath,
369        evaluatable_assets: Vc<EvaluatableAssets>,
370        module_graph: Vc<ModuleGraph>,
371        extra_chunks: Vc<OutputAssets>,
372        extra_referenced_assets: Vc<OutputAssets>,
373    ) -> Vc<EntryChunkGroupResult>
374    where
375        Self: Send;
376
377    fn root_entry_chunk_group_asset(
378        self: Vc<Self>,
379        path: FileSystemPath,
380        evaluatable_assets: Vc<EvaluatableAssets>,
381        module_graph: Vc<ModuleGraph>,
382        extra_chunks: Vc<OutputAssets>,
383        extra_referenced_assets: Vc<OutputAssets>,
384    ) -> Vc<Box<dyn OutputAsset>>
385    where
386        Self: Send;
387
388    fn chunk_group_assets(
389        self: Vc<Self>,
390        ident: Vc<AssetIdent>,
391        chunk_group: ChunkGroup,
392        module_graph: Vc<ModuleGraph>,
393        availability_info: AvailabilityInfo,
394    ) -> Vc<OutputAssetsWithReferenced>
395    where
396        Self: Send;
397
398    /// Computes the relative path from the chunk output root to the project root.
399    ///
400    /// This is used to compute relative paths for source maps in certain configurations.
401    fn relative_path_from_chunk_root_to_project_root(self: Vc<Self>) -> Vc<RcStr>
402    where
403        Self: Send;
404}
405
406impl<T: ChunkingContext + Send + Upcast<Box<dyn ChunkingContext>>> ChunkingContextExt for T {
407    fn root_chunk_group(
408        self: Vc<Self>,
409        ident: Vc<AssetIdent>,
410        chunk_group: ChunkGroup,
411        module_graph: Vc<ModuleGraph>,
412    ) -> Vc<ChunkGroupResult> {
413        self.chunk_group(ident, chunk_group, module_graph, AvailabilityInfo::Root)
414    }
415
416    fn root_chunk_group_assets(
417        self: Vc<Self>,
418        ident: Vc<AssetIdent>,
419        chunk_group: ChunkGroup,
420        module_graph: Vc<ModuleGraph>,
421    ) -> Vc<OutputAssetsWithReferenced> {
422        root_chunk_group_assets(
423            Vc::upcast_non_strict(self),
424            ident,
425            chunk_group,
426            module_graph,
427        )
428    }
429
430    fn evaluated_chunk_group_assets(
431        self: Vc<Self>,
432        ident: Vc<AssetIdent>,
433        chunk_group: ChunkGroup,
434        module_graph: Vc<ModuleGraph>,
435        availability_info: AvailabilityInfo,
436    ) -> Vc<OutputAssetsWithReferenced> {
437        evaluated_chunk_group_assets(
438            Vc::upcast_non_strict(self),
439            ident,
440            chunk_group,
441            module_graph,
442            availability_info,
443        )
444    }
445
446    fn entry_chunk_group_asset(
447        self: Vc<Self>,
448        path: FileSystemPath,
449        evaluatable_assets: Vc<EvaluatableAssets>,
450        module_graph: Vc<ModuleGraph>,
451        extra_chunks: Vc<OutputAssets>,
452        extra_referenced_assets: Vc<OutputAssets>,
453        availability_info: AvailabilityInfo,
454    ) -> Vc<Box<dyn OutputAsset>> {
455        entry_chunk_group_asset(
456            Vc::upcast_non_strict(self),
457            path,
458            evaluatable_assets,
459            module_graph,
460            extra_chunks,
461            extra_referenced_assets,
462            availability_info,
463        )
464    }
465
466    fn root_entry_chunk_group(
467        self: Vc<Self>,
468        path: FileSystemPath,
469        evaluatable_assets: Vc<EvaluatableAssets>,
470        module_graph: Vc<ModuleGraph>,
471        extra_chunks: Vc<OutputAssets>,
472        extra_referenced_assets: Vc<OutputAssets>,
473    ) -> Vc<EntryChunkGroupResult> {
474        self.entry_chunk_group(
475            path,
476            evaluatable_assets,
477            module_graph,
478            extra_chunks,
479            extra_referenced_assets,
480            AvailabilityInfo::Root,
481        )
482    }
483
484    fn root_entry_chunk_group_asset(
485        self: Vc<Self>,
486        path: FileSystemPath,
487        evaluatable_assets: Vc<EvaluatableAssets>,
488        module_graph: Vc<ModuleGraph>,
489        extra_chunks: Vc<OutputAssets>,
490        extra_referenced_assets: Vc<OutputAssets>,
491    ) -> Vc<Box<dyn OutputAsset>> {
492        entry_chunk_group_asset(
493            Vc::upcast_non_strict(self),
494            path,
495            evaluatable_assets,
496            module_graph,
497            extra_chunks,
498            extra_referenced_assets,
499            AvailabilityInfo::Root,
500        )
501    }
502
503    fn chunk_group_assets(
504        self: Vc<Self>,
505        ident: Vc<AssetIdent>,
506        chunk_group: ChunkGroup,
507        module_graph: Vc<ModuleGraph>,
508        availability_info: AvailabilityInfo,
509    ) -> Vc<OutputAssetsWithReferenced> {
510        chunk_group_assets(
511            Vc::upcast_non_strict(self),
512            ident,
513            chunk_group,
514            module_graph,
515            availability_info,
516        )
517    }
518
519    fn relative_path_from_chunk_root_to_project_root(self: Vc<Self>) -> Vc<RcStr> {
520        relative_path_from_chunk_root_to_project_root(Vc::upcast_non_strict(self))
521    }
522}
523
524#[turbo_tasks::function]
525async fn relative_path_from_chunk_root_to_project_root(
526    chunking_context: Vc<Box<dyn ChunkingContext>>,
527) -> Result<Vc<RcStr>> {
528    // Example,
529    //   project root: /project/root
530    //   output root: /project/root/dist
531    //   chunk root path: /project/root/dist/ssr/chunks
532    //   output_root_to_chunk_root: ../
533    //
534    // Example2,
535    //   project root: /project/root
536    //   output root: /project/out
537    //   chunk root path: /project/out/ssr/chunks
538    //   output_root_to_chunk_root: ../root
539    //
540    // From that we want to return  ../../../root to get from a path in `chunks` to a path in the
541    // project root.
542
543    let chunk_root_path = chunking_context.chunk_root_path().await?;
544    let output_root = chunking_context.output_root().await?;
545    let chunk_to_output_root = chunk_root_path.get_relative_path_to(&output_root);
546    let Some(chunk_to_output_root) = chunk_to_output_root else {
547        bail!(
548            "expected chunk_root_path: {chunk_root_path} to be inside of output_root: \
549             {output_root}",
550            chunk_root_path = chunk_root_path.value_to_string().await?,
551            output_root = output_root.value_to_string().await?
552        );
553    };
554    let output_root_to_chunk_root_path = chunking_context.output_root_to_root_path().await?;
555
556    // Note we cannot use `normalize_path` here since it rejects paths that start with `../`
557    Ok(Vc::cell(
558        format!(
559            "{}/{}",
560            chunk_to_output_root, output_root_to_chunk_root_path
561        )
562        .into(),
563    ))
564}
565
566#[turbo_tasks::function]
567async fn root_chunk_group_assets(
568    chunking_context: Vc<Box<dyn ChunkingContext>>,
569    ident: Vc<AssetIdent>,
570    chunk_group: ChunkGroup,
571    module_graph: Vc<ModuleGraph>,
572) -> Result<Vc<OutputAssetsWithReferenced>> {
573    let root_chunk_group = chunking_context
574        .root_chunk_group(ident, chunk_group, module_graph)
575        .await?;
576    Ok(OutputAssetsWithReferenced {
577        assets: root_chunk_group.assets,
578        referenced_assets: root_chunk_group.referenced_assets,
579    }
580    .cell())
581}
582
583#[turbo_tasks::function]
584async fn evaluated_chunk_group_assets(
585    chunking_context: Vc<Box<dyn ChunkingContext>>,
586    ident: Vc<AssetIdent>,
587    chunk_group: ChunkGroup,
588    module_graph: Vc<ModuleGraph>,
589    availability_info: AvailabilityInfo,
590) -> Result<Vc<OutputAssetsWithReferenced>> {
591    let evaluated_chunk_group = chunking_context
592        .evaluated_chunk_group(ident, chunk_group, module_graph, availability_info)
593        .await?;
594    Ok(OutputAssetsWithReferenced {
595        assets: evaluated_chunk_group.assets,
596        referenced_assets: evaluated_chunk_group.referenced_assets,
597    }
598    .cell())
599}
600
601#[turbo_tasks::function]
602async fn entry_chunk_group_asset(
603    chunking_context: Vc<Box<dyn ChunkingContext>>,
604    path: FileSystemPath,
605    evaluatable_assets: Vc<EvaluatableAssets>,
606    module_graph: Vc<ModuleGraph>,
607    extra_chunks: Vc<OutputAssets>,
608    extra_referenced_assets: Vc<OutputAssets>,
609    availability_info: AvailabilityInfo,
610) -> Result<Vc<Box<dyn OutputAsset>>> {
611    Ok(*chunking_context
612        .entry_chunk_group(
613            path,
614            evaluatable_assets,
615            module_graph,
616            extra_chunks,
617            extra_referenced_assets,
618            availability_info,
619        )
620        .await?
621        .asset)
622}
623
624#[turbo_tasks::function]
625async fn chunk_group_assets(
626    chunking_context: Vc<Box<dyn ChunkingContext>>,
627    ident: Vc<AssetIdent>,
628    chunk_group: ChunkGroup,
629    module_graph: Vc<ModuleGraph>,
630    availability_info: AvailabilityInfo,
631) -> Result<Vc<OutputAssetsWithReferenced>> {
632    let chunk_group = chunking_context
633        .chunk_group(ident, chunk_group, module_graph, availability_info)
634        .await?;
635    Ok(OutputAssetsWithReferenced {
636        assets: chunk_group.assets,
637        referenced_assets: chunk_group.referenced_assets,
638    }
639    .cell())
640}