Skip to main content

turbopack_browser/
chunking_context.rs

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