turbopack_browser/
chunking_context.rs

1use anyhow::{Context, Result, bail};
2use serde::{Deserialize, Serialize};
3use tracing::Instrument;
4use turbo_rcstr::{RcStr, rcstr};
5use turbo_tasks::{
6    FxIndexMap, NonLocalValue, ResolvedVc, TaskInput, TryJoinIterExt, Upcast, ValueToString, Vc,
7    trace::TraceRawVcs,
8};
9use turbo_tasks_fs::FileSystemPath;
10use turbo_tasks_hash::{DeterministicHash, hash_xxh3_hash64};
11use turbopack_core::{
12    asset::{Asset, AssetContent},
13    chunk::{
14        Chunk, ChunkGroupResult, ChunkItem, ChunkType, ChunkableModule, ChunkingConfig,
15        ChunkingConfigs, ChunkingContext, EntryChunkGroupResult, EvaluatableAsset,
16        EvaluatableAssets, MinifyType, ModuleId, SourceMapsType,
17        availability_info::AvailabilityInfo,
18        chunk_group::{MakeChunkGroupResult, make_chunk_group},
19        module_id_strategies::{DevModuleIdStrategy, ModuleIdStrategy},
20    },
21    environment::Environment,
22    ident::AssetIdent,
23    module::Module,
24    module_graph::{
25        ModuleGraph,
26        chunk_group_info::ChunkGroup,
27        export_usage::{ExportUsageInfo, ModuleExportUsage},
28    },
29    output::{OutputAsset, OutputAssets},
30};
31use turbopack_ecmascript::{
32    async_chunk::module::AsyncLoaderModule,
33    chunk::EcmascriptChunk,
34    manifest::{chunk_asset::ManifestAsyncModule, loader_item::ManifestLoaderChunkItem},
35};
36use turbopack_ecmascript_runtime::RuntimeType;
37
38use crate::ecmascript::{
39    chunk::EcmascriptBrowserChunk,
40    evaluate::chunk::EcmascriptBrowserEvaluateChunk,
41    list::asset::{EcmascriptDevChunkList, EcmascriptDevChunkListSource},
42};
43
44#[turbo_tasks::value]
45#[derive(Debug, Clone, Copy, Hash, TaskInput)]
46pub enum CurrentChunkMethod {
47    StringLiteral,
48    DocumentCurrentScript,
49}
50
51pub const CURRENT_CHUNK_METHOD_DOCUMENT_CURRENT_SCRIPT_EXPR: &str =
52    "typeof document === \"object\" ? document.currentScript : undefined";
53
54#[derive(
55    Debug,
56    TaskInput,
57    Clone,
58    Copy,
59    PartialEq,
60    Eq,
61    Hash,
62    Serialize,
63    Deserialize,
64    TraceRawVcs,
65    DeterministicHash,
66    NonLocalValue,
67)]
68pub enum ContentHashing {
69    /// Direct content hashing: Embeds the chunk content hash directly into the referencing chunk.
70    /// Benefit: No hash manifest needed.
71    /// Downside: Causes cascading hash invalidation.
72    Direct {
73        /// The length of the content hash in hex chars. Anything lower than 8 is not recommended
74        /// due to the high risk of collisions.
75        length: u8,
76    },
77}
78
79pub struct BrowserChunkingContextBuilder {
80    chunking_context: BrowserChunkingContext,
81}
82
83impl BrowserChunkingContextBuilder {
84    pub fn name(mut self, name: RcStr) -> Self {
85        self.chunking_context.name = Some(name);
86        self
87    }
88
89    pub fn hot_module_replacement(mut self) -> Self {
90        self.chunking_context.enable_hot_module_replacement = true;
91        self
92    }
93
94    pub fn use_file_source_map_uris(mut self) -> Self {
95        self.chunking_context.should_use_file_source_map_uris = true;
96        self
97    }
98
99    pub fn tracing(mut self, enable_tracing: bool) -> Self {
100        self.chunking_context.enable_tracing = enable_tracing;
101        self
102    }
103
104    pub fn module_merging(mut self, enable_module_merging: bool) -> Self {
105        self.chunking_context.enable_module_merging = enable_module_merging;
106        self
107    }
108
109    pub fn dynamic_chunk_content_loading(
110        mut self,
111        enable_dynamic_chunk_content_loading: bool,
112    ) -> Self {
113        self.chunking_context.enable_dynamic_chunk_content_loading =
114            enable_dynamic_chunk_content_loading;
115        self
116    }
117
118    pub fn asset_base_path(mut self, asset_base_path: Option<RcStr>) -> Self {
119        self.chunking_context.asset_base_path = asset_base_path;
120        self
121    }
122
123    pub fn chunk_base_path(mut self, chunk_base_path: Option<RcStr>) -> Self {
124        self.chunking_context.chunk_base_path = chunk_base_path;
125        self
126    }
127
128    pub fn chunk_suffix_path(mut self, chunk_suffix_path: ResolvedVc<Option<RcStr>>) -> Self {
129        self.chunking_context.chunk_suffix_path = Some(chunk_suffix_path);
130        self
131    }
132
133    pub fn runtime_type(mut self, runtime_type: RuntimeType) -> Self {
134        self.chunking_context.runtime_type = runtime_type;
135        self
136    }
137
138    pub fn manifest_chunks(mut self, manifest_chunks: bool) -> Self {
139        self.chunking_context.manifest_chunks = manifest_chunks;
140        self
141    }
142
143    pub fn minify_type(mut self, minify_type: MinifyType) -> Self {
144        self.chunking_context.minify_type = minify_type;
145        self
146    }
147
148    pub fn source_maps(mut self, source_maps: SourceMapsType) -> Self {
149        self.chunking_context.source_maps_type = source_maps;
150        self
151    }
152
153    pub fn current_chunk_method(mut self, method: CurrentChunkMethod) -> Self {
154        self.chunking_context.current_chunk_method = method;
155        self
156    }
157
158    pub fn module_id_strategy(
159        mut self,
160        module_id_strategy: ResolvedVc<Box<dyn ModuleIdStrategy>>,
161    ) -> Self {
162        self.chunking_context.module_id_strategy = module_id_strategy;
163        self
164    }
165
166    pub fn export_usage(mut self, export_usage: Option<ResolvedVc<ExportUsageInfo>>) -> Self {
167        self.chunking_context.export_usage = export_usage;
168        self
169    }
170
171    pub fn debug_ids(mut self, debug_ids: bool) -> Self {
172        self.chunking_context.debug_ids = debug_ids;
173        self
174    }
175
176    pub fn should_use_absolute_url_references(
177        mut self,
178        should_use_absolute_url_references: bool,
179    ) -> Self {
180        self.chunking_context.should_use_absolute_url_references =
181            should_use_absolute_url_references;
182        self
183    }
184
185    pub fn asset_root_path_override(mut self, tag: RcStr, path: FileSystemPath) -> Self {
186        self.chunking_context.asset_root_paths.insert(tag, path);
187        self
188    }
189
190    pub fn client_roots_override(mut self, tag: RcStr, path: FileSystemPath) -> Self {
191        self.chunking_context.client_roots.insert(tag, path);
192        self
193    }
194
195    pub fn asset_base_path_override(mut self, tag: RcStr, path: RcStr) -> Self {
196        self.chunking_context.asset_base_paths.insert(tag, path);
197        self
198    }
199
200    pub fn chunking_config<T>(mut self, ty: ResolvedVc<T>, chunking_config: ChunkingConfig) -> Self
201    where
202        T: Upcast<Box<dyn ChunkType>>,
203    {
204        self.chunking_context
205            .chunking_configs
206            .push((ResolvedVc::upcast_non_strict(ty), chunking_config));
207        self
208    }
209
210    pub fn use_content_hashing(mut self, content_hashing: ContentHashing) -> Self {
211        self.chunking_context.content_hashing = Some(content_hashing);
212        self
213    }
214
215    pub fn build(self) -> Vc<BrowserChunkingContext> {
216        BrowserChunkingContext::cell(self.chunking_context)
217    }
218}
219
220/// A chunking context for development mode.
221///
222/// It uses readable filenames and module ids to improve development.
223/// It also uses a chunking heuristic that is incremental and cacheable.
224/// It splits "node_modules" separately as these are less likely to change
225/// during development
226#[turbo_tasks::value]
227#[derive(Debug, Clone)]
228pub struct BrowserChunkingContext {
229    name: Option<RcStr>,
230    /// The root path of the project
231    root_path: FileSystemPath,
232    /// Whether to write file sources as file:// paths in source maps
233    should_use_file_source_map_uris: bool,
234    /// This path is used to compute the url to request chunks from
235    output_root: FileSystemPath,
236    /// The relative path from the output_root to the root_path.
237    output_root_to_root_path: RcStr,
238    /// This path is used to compute the url to request assets from
239    client_root: FileSystemPath,
240    /// This path is used to compute the url to request chunks or assets from
241    client_roots: FxIndexMap<RcStr, FileSystemPath>,
242    /// Chunks are placed at this path
243    chunk_root_path: FileSystemPath,
244    /// Static assets are placed at this path
245    asset_root_path: FileSystemPath,
246    /// Static assets are placed at this path
247    asset_root_paths: FxIndexMap<RcStr, FileSystemPath>,
248    /// Base path that will be prepended to all chunk URLs when loading them.
249    /// This path will not appear in chunk paths or chunk data.
250    chunk_base_path: Option<RcStr>,
251    /// Suffix path that will be appended to all chunk URLs when loading them.
252    /// This path will not appear in chunk paths or chunk data.
253    chunk_suffix_path: Option<ResolvedVc<Option<RcStr>>>,
254    /// URL prefix that will be prepended to all static asset URLs when loading
255    /// them.
256    asset_base_path: Option<RcStr>,
257    /// URL prefix that will be prepended to all static asset URLs when loading
258    /// them.
259    asset_base_paths: FxIndexMap<RcStr, RcStr>,
260    /// Enable HMR for this chunking
261    enable_hot_module_replacement: bool,
262    /// Enable tracing for this chunking
263    enable_tracing: bool,
264    /// Enable module merging
265    enable_module_merging: bool,
266    /// Enable dynamic chunk content loading.
267    enable_dynamic_chunk_content_loading: bool,
268    /// Enable debug IDs for chunks and source maps.
269    debug_ids: bool,
270    /// The environment chunks will be evaluated in.
271    environment: ResolvedVc<Environment>,
272    /// The kind of runtime to include in the output.
273    runtime_type: RuntimeType,
274    /// Whether to minify resulting chunks
275    minify_type: MinifyType,
276    /// Whether content hashing is enabled.
277    content_hashing: Option<ContentHashing>,
278    /// Whether to generate source maps
279    source_maps_type: SourceMapsType,
280    /// Method to use when figuring out the current chunk src
281    current_chunk_method: CurrentChunkMethod,
282    /// Whether to use manifest chunks for lazy compilation
283    manifest_chunks: bool,
284    /// The module id strategy to use
285    module_id_strategy: ResolvedVc<Box<dyn ModuleIdStrategy>>,
286    /// The module export usage info, if available.
287    export_usage: Option<ResolvedVc<ExportUsageInfo>>,
288    /// The chunking configs
289    chunking_configs: Vec<(ResolvedVc<Box<dyn ChunkType>>, ChunkingConfig)>,
290    /// Whether to use absolute URLs for static assets (e.g. in CSS: `url("/absolute/path")`)
291    should_use_absolute_url_references: bool,
292}
293
294impl BrowserChunkingContext {
295    pub fn builder(
296        root_path: FileSystemPath,
297        output_root: FileSystemPath,
298        output_root_to_root_path: RcStr,
299        client_root: FileSystemPath,
300        chunk_root_path: FileSystemPath,
301        asset_root_path: FileSystemPath,
302        environment: ResolvedVc<Environment>,
303        runtime_type: RuntimeType,
304    ) -> BrowserChunkingContextBuilder {
305        BrowserChunkingContextBuilder {
306            chunking_context: BrowserChunkingContext {
307                name: None,
308                root_path,
309                output_root,
310                output_root_to_root_path,
311                client_root,
312                client_roots: Default::default(),
313                chunk_root_path,
314                should_use_file_source_map_uris: false,
315                asset_root_path,
316                asset_root_paths: Default::default(),
317                chunk_base_path: None,
318                chunk_suffix_path: None,
319                asset_base_path: None,
320                asset_base_paths: Default::default(),
321                enable_hot_module_replacement: false,
322                enable_tracing: false,
323                enable_module_merging: false,
324                enable_dynamic_chunk_content_loading: false,
325                debug_ids: false,
326                environment,
327                runtime_type,
328                minify_type: MinifyType::NoMinify,
329                content_hashing: None,
330                source_maps_type: SourceMapsType::Full,
331                current_chunk_method: CurrentChunkMethod::StringLiteral,
332                manifest_chunks: false,
333                module_id_strategy: ResolvedVc::upcast(DevModuleIdStrategy::new_resolved()),
334                export_usage: None,
335                chunking_configs: Default::default(),
336                should_use_absolute_url_references: false,
337            },
338        }
339    }
340}
341
342#[turbo_tasks::value_impl]
343impl BrowserChunkingContext {
344    #[turbo_tasks::function]
345    fn generate_evaluate_chunk(
346        self: Vc<Self>,
347        ident: Vc<AssetIdent>,
348        other_chunks: Vc<OutputAssets>,
349        evaluatable_assets: Vc<EvaluatableAssets>,
350        // TODO(sokra) remove this argument and pass chunk items instead
351        module_graph: Vc<ModuleGraph>,
352    ) -> Vc<Box<dyn OutputAsset>> {
353        Vc::upcast(EcmascriptBrowserEvaluateChunk::new(
354            self,
355            ident,
356            other_chunks,
357            evaluatable_assets,
358            module_graph,
359        ))
360    }
361
362    #[turbo_tasks::function]
363    fn generate_chunk_list_register_chunk(
364        self: Vc<Self>,
365        ident: Vc<AssetIdent>,
366        evaluatable_assets: Vc<EvaluatableAssets>,
367        other_chunks: Vc<OutputAssets>,
368        source: EcmascriptDevChunkListSource,
369    ) -> Vc<Box<dyn OutputAsset>> {
370        Vc::upcast(EcmascriptDevChunkList::new(
371            self,
372            ident,
373            evaluatable_assets,
374            other_chunks,
375            source,
376        ))
377    }
378
379    #[turbo_tasks::function]
380    async fn generate_chunk(
381        self: Vc<Self>,
382        chunk: ResolvedVc<Box<dyn Chunk>>,
383    ) -> Result<Vc<Box<dyn OutputAsset>>> {
384        Ok(
385            if let Some(ecmascript_chunk) = ResolvedVc::try_downcast_type::<EcmascriptChunk>(chunk)
386            {
387                Vc::upcast(EcmascriptBrowserChunk::new(self, *ecmascript_chunk))
388            } else if let Some(output_asset) =
389                ResolvedVc::try_sidecast::<Box<dyn OutputAsset>>(chunk)
390            {
391                *output_asset
392            } else {
393                bail!("Unable to generate output asset for chunk");
394            },
395        )
396    }
397
398    #[turbo_tasks::function]
399    pub fn current_chunk_method(&self) -> Vc<CurrentChunkMethod> {
400        self.current_chunk_method.cell()
401    }
402
403    /// Returns the kind of runtime to include in output chunks.
404    ///
405    /// This is defined directly on `BrowserChunkingContext` so it is zero-cost
406    /// when `RuntimeType` has a single variant.
407    #[turbo_tasks::function]
408    pub fn runtime_type(&self) -> Vc<RuntimeType> {
409        self.runtime_type.cell()
410    }
411
412    /// Returns the asset base path.
413    #[turbo_tasks::function]
414    pub fn chunk_base_path(&self) -> Vc<Option<RcStr>> {
415        Vc::cell(self.chunk_base_path.clone())
416    }
417
418    /// Returns the asset suffix path.
419    #[turbo_tasks::function]
420    pub fn chunk_suffix_path(&self) -> Vc<Option<RcStr>> {
421        if let Some(chunk_suffix_path) = self.chunk_suffix_path {
422            *chunk_suffix_path
423        } else {
424            Vc::cell(None)
425        }
426    }
427
428    /// Returns the source map type.
429    #[turbo_tasks::function]
430    pub fn source_maps_type(&self) -> Vc<SourceMapsType> {
431        self.source_maps_type.cell()
432    }
433
434    /// Returns the minify type.
435    #[turbo_tasks::function]
436    pub fn minify_type(&self) -> Vc<MinifyType> {
437        self.minify_type.cell()
438    }
439
440    /// Returns the chunk path information.
441    #[turbo_tasks::function]
442    fn chunk_path_info(&self) -> Vc<ChunkPathInfo> {
443        ChunkPathInfo {
444            root_path: self.root_path.clone(),
445            chunk_root_path: self.chunk_root_path.clone(),
446            content_hashing: self.content_hashing,
447        }
448        .cell()
449    }
450}
451
452#[turbo_tasks::value_impl]
453impl ChunkingContext for BrowserChunkingContext {
454    #[turbo_tasks::function]
455    fn name(&self) -> Vc<RcStr> {
456        if let Some(name) = &self.name {
457            Vc::cell(name.clone())
458        } else {
459            Vc::cell(rcstr!("unknown"))
460        }
461    }
462
463    #[turbo_tasks::function]
464    fn root_path(&self) -> Vc<FileSystemPath> {
465        self.root_path.clone().cell()
466    }
467
468    #[turbo_tasks::function]
469    fn output_root(&self) -> Vc<FileSystemPath> {
470        self.output_root.clone().cell()
471    }
472
473    #[turbo_tasks::function]
474    fn output_root_to_root_path(&self) -> Vc<RcStr> {
475        Vc::cell(self.output_root_to_root_path.clone())
476    }
477
478    #[turbo_tasks::function]
479    fn environment(&self) -> Vc<Environment> {
480        *self.environment
481    }
482
483    #[turbo_tasks::function]
484    fn chunk_root_path(&self) -> Vc<FileSystemPath> {
485        self.chunk_root_path.clone().cell()
486    }
487
488    #[turbo_tasks::function]
489    async fn chunk_path(
490        self: Vc<Self>,
491        asset: Option<Vc<Box<dyn Asset>>>,
492        ident: Vc<AssetIdent>,
493        prefix: Option<RcStr>,
494        extension: RcStr,
495    ) -> Result<Vc<FileSystemPath>> {
496        debug_assert!(
497            extension.starts_with("."),
498            "`extension` should include the leading '.', got '{extension}'"
499        );
500        let ChunkPathInfo {
501            chunk_root_path,
502            content_hashing,
503            root_path,
504        } = &*self.chunk_path_info().await?;
505        let name = match *content_hashing {
506            None => {
507                ident
508                    .output_name(root_path.clone(), prefix, extension)
509                    .owned()
510                    .await?
511            }
512            Some(ContentHashing::Direct { length }) => {
513                let Some(asset) = asset else {
514                    bail!("chunk_path requires an asset when content hashing is enabled");
515                };
516                let content = asset.content().await?;
517                if let AssetContent::File(file) = &*content {
518                    let hash = hash_xxh3_hash64(&file.await?);
519                    let length = length as usize;
520                    if let Some(prefix) = prefix {
521                        format!("{prefix}-{hash:0length$x}{extension}").into()
522                    } else {
523                        format!("{hash:0length$x}{extension}").into()
524                    }
525                } else {
526                    bail!(
527                        "chunk_path requires an asset with file content when content hashing is \
528                         enabled"
529                    );
530                }
531            }
532        };
533        Ok(chunk_root_path.join(&name)?.cell())
534    }
535
536    #[turbo_tasks::function]
537    async fn asset_url(&self, ident: FileSystemPath, tag: Option<RcStr>) -> Result<Vc<RcStr>> {
538        let asset_path = ident.to_string();
539
540        let client_root = tag
541            .as_ref()
542            .and_then(|tag| self.client_roots.get(tag))
543            .unwrap_or(&self.client_root);
544
545        let asset_base_path = tag
546            .as_ref()
547            .and_then(|tag| self.asset_base_paths.get(tag))
548            .or(self.asset_base_path.as_ref());
549
550        let asset_path = asset_path
551            .strip_prefix(&format!("{}/", client_root.path))
552            .context("expected asset_path to contain client_root")?;
553
554        Ok(Vc::cell(
555            format!(
556                "{}{}",
557                asset_base_path.map(|s| s.as_str()).unwrap_or("/"),
558                asset_path
559            )
560            .into(),
561        ))
562    }
563
564    #[turbo_tasks::function]
565    fn reference_chunk_source_maps(&self, _chunk: Vc<Box<dyn OutputAsset>>) -> Vc<bool> {
566        Vc::cell(match self.source_maps_type {
567            SourceMapsType::Full => true,
568            SourceMapsType::None => false,
569        })
570    }
571
572    #[turbo_tasks::function]
573    fn reference_module_source_maps(&self, _module: Vc<Box<dyn Module>>) -> Vc<bool> {
574        Vc::cell(match self.source_maps_type {
575            SourceMapsType::Full => true,
576            SourceMapsType::None => false,
577        })
578    }
579
580    #[turbo_tasks::function]
581    async fn asset_path(
582        &self,
583        content_hash: RcStr,
584        original_asset_ident: Vc<AssetIdent>,
585        tag: Option<RcStr>,
586    ) -> Result<Vc<FileSystemPath>> {
587        let source_path = original_asset_ident.path().await?;
588        let basename = source_path.file_name();
589        let asset_path = match source_path.extension_ref() {
590            Some(ext) => format!(
591                "{basename}.{content_hash}.{ext}",
592                basename = &basename[..basename.len() - ext.len() - 1],
593                content_hash = &content_hash[..8]
594            ),
595            None => format!(
596                "{basename}.{content_hash}",
597                content_hash = &content_hash[..8]
598            ),
599        };
600
601        let asset_root_path = tag
602            .as_ref()
603            .and_then(|tag| self.asset_root_paths.get(tag))
604            .unwrap_or(&self.asset_root_path);
605
606        Ok(asset_root_path.join(&asset_path)?.cell())
607    }
608
609    #[turbo_tasks::function]
610    fn is_hot_module_replacement_enabled(&self) -> Vc<bool> {
611        Vc::cell(self.enable_hot_module_replacement)
612    }
613
614    #[turbo_tasks::function]
615    fn chunking_configs(&self) -> Result<Vc<ChunkingConfigs>> {
616        Ok(Vc::cell(self.chunking_configs.iter().cloned().collect()))
617    }
618
619    #[turbo_tasks::function]
620    fn should_use_file_source_map_uris(&self) -> Vc<bool> {
621        Vc::cell(self.should_use_file_source_map_uris)
622    }
623
624    #[turbo_tasks::function]
625    fn is_tracing_enabled(&self) -> Vc<bool> {
626        Vc::cell(self.enable_tracing)
627    }
628
629    #[turbo_tasks::function]
630    fn is_module_merging_enabled(&self) -> Vc<bool> {
631        Vc::cell(self.enable_module_merging)
632    }
633
634    #[turbo_tasks::function]
635    fn is_dynamic_chunk_content_loading_enabled(&self) -> Vc<bool> {
636        Vc::cell(self.enable_dynamic_chunk_content_loading)
637    }
638
639    #[turbo_tasks::function]
640    pub fn minify_type(&self) -> Vc<MinifyType> {
641        self.minify_type.cell()
642    }
643
644    #[turbo_tasks::function]
645    fn should_use_absolute_url_references(&self) -> Vc<bool> {
646        Vc::cell(self.should_use_absolute_url_references)
647    }
648
649    #[turbo_tasks::function]
650    async fn chunk_group(
651        self: ResolvedVc<Self>,
652        ident: Vc<AssetIdent>,
653        chunk_group: ChunkGroup,
654        module_graph: Vc<ModuleGraph>,
655        availability_info: AvailabilityInfo,
656    ) -> Result<Vc<ChunkGroupResult>> {
657        let span = tracing::info_span!("chunking", name = display(ident.to_string().await?));
658        async move {
659            let this = self.await?;
660            let entries = chunk_group.entries();
661            let input_availability_info = availability_info;
662            let MakeChunkGroupResult {
663                chunks,
664                referenced_output_assets,
665                availability_info,
666            } = make_chunk_group(
667                entries,
668                module_graph,
669                ResolvedVc::upcast(self),
670                input_availability_info,
671            )
672            .await?;
673
674            let mut assets = chunks
675                .iter()
676                .map(|chunk| self.generate_chunk(**chunk).to_resolved())
677                .try_join()
678                .await?;
679
680            if this.enable_hot_module_replacement {
681                let mut ident = ident;
682                match input_availability_info {
683                    AvailabilityInfo::Root => {}
684                    AvailabilityInfo::Untracked => {
685                        ident = ident.with_modifier(rcstr!("untracked"));
686                    }
687                    AvailabilityInfo::Complete { available_modules } => {
688                        ident =
689                            ident.with_modifier(available_modules.hash().await?.to_string().into());
690                    }
691                }
692                let other_assets = Vc::cell(assets.clone());
693                assets.push(
694                    self.generate_chunk_list_register_chunk(
695                        ident,
696                        EvaluatableAssets::empty(),
697                        other_assets,
698                        EcmascriptDevChunkListSource::Dynamic,
699                    )
700                    .to_resolved()
701                    .await?,
702                );
703            }
704
705            Ok(ChunkGroupResult {
706                assets: ResolvedVc::cell(assets),
707                referenced_assets: ResolvedVc::cell(referenced_output_assets),
708                availability_info,
709            }
710            .cell())
711        }
712        .instrument(span)
713        .await
714    }
715
716    #[turbo_tasks::function]
717    async fn evaluated_chunk_group(
718        self: ResolvedVc<Self>,
719        ident: Vc<AssetIdent>,
720        chunk_group: ChunkGroup,
721        module_graph: Vc<ModuleGraph>,
722        input_availability_info: AvailabilityInfo,
723    ) -> Result<Vc<ChunkGroupResult>> {
724        let span = tracing::info_span!(
725            "chunking",
726            name = display(ident.to_string().await?),
727            chunking_type = "evaluated",
728        );
729        async move {
730            let this = self.await?;
731            let entries = chunk_group.entries();
732            let MakeChunkGroupResult {
733                chunks,
734                referenced_output_assets,
735                availability_info,
736            } = make_chunk_group(
737                entries,
738                module_graph,
739                ResolvedVc::upcast(self),
740                input_availability_info,
741            )
742            .await?;
743
744            let mut assets: Vec<ResolvedVc<Box<dyn OutputAsset>>> = chunks
745                .iter()
746                .map(|chunk| self.generate_chunk(**chunk).to_resolved())
747                .try_join()
748                .await?;
749
750            let other_assets = Vc::cell(assets.clone());
751
752            let entries = Vc::cell(
753                chunk_group
754                    .entries()
755                    .map(|m| {
756                        ResolvedVc::try_downcast::<Box<dyn EvaluatableAsset>>(m)
757                            .context("evaluated_chunk_group entries must be evaluatable assets")
758                    })
759                    .collect::<Result<Vec<_>>>()?,
760            );
761
762            if this.enable_hot_module_replacement {
763                let mut ident = ident;
764                match input_availability_info {
765                    AvailabilityInfo::Root => {}
766                    AvailabilityInfo::Untracked => {
767                        ident = ident.with_modifier(rcstr!("untracked"));
768                    }
769                    AvailabilityInfo::Complete { available_modules } => {
770                        ident =
771                            ident.with_modifier(available_modules.hash().await?.to_string().into());
772                    }
773                }
774                assets.push(
775                    self.generate_chunk_list_register_chunk(
776                        ident,
777                        entries,
778                        other_assets,
779                        EcmascriptDevChunkListSource::Entry,
780                    )
781                    .to_resolved()
782                    .await?,
783                );
784            }
785
786            assets.push(
787                self.generate_evaluate_chunk(ident, other_assets, entries, module_graph)
788                    .to_resolved()
789                    .await?,
790            );
791
792            Ok(ChunkGroupResult {
793                assets: ResolvedVc::cell(assets),
794                referenced_assets: ResolvedVc::cell(referenced_output_assets),
795                availability_info,
796            }
797            .cell())
798        }
799        .instrument(span)
800        .await
801    }
802
803    #[turbo_tasks::function]
804    fn entry_chunk_group(
805        self: Vc<Self>,
806        _path: FileSystemPath,
807        _evaluatable_assets: Vc<EvaluatableAssets>,
808        _module_graph: Vc<ModuleGraph>,
809        _extra_chunks: Vc<OutputAssets>,
810        _extra_referenced_assets: Vc<OutputAssets>,
811        _availability_info: AvailabilityInfo,
812    ) -> Result<Vc<EntryChunkGroupResult>> {
813        bail!("Browser chunking context does not support entry chunk groups")
814    }
815
816    #[turbo_tasks::function]
817    fn chunk_item_id_from_ident(&self, ident: Vc<AssetIdent>) -> Vc<ModuleId> {
818        self.module_id_strategy.get_module_id(ident)
819    }
820
821    #[turbo_tasks::function]
822    async fn async_loader_chunk_item(
823        self: Vc<Self>,
824        module: Vc<Box<dyn ChunkableModule>>,
825        module_graph: Vc<ModuleGraph>,
826        availability_info: AvailabilityInfo,
827    ) -> Result<Vc<Box<dyn ChunkItem>>> {
828        Ok(if self.await?.manifest_chunks {
829            let manifest_asset =
830                ManifestAsyncModule::new(module, module_graph, Vc::upcast(self), availability_info);
831            Vc::upcast(ManifestLoaderChunkItem::new(
832                manifest_asset,
833                module_graph,
834                Vc::upcast(self),
835            ))
836        } else {
837            let module = AsyncLoaderModule::new(module, Vc::upcast(self), availability_info);
838            module.as_chunk_item(module_graph, Vc::upcast(self))
839        })
840    }
841
842    #[turbo_tasks::function]
843    async fn async_loader_chunk_item_id(
844        self: Vc<Self>,
845        module: Vc<Box<dyn ChunkableModule>>,
846    ) -> Result<Vc<ModuleId>> {
847        Ok(if self.await?.manifest_chunks {
848            self.chunk_item_id_from_ident(ManifestLoaderChunkItem::asset_ident_for(module))
849        } else {
850            self.chunk_item_id_from_ident(AsyncLoaderModule::asset_ident_for(module))
851        })
852    }
853
854    #[turbo_tasks::function]
855    async fn module_export_usage(
856        self: Vc<Self>,
857        module: ResolvedVc<Box<dyn Module>>,
858    ) -> Result<Vc<ModuleExportUsage>> {
859        if let Some(export_usage) = self.await?.export_usage {
860            Ok(export_usage.await?.used_exports(module).await?)
861        } else {
862            // In development mode, we don't have export usage info, so we assume all exports are
863            // used.
864            Ok(ModuleExportUsage::all())
865        }
866    }
867
868    #[turbo_tasks::function]
869    async fn debug_ids_enabled(self: Vc<Self>) -> Result<Vc<bool>> {
870        Ok(Vc::cell(self.await?.debug_ids))
871    }
872}
873
874#[turbo_tasks::value]
875struct ChunkPathInfo {
876    root_path: FileSystemPath,
877    chunk_root_path: FileSystemPath,
878    content_hashing: Option<ContentHashing>,
879}