Skip to main content

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