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    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: Option<RcStr>) -> Self {
129        self.chunking_context.chunk_suffix_path = 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 chunking_config<T>(mut self, ty: ResolvedVc<T>, chunking_config: ChunkingConfig) -> Self
172    where
173        T: Upcast<Box<dyn ChunkType>>,
174    {
175        self.chunking_context
176            .chunking_configs
177            .push((ResolvedVc::upcast(ty), chunking_config));
178        self
179    }
180
181    pub fn use_content_hashing(mut self, content_hashing: ContentHashing) -> Self {
182        self.chunking_context.content_hashing = Some(content_hashing);
183        self
184    }
185
186    pub fn build(self) -> Vc<BrowserChunkingContext> {
187        BrowserChunkingContext::cell(self.chunking_context)
188    }
189}
190
191/// A chunking context for development mode.
192///
193/// It uses readable filenames and module ids to improve development.
194/// It also uses a chunking heuristic that is incremental and cacheable.
195/// It splits "node_modules" separately as these are less likely to change
196/// during development
197#[turbo_tasks::value]
198#[derive(Debug, Clone, Hash, TaskInput)]
199pub struct BrowserChunkingContext {
200    name: Option<RcStr>,
201    /// The root path of the project
202    root_path: FileSystemPath,
203    /// Whether to write file sources as file:// paths in source maps
204    should_use_file_source_map_uris: bool,
205    /// This path is used to compute the url to request chunks from
206    output_root: FileSystemPath,
207    /// The relative path from the output_root to the root_path.
208    output_root_to_root_path: RcStr,
209    /// This path is used to compute the url to request assets from
210    client_root: FileSystemPath,
211    /// Chunks are placed at this path
212    chunk_root_path: FileSystemPath,
213    /// Static assets are placed at this path
214    asset_root_path: FileSystemPath,
215    /// Base path that will be prepended to all chunk URLs when loading them.
216    /// This path will not appear in chunk paths or chunk data.
217    chunk_base_path: Option<RcStr>,
218    /// Suffix path that will be appended to all chunk URLs when loading them.
219    /// This path will not appear in chunk paths or chunk data.
220    chunk_suffix_path: Option<RcStr>,
221    /// URL prefix that will be prepended to all static asset URLs when loading
222    /// them.
223    asset_base_path: Option<RcStr>,
224    /// Enable HMR for this chunking
225    enable_hot_module_replacement: bool,
226    /// Enable tracing for this chunking
227    enable_tracing: bool,
228    /// Enable module merging
229    enable_module_merging: bool,
230    /// Enable dynamic chunk content loading.
231    enable_dynamic_chunk_content_loading: bool,
232    /// The environment chunks will be evaluated in.
233    environment: ResolvedVc<Environment>,
234    /// The kind of runtime to include in the output.
235    runtime_type: RuntimeType,
236    /// Whether to minify resulting chunks
237    minify_type: MinifyType,
238    /// Whether content hashing is enabled.
239    content_hashing: Option<ContentHashing>,
240    /// Whether to generate source maps
241    source_maps_type: SourceMapsType,
242    /// Method to use when figuring out the current chunk src
243    current_chunk_method: CurrentChunkMethod,
244    /// Whether to use manifest chunks for lazy compilation
245    manifest_chunks: bool,
246    /// The module id strategy to use
247    module_id_strategy: ResolvedVc<Box<dyn ModuleIdStrategy>>,
248    /// The module export usage info, if available.
249    export_usage: Option<ResolvedVc<ExportUsageInfo>>,
250    /// The chunking configs
251    chunking_configs: Vec<(ResolvedVc<Box<dyn ChunkType>>, ChunkingConfig)>,
252}
253
254impl BrowserChunkingContext {
255    pub fn builder(
256        root_path: FileSystemPath,
257        output_root: FileSystemPath,
258        output_root_to_root_path: RcStr,
259        client_root: FileSystemPath,
260        chunk_root_path: FileSystemPath,
261        asset_root_path: FileSystemPath,
262        environment: ResolvedVc<Environment>,
263        runtime_type: RuntimeType,
264    ) -> BrowserChunkingContextBuilder {
265        BrowserChunkingContextBuilder {
266            chunking_context: BrowserChunkingContext {
267                name: None,
268                root_path,
269                output_root,
270                output_root_to_root_path,
271                client_root,
272                chunk_root_path,
273                should_use_file_source_map_uris: false,
274                asset_root_path,
275                chunk_base_path: None,
276                chunk_suffix_path: None,
277                asset_base_path: None,
278                enable_hot_module_replacement: false,
279                enable_tracing: false,
280                enable_module_merging: false,
281                enable_dynamic_chunk_content_loading: false,
282                environment,
283                runtime_type,
284                minify_type: MinifyType::NoMinify,
285                content_hashing: None,
286                source_maps_type: SourceMapsType::Full,
287                current_chunk_method: CurrentChunkMethod::StringLiteral,
288                manifest_chunks: false,
289                module_id_strategy: ResolvedVc::upcast(DevModuleIdStrategy::new_resolved()),
290                export_usage: None,
291                chunking_configs: Default::default(),
292            },
293        }
294    }
295}
296
297#[turbo_tasks::value_impl]
298impl BrowserChunkingContext {
299    #[turbo_tasks::function]
300    fn generate_evaluate_chunk(
301        self: Vc<Self>,
302        ident: Vc<AssetIdent>,
303        other_chunks: Vc<OutputAssets>,
304        evaluatable_assets: Vc<EvaluatableAssets>,
305        // TODO(sokra) remove this argument and pass chunk items instead
306        module_graph: Vc<ModuleGraph>,
307    ) -> Vc<Box<dyn OutputAsset>> {
308        Vc::upcast(EcmascriptBrowserEvaluateChunk::new(
309            self,
310            ident,
311            other_chunks,
312            evaluatable_assets,
313            module_graph,
314        ))
315    }
316
317    #[turbo_tasks::function]
318    fn generate_chunk_list_register_chunk(
319        self: Vc<Self>,
320        ident: Vc<AssetIdent>,
321        evaluatable_assets: Vc<EvaluatableAssets>,
322        other_chunks: Vc<OutputAssets>,
323        source: EcmascriptDevChunkListSource,
324    ) -> Vc<Box<dyn OutputAsset>> {
325        Vc::upcast(EcmascriptDevChunkList::new(
326            self,
327            ident,
328            evaluatable_assets,
329            other_chunks,
330            source,
331        ))
332    }
333
334    #[turbo_tasks::function]
335    async fn generate_chunk(
336        self: Vc<Self>,
337        chunk: Vc<Box<dyn Chunk>>,
338    ) -> Result<Vc<Box<dyn OutputAsset>>> {
339        Ok(
340            if let Some(ecmascript_chunk) =
341                Vc::try_resolve_downcast_type::<EcmascriptChunk>(chunk).await?
342            {
343                Vc::upcast(EcmascriptBrowserChunk::new(self, ecmascript_chunk))
344            } else if let Some(output_asset) =
345                Vc::try_resolve_sidecast::<Box<dyn OutputAsset>>(chunk).await?
346            {
347                output_asset
348            } else {
349                bail!("Unable to generate output asset for chunk");
350            },
351        )
352    }
353
354    #[turbo_tasks::function]
355    pub fn current_chunk_method(&self) -> Vc<CurrentChunkMethod> {
356        self.current_chunk_method.cell()
357    }
358
359    /// Returns the kind of runtime to include in output chunks.
360    ///
361    /// This is defined directly on `BrowserChunkingContext` so it is zero-cost
362    /// when `RuntimeType` has a single variant.
363    #[turbo_tasks::function]
364    pub fn runtime_type(&self) -> Vc<RuntimeType> {
365        self.runtime_type.cell()
366    }
367
368    /// Returns the asset base path.
369    #[turbo_tasks::function]
370    pub fn chunk_base_path(&self) -> Vc<Option<RcStr>> {
371        Vc::cell(self.chunk_base_path.clone())
372    }
373
374    /// Returns the asset suffix path.
375    #[turbo_tasks::function]
376    pub fn chunk_suffix_path(&self) -> Vc<Option<RcStr>> {
377        Vc::cell(self.chunk_suffix_path.clone())
378    }
379
380    /// Returns the source map type.
381    #[turbo_tasks::function]
382    pub fn source_maps_type(&self) -> Vc<SourceMapsType> {
383        self.source_maps_type.cell()
384    }
385
386    /// Returns the minify type.
387    #[turbo_tasks::function]
388    pub fn minify_type(&self) -> Vc<MinifyType> {
389        self.minify_type.cell()
390    }
391}
392
393#[turbo_tasks::value_impl]
394impl ChunkingContext for BrowserChunkingContext {
395    #[turbo_tasks::function]
396    fn name(&self) -> Vc<RcStr> {
397        if let Some(name) = &self.name {
398            Vc::cell(name.clone())
399        } else {
400            Vc::cell(rcstr!("unknown"))
401        }
402    }
403
404    #[turbo_tasks::function]
405    fn root_path(&self) -> Vc<FileSystemPath> {
406        self.root_path.clone().cell()
407    }
408
409    #[turbo_tasks::function]
410    fn output_root(&self) -> Vc<FileSystemPath> {
411        self.output_root.clone().cell()
412    }
413
414    #[turbo_tasks::function]
415    fn output_root_to_root_path(&self) -> Vc<RcStr> {
416        Vc::cell(self.output_root_to_root_path.clone())
417    }
418
419    #[turbo_tasks::function]
420    fn environment(&self) -> Vc<Environment> {
421        *self.environment
422    }
423
424    #[turbo_tasks::function]
425    fn chunk_root_path(&self) -> Vc<FileSystemPath> {
426        self.chunk_root_path.clone().cell()
427    }
428
429    #[turbo_tasks::function]
430    async fn chunk_path(
431        &self,
432        asset: Option<Vc<Box<dyn Asset>>>,
433        ident: Vc<AssetIdent>,
434        prefix: Option<RcStr>,
435        extension: RcStr,
436    ) -> Result<Vc<FileSystemPath>> {
437        debug_assert!(
438            extension.starts_with("."),
439            "`extension` should include the leading '.', got '{extension}'"
440        );
441        let root_path = self.chunk_root_path.clone();
442        let name = match self.content_hashing {
443            None => {
444                ident
445                    .output_name(self.root_path.clone(), prefix, extension)
446                    .owned()
447                    .await?
448            }
449            Some(ContentHashing::Direct { length }) => {
450                let Some(asset) = asset else {
451                    bail!("chunk_path requires an asset when content hashing is enabled");
452                };
453                let content = asset.content().await?;
454                if let AssetContent::File(file) = &*content {
455                    let hash = hash_xxh3_hash64(&file.await?);
456                    let length = length as usize;
457                    if let Some(prefix) = prefix {
458                        format!("{prefix}-{hash:0length$x}{extension}").into()
459                    } else {
460                        format!("{hash:0length$x}{extension}").into()
461                    }
462                } else {
463                    bail!(
464                        "chunk_path requires an asset with file content when content hashing is \
465                         enabled"
466                    );
467                }
468            }
469        };
470        Ok(root_path.join(&name)?.cell())
471    }
472
473    #[turbo_tasks::function]
474    async fn asset_url(&self, ident: FileSystemPath) -> Result<Vc<RcStr>> {
475        let asset_path = ident.to_string();
476        let asset_path = asset_path
477            .strip_prefix(&format!("{}/", self.client_root.path))
478            .context("expected asset_path to contain client_root")?;
479
480        Ok(Vc::cell(
481            format!(
482                "{}{}",
483                self.asset_base_path
484                    .as_ref()
485                    .map(|s| s.as_str())
486                    .unwrap_or("/"),
487                asset_path
488            )
489            .into(),
490        ))
491    }
492
493    #[turbo_tasks::function]
494    fn reference_chunk_source_maps(&self, _chunk: Vc<Box<dyn OutputAsset>>) -> Vc<bool> {
495        Vc::cell(match self.source_maps_type {
496            SourceMapsType::Full => true,
497            SourceMapsType::None => false,
498        })
499    }
500
501    #[turbo_tasks::function]
502    fn reference_module_source_maps(&self, _module: Vc<Box<dyn Module>>) -> Vc<bool> {
503        Vc::cell(match self.source_maps_type {
504            SourceMapsType::Full => true,
505            SourceMapsType::None => false,
506        })
507    }
508
509    #[turbo_tasks::function]
510    async fn asset_path(
511        &self,
512        content_hash: RcStr,
513        original_asset_ident: Vc<AssetIdent>,
514    ) -> Result<Vc<FileSystemPath>> {
515        let source_path = original_asset_ident.path().await?;
516        let basename = source_path.file_name();
517        let asset_path = match source_path.extension_ref() {
518            Some(ext) => format!(
519                "{basename}.{content_hash}.{ext}",
520                basename = &basename[..basename.len() - ext.len() - 1],
521                content_hash = &content_hash[..8]
522            ),
523            None => format!(
524                "{basename}.{content_hash}",
525                content_hash = &content_hash[..8]
526            ),
527        };
528        Ok(self.asset_root_path.join(&asset_path)?.cell())
529    }
530
531    #[turbo_tasks::function]
532    fn is_hot_module_replacement_enabled(&self) -> Vc<bool> {
533        Vc::cell(self.enable_hot_module_replacement)
534    }
535
536    #[turbo_tasks::function]
537    fn chunking_configs(&self) -> Result<Vc<ChunkingConfigs>> {
538        Ok(Vc::cell(self.chunking_configs.iter().cloned().collect()))
539    }
540
541    #[turbo_tasks::function]
542    fn should_use_file_source_map_uris(&self) -> Vc<bool> {
543        Vc::cell(self.should_use_file_source_map_uris)
544    }
545
546    #[turbo_tasks::function]
547    fn is_tracing_enabled(&self) -> Vc<bool> {
548        Vc::cell(self.enable_tracing)
549    }
550
551    #[turbo_tasks::function]
552    fn is_module_merging_enabled(&self) -> Vc<bool> {
553        Vc::cell(self.enable_module_merging)
554    }
555
556    #[turbo_tasks::function]
557    fn is_dynamic_chunk_content_loading_enabled(&self) -> Vc<bool> {
558        Vc::cell(self.enable_dynamic_chunk_content_loading)
559    }
560
561    #[turbo_tasks::function]
562    pub fn minify_type(&self) -> Vc<MinifyType> {
563        self.minify_type.cell()
564    }
565
566    #[turbo_tasks::function]
567    async fn chunk_group(
568        self: ResolvedVc<Self>,
569        ident: Vc<AssetIdent>,
570        chunk_group: ChunkGroup,
571        module_graph: Vc<ModuleGraph>,
572        availability_info: AvailabilityInfo,
573    ) -> Result<Vc<ChunkGroupResult>> {
574        let span = tracing::info_span!("chunking", name = ident.to_string().await?.to_string());
575        async move {
576            let this = self.await?;
577            let modules = chunk_group.entries();
578            let input_availability_info = availability_info;
579            let MakeChunkGroupResult {
580                chunks,
581                availability_info,
582            } = make_chunk_group(
583                modules,
584                module_graph,
585                ResolvedVc::upcast(self),
586                input_availability_info,
587            )
588            .await?;
589
590            let mut assets = chunks
591                .iter()
592                .map(|chunk| self.generate_chunk(**chunk).to_resolved())
593                .try_join()
594                .await?;
595
596            if this.enable_hot_module_replacement {
597                let mut ident = ident;
598                match input_availability_info {
599                    AvailabilityInfo::Root => {}
600                    AvailabilityInfo::Untracked => {
601                        ident = ident.with_modifier(rcstr!("untracked"));
602                    }
603                    AvailabilityInfo::Complete { available_modules } => {
604                        ident =
605                            ident.with_modifier(available_modules.hash().await?.to_string().into());
606                    }
607                }
608                assets.push(
609                    self.generate_chunk_list_register_chunk(
610                        ident,
611                        EvaluatableAssets::empty(),
612                        Vc::cell(assets.clone()),
613                        EcmascriptDevChunkListSource::Dynamic,
614                    )
615                    .to_resolved()
616                    .await?,
617                );
618            }
619
620            Ok(ChunkGroupResult {
621                assets: ResolvedVc::cell(assets),
622                availability_info,
623            }
624            .cell())
625        }
626        .instrument(span)
627        .await
628    }
629
630    #[turbo_tasks::function]
631    async fn evaluated_chunk_group(
632        self: ResolvedVc<Self>,
633        ident: Vc<AssetIdent>,
634        chunk_group: ChunkGroup,
635        module_graph: Vc<ModuleGraph>,
636        availability_info: AvailabilityInfo,
637    ) -> Result<Vc<ChunkGroupResult>> {
638        let span = {
639            let ident = ident.to_string().await?.to_string();
640            tracing::info_span!("chunking", chunking_type = "evaluated", ident = ident)
641        };
642        async move {
643            let this = self.await?;
644
645            let entries = chunk_group.entries();
646
647            let MakeChunkGroupResult {
648                chunks,
649                availability_info,
650            } = make_chunk_group(
651                entries,
652                module_graph,
653                ResolvedVc::upcast(self),
654                availability_info,
655            )
656            .await?;
657
658            let mut assets: Vec<ResolvedVc<Box<dyn OutputAsset>>> = chunks
659                .iter()
660                .map(|chunk| self.generate_chunk(**chunk).to_resolved())
661                .try_join()
662                .await?;
663
664            let other_assets = Vc::cell(assets.clone());
665
666            let entries = Vc::cell(
667                chunk_group
668                    .entries()
669                    .map(|m| {
670                        ResolvedVc::try_downcast::<Box<dyn EvaluatableAsset>>(m)
671                            .context("evaluated_chunk_group entries must be evaluatable assets")
672                    })
673                    .collect::<Result<Vec<_>>>()?,
674            );
675
676            if this.enable_hot_module_replacement {
677                assets.push(
678                    self.generate_chunk_list_register_chunk(
679                        ident,
680                        entries,
681                        other_assets,
682                        EcmascriptDevChunkListSource::Entry,
683                    )
684                    .to_resolved()
685                    .await?,
686                );
687            }
688
689            assets.push(
690                self.generate_evaluate_chunk(ident, other_assets, entries, module_graph)
691                    .to_resolved()
692                    .await?,
693            );
694
695            Ok(ChunkGroupResult {
696                assets: ResolvedVc::cell(assets),
697                availability_info,
698            }
699            .cell())
700        }
701        .instrument(span)
702        .await
703    }
704
705    #[turbo_tasks::function]
706    fn entry_chunk_group(
707        self: Vc<Self>,
708        _path: FileSystemPath,
709        _evaluatable_assets: Vc<EvaluatableAssets>,
710        _module_graph: Vc<ModuleGraph>,
711        _extra_chunks: Vc<OutputAssets>,
712        _availability_info: AvailabilityInfo,
713    ) -> Result<Vc<EntryChunkGroupResult>> {
714        bail!("Browser chunking context does not support entry chunk groups")
715    }
716
717    #[turbo_tasks::function]
718    fn chunk_item_id_from_ident(&self, ident: Vc<AssetIdent>) -> Vc<ModuleId> {
719        self.module_id_strategy.get_module_id(ident)
720    }
721
722    #[turbo_tasks::function]
723    async fn async_loader_chunk_item(
724        self: Vc<Self>,
725        module: Vc<Box<dyn ChunkableModule>>,
726        module_graph: Vc<ModuleGraph>,
727        availability_info: AvailabilityInfo,
728    ) -> Result<Vc<Box<dyn ChunkItem>>> {
729        Ok(if self.await?.manifest_chunks {
730            let manifest_asset =
731                ManifestAsyncModule::new(module, module_graph, Vc::upcast(self), availability_info);
732            Vc::upcast(ManifestLoaderChunkItem::new(
733                manifest_asset,
734                module_graph,
735                Vc::upcast(self),
736            ))
737        } else {
738            let module = AsyncLoaderModule::new(module, Vc::upcast(self), availability_info);
739            Vc::upcast(module.as_chunk_item(module_graph, Vc::upcast(self)))
740        })
741    }
742
743    #[turbo_tasks::function]
744    async fn async_loader_chunk_item_id(
745        self: Vc<Self>,
746        module: Vc<Box<dyn ChunkableModule>>,
747    ) -> Result<Vc<ModuleId>> {
748        Ok(if self.await?.manifest_chunks {
749            self.chunk_item_id_from_ident(ManifestLoaderChunkItem::asset_ident_for(module))
750        } else {
751            self.chunk_item_id_from_ident(AsyncLoaderModule::asset_ident_for(module))
752        })
753    }
754
755    #[turbo_tasks::function]
756    async fn module_export_usage(
757        self: Vc<Self>,
758        module: ResolvedVc<Box<dyn Module>>,
759    ) -> Result<Vc<ModuleExportUsage>> {
760        if let Some(export_usage) = self.await?.export_usage {
761            Ok(export_usage.await?.used_exports(module).await?)
762        } else {
763            // In development mode, we don't have export usage info, so we assume all exports are
764            // used.
765            Ok(ModuleExportUsage::all())
766        }
767    }
768}