turbopack_browser/
chunking_context.rs

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