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