Skip to main content

turbopack_browser/
chunking_context.rs

1use anyhow::{Context, Result, bail};
2use async_trait::async_trait;
3use tracing::Instrument;
4use turbo_rcstr::{RcStr, rcstr};
5use turbo_tasks::{
6    FxIndexMap, FxIndexSet, ResolvedVc, TryJoinIterExt, Upcast, ValueToString, ValueToStringRef, Vc,
7};
8use turbo_tasks_fs::FileSystemPath;
9use turbo_tasks_hash::HashAlgorithm;
10use turbopack_core::{
11    asset::{Asset, AssetContent},
12    chunk::{
13        AssetSuffix, Chunk, ChunkGroupResult, ChunkItem, ChunkItemOrBatchWithAsyncModuleInfo,
14        ChunkItemWithAsyncModuleInfo, ChunkLoadRetry, ChunkType, ChunkableModule, ChunkingConfig,
15        ChunkingConfigs, ChunkingContext, ContentHashing, CrossOrigin, EntryChunkGroupResult,
16        EvaluatableAsset, EvaluatableAssets, HmrChunkListSource, MinifyType, SourceMapSourceType,
17        SourceMapsType, UnusedReferences, UrlBehavior, WorkerConfigurationOptions,
18        availability_info::AvailabilityInfo,
19        chunk_group::{MakeChunkGroupResult, make_chunk_group},
20        chunk_id_strategy::ModuleIdStrategy,
21    },
22    environment::{ChunkLoading, Environment},
23    ident::AssetIdent,
24    issue::{Issue, IssueExt, IssueSeverity, IssueStage, StyledString},
25    module::Module,
26    module_graph::{
27        ModuleGraph,
28        binding_usage_info::{BindingUsageInfo, ModuleExportUsage},
29        chunk_group_info::ChunkGroup,
30    },
31    output::{ExpandOutputAssetsInput, OutputAsset, OutputAssets, expand_output_assets},
32};
33use turbopack_ecmascript::{
34    async_chunk::module::AsyncLoaderModule,
35    chunk::{
36        EcmascriptChunk, EcmascriptChunkContent, EcmascriptChunkPlaceable, EcmascriptChunkType,
37    },
38    manifest::{chunk_asset::ManifestAsyncModule, loader_module::ManifestLoaderModule},
39};
40use turbopack_ecmascript_runtime::RuntimeType;
41
42use crate::ecmascript::{
43    chunk::EcmascriptBrowserChunk,
44    evaluate::{
45        chunk::EcmascriptBrowserEvaluateChunk, runtime::EcmascriptBrowserRuntimeChunk,
46        single_entry_chunk::EcmascriptBrowserSingleEntryChunk,
47    },
48    list::asset::{EcmascriptDevChunkList, EcmascriptDevChunkListSource},
49    worker::EcmascriptBrowserWorkerEntrypoint,
50};
51
52#[turbo_tasks::value]
53#[derive(Debug, Clone, Copy, Hash)]
54pub enum CurrentChunkMethod {
55    StringLiteral,
56    DocumentCurrentScript,
57}
58
59pub const CURRENT_CHUNK_METHOD_DOCUMENT_CURRENT_SCRIPT_EXPR: &str =
60    "typeof document === \"object\" ? document.currentScript : undefined";
61
62pub struct BrowserChunkingContextBuilder {
63    chunking_context: BrowserChunkingContext,
64}
65
66impl BrowserChunkingContextBuilder {
67    pub fn name(mut self, name: RcStr) -> Self {
68        self.chunking_context.name = Some(name);
69        self
70    }
71
72    pub fn hot_module_replacement(mut self) -> Self {
73        self.chunking_context.enable_hot_module_replacement = true;
74        self
75    }
76
77    pub fn source_map_source_type(mut self, source_map_source_type: SourceMapSourceType) -> Self {
78        self.chunking_context.source_map_source_type = source_map_source_type;
79        self
80    }
81
82    pub fn nested_async_availability(mut self, enable_nested_async_availability: bool) -> Self {
83        self.chunking_context.enable_nested_async_availability = enable_nested_async_availability;
84        self
85    }
86
87    pub fn module_merging(mut self, enable_module_merging: bool) -> Self {
88        self.chunking_context.enable_module_merging = enable_module_merging;
89        self
90    }
91
92    pub fn dynamic_chunk_content_loading(
93        mut self,
94        enable_dynamic_chunk_content_loading: bool,
95    ) -> Self {
96        self.chunking_context.enable_dynamic_chunk_content_loading =
97            enable_dynamic_chunk_content_loading;
98        self
99    }
100
101    pub fn asset_base_path(mut self, asset_base_path: Option<RcStr>) -> Self {
102        self.chunking_context.asset_base_path = asset_base_path;
103        self
104    }
105
106    pub fn service_worker_scope_base_path(
107        mut self,
108        service_worker_scope_base_path: Option<RcStr>,
109    ) -> Self {
110        self.chunking_context.service_worker_scope_base_path = service_worker_scope_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 worker_asset_prefix(mut self, worker_asset_prefix: Option<RcStr>) -> Self {
120        self.chunking_context.worker_asset_prefix = worker_asset_prefix;
121        self
122    }
123
124    pub fn asset_suffix(mut self, asset_suffix: ResolvedVc<AssetSuffix>) -> Self {
125        self.chunking_context.asset_suffix = Some(asset_suffix);
126        self
127    }
128
129    pub fn runtime_type(mut self, runtime_type: RuntimeType) -> Self {
130        self.chunking_context.runtime_type = runtime_type;
131        self
132    }
133
134    pub fn manifest_chunks(mut self, manifest_chunks: bool) -> Self {
135        self.chunking_context.manifest_chunks = manifest_chunks;
136        self
137    }
138
139    pub fn minify_type(mut self, minify_type: MinifyType) -> Self {
140        self.chunking_context.minify_type = minify_type;
141        self
142    }
143
144    pub fn source_maps(mut self, source_maps: SourceMapsType) -> Self {
145        self.chunking_context.source_maps_type = source_maps;
146        self
147    }
148
149    pub fn current_chunk_method(mut self, method: CurrentChunkMethod) -> Self {
150        self.chunking_context.current_chunk_method = method;
151        self
152    }
153
154    pub fn module_id_strategy(mut self, module_id_strategy: ResolvedVc<ModuleIdStrategy>) -> Self {
155        self.chunking_context.module_id_strategy = Some(module_id_strategy);
156        self
157    }
158
159    pub fn export_usage(mut self, export_usage: Option<ResolvedVc<BindingUsageInfo>>) -> Self {
160        self.chunking_context.export_usage = export_usage;
161        self
162    }
163
164    pub fn unused_references(mut self, unused_references: ResolvedVc<UnusedReferences>) -> Self {
165        self.chunking_context.unused_references = Some(unused_references);
166        self
167    }
168
169    pub fn debug_ids(mut self, debug_ids: bool) -> Self {
170        self.chunking_context.debug_ids = debug_ids;
171        self
172    }
173
174    pub fn shared_runtime(mut self, shared_runtime: bool) -> Self {
175        self.chunking_context.shared_runtime = shared_runtime;
176        self
177    }
178
179    /// Marks this context as being shared by multiple independent module graphs (e.g. per-page
180    /// graphs), each of which only sees part of what is written to `chunk_root_path`.
181    ///
182    /// The runtime chunk is emitted to a fixed path, so every graph sharing this context writes
183    /// the same file. Optional runtime features must therefore not be decided from a single
184    /// graph: one graph would omit a helper that another graph's chunks call, and which variant
185    /// lands on disk depends on emission order.
186    pub fn shared_runtime_chunk(mut self, shared_runtime_chunk: bool) -> Self {
187        self.chunking_context.shared_runtime_chunk = shared_runtime_chunk;
188        self
189    }
190
191    pub fn should_use_absolute_url_references(
192        mut self,
193        should_use_absolute_url_references: bool,
194    ) -> Self {
195        self.chunking_context.should_use_absolute_url_references =
196            should_use_absolute_url_references;
197        self
198    }
199
200    pub fn asset_root_path_override(mut self, tag: RcStr, path: FileSystemPath) -> Self {
201        self.chunking_context.asset_root_paths.insert(tag, path);
202        self
203    }
204
205    pub fn client_roots_override(mut self, tag: RcStr, path: FileSystemPath) -> Self {
206        self.chunking_context.client_roots.insert(tag, path);
207        self
208    }
209
210    pub fn asset_base_path_override(mut self, tag: RcStr, path: RcStr) -> Self {
211        self.chunking_context.asset_base_paths.insert(tag, path);
212        self
213    }
214
215    pub fn url_behavior_override(mut self, tag: RcStr, behavior: UrlBehavior) -> Self {
216        self.chunking_context.url_behaviors.insert(tag, behavior);
217        self
218    }
219
220    pub fn default_url_behavior(mut self, behavior: UrlBehavior) -> Self {
221        self.chunking_context.default_url_behavior = Some(behavior);
222        self
223    }
224
225    pub fn chunking_config<T>(mut self, ty: ResolvedVc<T>, chunking_config: ChunkingConfig) -> Self
226    where
227        T: Upcast<Box<dyn ChunkType>>,
228    {
229        self.chunking_context
230            .chunking_configs
231            .push((ResolvedVc::upcast_non_strict(ty), chunking_config));
232        self
233    }
234
235    pub fn chunk_content_hashing(mut self, content_hashing: ContentHashing) -> Self {
236        self.chunking_context.chunk_content_hashing = Some(content_hashing);
237        self
238    }
239
240    pub fn asset_content_hashing(mut self, content_hashing: ContentHashing) -> Self {
241        self.chunking_context.asset_content_hashing = content_hashing;
242        self
243    }
244
245    pub fn worker_forwarded_globals(mut self, globals: Vec<RcStr>) -> Self {
246        self.chunking_context
247            .worker_forwarded_globals
248            .extend(globals);
249        self
250    }
251
252    pub fn chunk_loading_global(mut self, chunk_loading_global: RcStr) -> Self {
253        self.chunking_context.chunk_loading_global = Some(chunk_loading_global);
254        self
255    }
256
257    pub fn hash_salt(mut self, salt: ResolvedVc<RcStr>) -> Self {
258        self.chunking_context.hash_salt = salt;
259        self
260    }
261
262    pub fn cross_origin(mut self, cross_origin: CrossOrigin) -> Self {
263        self.chunking_context.cross_origin = cross_origin;
264        self
265    }
266
267    pub fn chunk_load_retry(mut self, chunk_load_retry: ChunkLoadRetry) -> Self {
268        self.chunking_context.chunk_load_retry = chunk_load_retry;
269        self
270    }
271
272    pub async fn single_chunk(mut self) -> Result<Self> {
273        self.chunking_context.single_chunk = true;
274        // Force every ECMAScript chunk item into a single output chunk.
275        let ecmascript_ty: ResolvedVc<Box<dyn ChunkType>> =
276            ResolvedVc::upcast(Vc::<EcmascriptChunkType>::default().to_resolved().await?);
277        self.chunking_context.chunking_configs.push((
278            ecmascript_ty,
279            ChunkingConfig {
280                min_chunk_size: usize::MAX,
281                max_chunk_count_per_group: 1,
282                max_merge_chunk_size: usize::MAX,
283                ..Default::default()
284            },
285        ));
286        Ok(self)
287    }
288
289    pub fn build(self) -> Vc<BrowserChunkingContext> {
290        BrowserChunkingContext::cell(self.chunking_context)
291    }
292}
293
294/// A chunking context for development mode.
295///
296/// It uses readable filenames and module ids to improve development.
297/// It also uses a chunking heuristic that is incremental and cacheable.
298/// It splits "node_modules" separately as these are less likely to change
299/// during development
300#[turbo_tasks::value]
301#[derive(Debug, Clone)]
302pub struct BrowserChunkingContext {
303    name: Option<RcStr>,
304    /// The root path of the project
305    root_path: FileSystemPath,
306    /// The strategy to use for generating source map source uris
307    source_map_source_type: SourceMapSourceType,
308    /// This path is used to compute the url to request chunks from
309    output_root: FileSystemPath,
310    /// The relative path from the output_root to the root_path.
311    output_root_to_root_path: RcStr,
312    /// This path is used to compute the url to request assets from
313    client_root: FileSystemPath,
314    /// This path is used to compute the url to request chunks or assets from
315    #[bincode(with = "turbo_bincode::indexmap")]
316    client_roots: FxIndexMap<RcStr, FileSystemPath>,
317    /// Chunks are placed at this path
318    chunk_root_path: FileSystemPath,
319    /// Static assets are placed at this path
320    asset_root_path: FileSystemPath,
321    /// Static assets are placed at this path
322    #[bincode(with = "turbo_bincode::indexmap")]
323    asset_root_paths: FxIndexMap<RcStr, FileSystemPath>,
324    /// Base path that will be prepended to all chunk URLs when loading them.
325    /// This path will not appear in chunk paths or chunk data.
326    chunk_base_path: Option<RcStr>,
327    /// Base path for Web Worker URLs (the entrypoint and the module chunks
328    /// loaded inside the worker). When `Some`, overrides `chunk_base_path`
329    /// for those URLs. Mirrors webpack's `output.workerPublicPath`. Primary
330    /// use case: keep Worker URLs same-origin when
331    /// `chunk_base_path`/`assetPrefix` points to a cross-origin CDN
332    /// (browsers reject cross-origin Worker construction, and the worker
333    /// bootstrap rejects cross-origin module chunks).
334    worker_asset_prefix: Option<RcStr>,
335    /// Suffix that will be appended to all chunk URLs when loading them.
336    /// This path will not appear in chunk paths or chunk data.
337    asset_suffix: Option<ResolvedVc<AssetSuffix>>,
338    /// URL prefix that will be prepended to all static asset URLs when loading
339    /// them.
340    asset_base_path: Option<RcStr>,
341    /// URL prefix that will be prepended to all static asset URLs when loading
342    /// them.
343    #[bincode(with = "turbo_bincode::indexmap")]
344    asset_base_paths: FxIndexMap<RcStr, RcStr>,
345    /// This is the base path used to generate the service worker scope, it is
346    /// not used for output subdirectory logic
347    service_worker_scope_base_path: Option<RcStr>,
348    /// URL behavior overrides for different tags.
349    #[bincode(with = "turbo_bincode::indexmap")]
350    url_behaviors: FxIndexMap<RcStr, UrlBehavior>,
351    /// Default URL behavior when no tag-specific override is found.
352    default_url_behavior: Option<UrlBehavior>,
353    /// Enable HMR for this chunking
354    enable_hot_module_replacement: bool,
355    /// Enable nested async availability for this chunking
356    enable_nested_async_availability: bool,
357    /// Enable module merging
358    enable_module_merging: bool,
359    /// Enable dynamic chunk content loading.
360    enable_dynamic_chunk_content_loading: bool,
361    /// Enable debug IDs for chunks and source maps.
362    debug_ids: bool,
363    /// Share the browser runtime across routes as a single `runtime.js` asset and expose each
364    /// entrypoint's chunk group bootstrap params via
365    /// `ChunkGroupResult.chunk_group_bootstrap_params`.
366    shared_runtime: bool,
367    /// Whether the runtime chunk is shared with other module graphs using this context.
368    /// See [`BrowserChunkingContextBuilder::shared_runtime_chunk`].
369    shared_runtime_chunk: bool,
370    /// The environment chunks will be evaluated in.
371    environment: ResolvedVc<Environment>,
372    /// The kind of runtime to include in the output.
373    runtime_type: RuntimeType,
374    /// Whether to minify resulting chunks
375    minify_type: MinifyType,
376    /// Whether content hashing is enabled for chunk filenames.
377    chunk_content_hashing: Option<ContentHashing>,
378    /// Content hashing for asset filenames.
379    asset_content_hashing: ContentHashing,
380    /// Whether to generate source maps
381    source_maps_type: SourceMapsType,
382    /// Method to use when figuring out the current chunk src
383    current_chunk_method: CurrentChunkMethod,
384    /// Whether to use manifest chunks for lazy compilation
385    manifest_chunks: bool,
386    /// The module id strategy to use
387    module_id_strategy: Option<ResolvedVc<ModuleIdStrategy>>,
388    /// The module export usage info, if available.
389    export_usage: Option<ResolvedVc<BindingUsageInfo>>,
390    /// Which references are unused and should be skipped (e.g. during codegen).
391    unused_references: Option<ResolvedVc<UnusedReferences>>,
392    /// The chunking configs
393    chunking_configs: Vec<(ResolvedVc<Box<dyn ChunkType>>, ChunkingConfig)>,
394    /// Whether to use absolute URLs for static assets (e.g. in CSS: `url("/absolute/path")`)
395    should_use_absolute_url_references: bool,
396    /// Global variable names to forward to workers (e.g. NEXT_DEPLOYMENT_ID)
397    worker_forwarded_globals: Vec<RcStr>,
398    /// The global variable name used for chunk loading.
399    /// Default: "TURBOPACK"
400    chunk_loading_global: Option<RcStr>,
401    /// Salt mixed into chunk and asset content hashes. Empty string means no salt.
402    hash_salt: ResolvedVc<RcStr>,
403    /// The crossorigin mode for dynamically loaded chunks.
404    cross_origin: CrossOrigin,
405    /// The retry policy for transient chunk load failures in the browser runtime.
406    chunk_load_retry: ChunkLoadRetry,
407    /// When enabled, module closure is inlined into a single output chunk
408    /// (no runtime chunk loading).
409    single_chunk: bool,
410}
411
412impl BrowserChunkingContext {
413    pub fn builder(
414        root_path: FileSystemPath,
415        output_root: FileSystemPath,
416        output_root_to_root_path: RcStr,
417        client_root: FileSystemPath,
418        chunk_root_path: FileSystemPath,
419        asset_root_path: FileSystemPath,
420        environment: ResolvedVc<Environment>,
421        runtime_type: RuntimeType,
422    ) -> BrowserChunkingContextBuilder {
423        BrowserChunkingContextBuilder {
424            chunking_context: BrowserChunkingContext {
425                name: None,
426                root_path,
427                output_root,
428                output_root_to_root_path,
429                client_root,
430                client_roots: Default::default(),
431                chunk_root_path,
432                source_map_source_type: SourceMapSourceType::TurbopackUri,
433                asset_root_path,
434                asset_root_paths: Default::default(),
435                chunk_base_path: None,
436                worker_asset_prefix: None,
437                asset_suffix: None,
438                asset_base_path: None,
439                asset_base_paths: Default::default(),
440                service_worker_scope_base_path: None,
441                url_behaviors: Default::default(),
442                default_url_behavior: None,
443                enable_hot_module_replacement: false,
444                enable_nested_async_availability: false,
445                enable_module_merging: false,
446                enable_dynamic_chunk_content_loading: false,
447                debug_ids: false,
448                shared_runtime: false,
449                shared_runtime_chunk: false,
450                environment,
451                runtime_type,
452                minify_type: MinifyType::NoMinify,
453                chunk_content_hashing: None,
454                asset_content_hashing: ContentHashing::Direct { length: 13 },
455                source_maps_type: SourceMapsType::Full,
456                current_chunk_method: CurrentChunkMethod::StringLiteral,
457                manifest_chunks: false,
458                module_id_strategy: None,
459                export_usage: None,
460                unused_references: None,
461                chunking_configs: Default::default(),
462                should_use_absolute_url_references: false,
463                worker_forwarded_globals: vec![],
464                chunk_loading_global: Default::default(),
465                hash_salt: ResolvedVc::cell(RcStr::default()),
466                cross_origin: Default::default(),
467                chunk_load_retry: Default::default(),
468                single_chunk: false,
469            },
470        }
471    }
472}
473impl BrowserChunkingContext {
474    fn generate_evaluate_chunk(
475        self: Vc<Self>,
476        ident: Vc<AssetIdent>,
477        other_chunks: Vc<OutputAssets>,
478        evaluatable_assets: Vc<EvaluatableAssets>,
479        module_graph: Vc<ModuleGraph>,
480    ) -> Vc<EcmascriptBrowserEvaluateChunk> {
481        EcmascriptBrowserEvaluateChunk::new(
482            self,
483            ident,
484            other_chunks,
485            evaluatable_assets,
486            module_graph,
487        )
488    }
489
490    /// The shared browser runtime chunk for this chunking context.
491    ///
492    /// Returns the same asset every time: [`EcmascriptBrowserRuntimeChunk::new`] is a
493    /// `#[turbo_tasks::function]` memoized on `(chunking_context, include_async_module_runtime)`.
494    pub(crate) async fn generate_runtime_chunk(
495        self: Vc<Self>,
496        module_graph: Vc<ModuleGraph>,
497    ) -> Result<Vc<EcmascriptBrowserRuntimeChunk>> {
498        // Only omit the machinery when this graph sees every chunk that shares the runtime. With
499        // per-page graphs it doesn't, and this asset is emitted to a fixed path, so a graph
500        // without async modules would otherwise strip a helper another page's chunks call.
501        let include_async_module_runtime =
502            self.await?.shared_runtime_chunk || !module_graph.async_module_info().await?.is_empty();
503        Ok(EcmascriptBrowserRuntimeChunk::new(
504            self,
505            include_async_module_runtime,
506        ))
507    }
508
509    fn generate_chunk_list_register_chunk(
510        self: Vc<Self>,
511        ident: Vc<AssetIdent>,
512        evaluatable_assets: Vc<EvaluatableAssets>,
513        other_chunks: Vc<OutputAssets>,
514        source: EcmascriptDevChunkListSource,
515    ) -> Vc<Box<dyn OutputAsset>> {
516        Vc::upcast(EcmascriptDevChunkList::new(
517            self,
518            ident,
519            evaluatable_assets,
520            other_chunks,
521            source,
522        ))
523    }
524    async fn generate_chunk(
525        self: Vc<Self>,
526        chunk: ResolvedVc<Box<dyn Chunk>>,
527    ) -> Result<ResolvedVc<Box<dyn OutputAsset>>> {
528        Ok(
529            if let Some(ecmascript_chunk) = ResolvedVc::try_downcast_type::<EcmascriptChunk>(chunk)
530            {
531                ResolvedVc::upcast(
532                    EcmascriptBrowserChunk::new(self, *ecmascript_chunk)
533                        .to_resolved()
534                        .await?,
535                )
536            } else if let Some(output_asset) =
537                ResolvedVc::try_sidecast::<Box<dyn OutputAsset>>(chunk)
538            {
539                output_asset
540            } else {
541                bail!("Unable to generate output asset for chunk");
542            },
543        )
544    }
545}
546
547#[turbo_tasks::value_impl]
548impl BrowserChunkingContext {
549    #[turbo_tasks::function]
550    pub fn current_chunk_method(&self) -> Vc<CurrentChunkMethod> {
551        self.current_chunk_method.cell()
552    }
553
554    #[turbo_tasks::function]
555    pub fn hash_salt(&self) -> Vc<RcStr> {
556        *self.hash_salt
557    }
558
559    /// Returns the kind of runtime to include in output chunks.
560    ///
561    /// This is defined directly on `BrowserChunkingContext` so it is zero-cost
562    /// when `RuntimeType` has a single variant.
563    #[turbo_tasks::function]
564    pub fn runtime_type(&self) -> Vc<RuntimeType> {
565        self.runtime_type.cell()
566    }
567
568    /// Whether the browser runtime is shared across routes (as a single `runtime.js` asset) and
569    /// the chunk-group bootstrap is inlined by the consumer. When `false`, the runtime is emitted
570    /// inline in each route's evaluate chunk (the pre-shared-runtime behavior).
571    #[turbo_tasks::function]
572    pub fn shared_runtime(&self) -> Vc<bool> {
573        Vc::cell(self.shared_runtime)
574    }
575
576    /// Whether the runtime chunk is shared with other module graphs using this context, meaning no
577    /// single graph may decide which optional runtime features to omit.
578    /// See [`BrowserChunkingContextBuilder::shared_runtime_chunk`].
579    #[turbo_tasks::function]
580    pub fn shared_runtime_chunk(&self) -> Vc<bool> {
581        Vc::cell(self.shared_runtime_chunk)
582    }
583
584    /// Returns the asset base path.
585    #[turbo_tasks::function]
586    pub fn chunk_base_path(&self) -> Vc<Option<RcStr>> {
587        Vc::cell(self.chunk_base_path.clone())
588    }
589
590    /// Returns the asset suffix path.
591    #[turbo_tasks::function]
592    pub fn asset_suffix(&self) -> Vc<AssetSuffix> {
593        if let Some(asset_suffix) = self.asset_suffix {
594            *asset_suffix
595        } else {
596            AssetSuffix::None.cell()
597        }
598    }
599
600    /// Returns the source map type.
601    #[turbo_tasks::function]
602    pub fn source_maps_type(&self) -> Vc<SourceMapsType> {
603        self.source_maps_type.cell()
604    }
605
606    /// Returns the minify type.
607    #[turbo_tasks::function]
608    pub fn minify_type(&self) -> Vc<MinifyType> {
609        self.minify_type.cell()
610    }
611
612    /// Returns the chunk path information.
613    #[turbo_tasks::function]
614    fn chunk_path_info(&self) -> Vc<ChunkPathInfo> {
615        ChunkPathInfo {
616            root_path: self.root_path.clone(),
617            chunk_root_path: self.chunk_root_path.clone(),
618            chunk_content_hashing: self.chunk_content_hashing,
619        }
620        .cell()
621    }
622
623    /// Returns the chunk loading global variable name.
624    /// Defaults to "TURBOPACK" if not set.
625    #[turbo_tasks::function]
626    pub fn chunk_loading_global(&self) -> Vc<RcStr> {
627        Vc::cell(
628            self.chunk_loading_global
629                .clone()
630                .unwrap_or_else(|| rcstr!("TURBOPACK")),
631        )
632    }
633
634    #[turbo_tasks::function]
635    pub fn cross_origin(&self) -> Vc<CrossOrigin> {
636        self.cross_origin.cell()
637    }
638
639    #[turbo_tasks::function]
640    pub fn chunk_load_retry(&self) -> Vc<ChunkLoadRetry> {
641        self.chunk_load_retry.cell()
642    }
643
644    /// Whether the ECMAScript chunking config emits component chunks alongside merged chunks.
645    #[turbo_tasks::function]
646    pub async fn generate_component_chunks(&self) -> Result<Vc<bool>> {
647        let ecmascript_ty: ResolvedVc<Box<dyn ChunkType>> =
648            ResolvedVc::upcast(Vc::<EcmascriptChunkType>::default().to_resolved().await?);
649        Ok(Vc::cell(self.chunking_configs.iter().any(
650            |(ty, config)| *ty == ecmascript_ty && config.generate_component_chunks,
651        )))
652    }
653}
654
655#[turbo_tasks::value_impl]
656impl ChunkingContext for BrowserChunkingContext {
657    #[turbo_tasks::function]
658    fn name(&self) -> Vc<RcStr> {
659        if let Some(name) = &self.name {
660            Vc::cell(name.clone())
661        } else {
662            Vc::cell(rcstr!("unknown"))
663        }
664    }
665
666    #[turbo_tasks::function]
667    fn root_path(&self) -> Vc<FileSystemPath> {
668        self.root_path.clone().cell()
669    }
670
671    #[turbo_tasks::function]
672    fn output_root(&self) -> Vc<FileSystemPath> {
673        self.output_root.clone().cell()
674    }
675
676    #[turbo_tasks::function]
677    fn output_root_to_root_path(&self) -> Vc<RcStr> {
678        Vc::cell(self.output_root_to_root_path.clone())
679    }
680
681    #[turbo_tasks::function]
682    fn environment(&self) -> Vc<Environment> {
683        *self.environment
684    }
685
686    #[turbo_tasks::function]
687    fn chunk_root_path(&self) -> Vc<FileSystemPath> {
688        self.chunk_root_path.clone().cell()
689    }
690
691    #[turbo_tasks::function]
692    async fn chunk_path(
693        self: Vc<Self>,
694        asset: Option<Vc<Box<dyn Asset>>>,
695        ident: Vc<AssetIdent>,
696        prefix: Option<RcStr>,
697        extension: RcStr,
698    ) -> Result<Vc<FileSystemPath>> {
699        debug_assert!(
700            extension.starts_with("."),
701            "`extension` should include the leading '.', got '{extension}'"
702        );
703        let ChunkPathInfo {
704            chunk_root_path,
705            chunk_content_hashing,
706            root_path,
707        } = &*self.chunk_path_info().await?;
708        let name = match *chunk_content_hashing {
709            None => {
710                ident
711                    .output_name(root_path.clone(), prefix, extension)
712                    .owned()
713                    .await?
714            }
715            Some(ContentHashing::Direct { length }) => {
716                let Some(asset) = asset else {
717                    bail!("chunk_path requires an asset when content hashing is enabled");
718                };
719                let hash = asset
720                    .content()
721                    .content_hash(self.hash_salt(), HashAlgorithm::Xxh3Hash128Base38)
722                    .await?;
723                let hash = hash.as_ref().context(
724                    "chunk_path requires an asset with file content when content hashing is \
725                     enabled",
726                )?;
727                let hash = &hash[..length as usize];
728                if let Some(prefix) = prefix {
729                    format!("{prefix}-{hash}{extension}").into()
730                } else {
731                    format!("{hash}{extension}").into()
732                }
733            }
734        };
735        Ok(chunk_root_path.join(&name)?.cell())
736    }
737
738    #[turbo_tasks::function]
739    async fn asset_url(&self, ident: FileSystemPath, tag: Option<RcStr>) -> Result<Vc<RcStr>> {
740        let asset_path = ident.to_string();
741
742        let client_root = tag
743            .as_ref()
744            .and_then(|tag| self.client_roots.get(tag))
745            .unwrap_or(&self.client_root);
746
747        let asset_base_path = tag
748            .as_ref()
749            .and_then(|tag| self.asset_base_paths.get(tag))
750            .or(self.asset_base_path.as_ref());
751
752        let asset_path = asset_path
753            .strip_prefix(&format!("{}/", client_root.path))
754            .context("expected asset_path to contain client_root")?;
755
756        Ok(Vc::cell(
757            format!(
758                "{}{}",
759                asset_base_path.map(|s| s.as_str()).unwrap_or("/"),
760                asset_path
761            )
762            .into(),
763        ))
764    }
765
766    #[turbo_tasks::function]
767    fn service_worker_scope_base_path(&self) -> Vc<RcStr> {
768        Vc::cell(
769            self.service_worker_scope_base_path
770                .clone()
771                .unwrap_or_default(),
772        )
773    }
774
775    #[turbo_tasks::function]
776    fn reference_chunk_source_maps(&self, _chunk: Vc<Box<dyn OutputAsset>>) -> Vc<bool> {
777        Vc::cell(match self.source_maps_type {
778            SourceMapsType::Full => true,
779            SourceMapsType::Partial => true,
780            SourceMapsType::None => false,
781        })
782    }
783
784    #[turbo_tasks::function]
785    fn reference_module_source_maps(&self, _module: Vc<Box<dyn Module>>) -> Vc<bool> {
786        Vc::cell(match self.source_maps_type {
787            SourceMapsType::Full => true,
788            SourceMapsType::Partial => true,
789            SourceMapsType::None => false,
790        })
791    }
792
793    #[turbo_tasks::function]
794    async fn asset_path(
795        self: Vc<Self>,
796        content: Vc<AssetContent>,
797        original_asset_ident: Vc<AssetIdent>,
798        tag: Option<RcStr>,
799    ) -> Result<Vc<FileSystemPath>> {
800        let this = self.await?;
801        let ident = original_asset_ident.await?;
802        let source_path = &ident.path;
803        let basename = source_path.file_name();
804        let ContentHashing::Direct { length } = this.asset_content_hashing;
805        let hash = content
806            .content_hash(self.hash_salt(), HashAlgorithm::Xxh3Hash128Base38)
807            .await?;
808        let hash = hash
809            .as_ref()
810            .context("Missing content when trying to generate the content hash for static asset")?;
811        let short_hash = &hash[..length as usize];
812        let asset_path = match source_path.extension() {
813            Some(ext) => format!(
814                "{basename}.{short_hash}.{ext}",
815                basename = &basename[..basename.len() - ext.len() - 1],
816            ),
817            None => format!("{basename}.{short_hash}"),
818        };
819
820        let asset_root_path = tag
821            .as_ref()
822            .and_then(|tag| this.asset_root_paths.get(tag))
823            .unwrap_or(&this.asset_root_path);
824
825        Ok(asset_root_path.join(&asset_path)?.cell())
826    }
827
828    #[turbo_tasks::function]
829    fn url_behavior(&self, tag: Option<RcStr>) -> Vc<UrlBehavior> {
830        tag.as_ref()
831            .and_then(|tag| self.url_behaviors.get(tag))
832            .cloned()
833            .or_else(|| self.default_url_behavior.clone())
834            .unwrap_or(UrlBehavior {
835                suffix: AssetSuffix::Inferred,
836                static_suffix: ResolvedVc::cell(None),
837            })
838            .cell()
839    }
840
841    #[turbo_tasks::function]
842    fn chunking_configs(&self) -> Vc<ChunkingConfigs> {
843        Vc::cell(self.chunking_configs.iter().cloned().collect())
844    }
845
846    #[turbo_tasks::function]
847    fn source_map_source_type(&self) -> Vc<SourceMapSourceType> {
848        self.source_map_source_type.cell()
849    }
850
851    #[turbo_tasks::function]
852    fn is_nested_async_availability_enabled(&self) -> Vc<bool> {
853        Vc::cell(self.enable_nested_async_availability)
854    }
855
856    #[turbo_tasks::function]
857    fn is_module_merging_enabled(&self) -> Vc<bool> {
858        Vc::cell(self.enable_module_merging)
859    }
860
861    #[turbo_tasks::function]
862    fn is_dynamic_chunk_content_loading_enabled(&self) -> Vc<bool> {
863        Vc::cell(self.enable_dynamic_chunk_content_loading)
864    }
865
866    #[turbo_tasks::function]
867    pub fn minify_type(&self) -> Vc<MinifyType> {
868        self.minify_type.cell()
869    }
870
871    #[turbo_tasks::function]
872    fn should_use_absolute_url_references(&self) -> Vc<bool> {
873        Vc::cell(self.should_use_absolute_url_references)
874    }
875
876    #[turbo_tasks::function]
877    async fn chunk_group(
878        self: ResolvedVc<Self>,
879        ident: Vc<AssetIdent>,
880        chunk_group: ChunkGroup,
881        module_graph: ResolvedVc<ModuleGraph>,
882        availability_info: AvailabilityInfo,
883    ) -> Result<Vc<ChunkGroupResult>> {
884        let span = tracing::info_span!("chunking", name = display(ident.to_string().await?));
885        async move {
886            let input_availability_info = availability_info;
887            let MakeChunkGroupResult {
888                chunks,
889                references,
890                availability_info,
891            } = make_chunk_group(
892                chunk_group,
893                module_graph,
894                ResolvedVc::upcast(self),
895                input_availability_info,
896            )
897            .await?;
898
899            let chunks = chunks.await?;
900
901            let assets = chunks
902                .iter()
903                .map(|chunk| self.generate_chunk(*chunk))
904                .try_join()
905                .await?;
906
907            Ok(ChunkGroupResult {
908                assets: ResolvedVc::cell(assets),
909                referenced_assets: OutputAssets::empty_resolved(),
910                references: ResolvedVc::cell(references),
911                availability_info,
912                chunk_group_bootstrap_params: None,
913            }
914            .cell())
915        }
916        .instrument(span)
917        .await
918    }
919
920    #[turbo_tasks::function]
921    async fn evaluated_chunk_group(
922        self: ResolvedVc<Self>,
923        ident: Vc<AssetIdent>,
924        chunk_group: ChunkGroup,
925        module_graph: ResolvedVc<ModuleGraph>,
926        // Extra chunks to include in the HMR chunk list beyond what is reachable from this chunk
927        // group. Used to cover RSC client reference chunks that are built separately.
928        extra_chunks: Vc<OutputAssets>,
929        input_availability_info: AvailabilityInfo,
930    ) -> Result<Vc<ChunkGroupResult>> {
931        let span = tracing::info_span!(
932            "chunking",
933            name = display(ident.to_string().await?),
934            chunking_type = "evaluated",
935        );
936        async move {
937            let this = self.await?;
938            let MakeChunkGroupResult {
939                chunks,
940                references,
941                availability_info,
942            } = make_chunk_group(
943                chunk_group.clone(),
944                module_graph,
945                ResolvedVc::upcast(self),
946                input_availability_info,
947            )
948            .await?;
949
950            let chunks = chunks.await?;
951
952            let mut assets: Vec<ResolvedVc<Box<dyn OutputAsset>>> = chunks
953                .iter()
954                .map(|chunk| self.generate_chunk(*chunk))
955                .try_join()
956                .await?;
957
958            // The evaluate chunk loads `other_assets` as `SourceType.Runtime` (without script
959            // tags), so it must contain only the directly-generated chunks for this chunk group.
960            // `extra_chunks` are loaded separately (already in the HTML), so excluding them here
961            // prevents the runtime from blocking on a load that will never happen.
962            let other_assets = Vc::cell(assets.clone());
963
964            let entries = Vc::cell(
965                chunk_group
966                    .entries()
967                    .map(|m| {
968                        ResolvedVc::try_downcast::<Box<dyn EvaluatableAsset>>(m)
969                            .context("evaluated_chunk_group entries must be evaluatable assets")
970                    })
971                    .collect::<Result<Vec<_>>>()?,
972            );
973
974            if this.enable_hot_module_replacement {
975                // Follow references (async loaders) to get actual dynamic component chunks, so
976                // the single HMR chunk list covers all lazily-loaded modules.
977                // inner=false: we only follow Reference inputs transitively, not Asset inputs,
978                // to avoid pulling in source maps and other asset-adjacent files that can't be
979                // reloaded by the DOM backend (which only handles CSS chunks via reloadChunk).
980                let all_dynamic_chunks = expand_output_assets(
981                    references
982                        .iter()
983                        .copied()
984                        .map(ExpandOutputAssetsInput::Reference)
985                        .chain(assets.iter().copied().map(ExpandOutputAssetsInput::Asset)),
986                    false,
987                )
988                .await?;
989
990                // Combine direct chunks, transitively-reachable dynamic chunks, and any caller-
991                // provided extras (e.g. RSC client reference chunks built outside this graph).
992                let extra_chunks_ref = extra_chunks.await?;
993                let mut hmr_chunks: FxIndexSet<ResolvedVc<Box<dyn OutputAsset>>> =
994                    all_dynamic_chunks.into_iter().collect();
995                hmr_chunks.extend(extra_chunks_ref.iter().copied());
996                let hmr_other_assets = Vc::cell(hmr_chunks.into_iter().collect());
997
998                let ident = if let Some(input_availability_info_ident) =
999                    input_availability_info.ident().await?
1000                {
1001                    ident
1002                        .owned()
1003                        .await?
1004                        .with_modifier(input_availability_info_ident)
1005                        .into_vc()
1006                } else {
1007                    ident
1008                };
1009                assets.push(
1010                    self.generate_chunk_list_register_chunk(
1011                        ident,
1012                        entries,
1013                        hmr_other_assets,
1014                        EcmascriptDevChunkListSource::Entry,
1015                    )
1016                    .to_resolved()
1017                    .await?,
1018                );
1019            }
1020
1021            // The evaluate chunk registers this entry's chunks/modules onto the
1022            // `globalThis[TURBOPACK]` queue. When `shared_runtime` is enabled we return that chunk
1023            // group's bootstrap params for Next to inline into the HTML and skip emitting the
1024            // per-route evaluate chunk file. Only `ChunkGroup::Entry` groups (the page/app client
1025            // entries Next renders into HTML) can be inlined. When `shared_runtime` is disabled the
1026            // evaluate chunk itself carries the runtime, so it is always emitted as an asset.
1027            let evaluate_chunk = self
1028                .generate_evaluate_chunk(ident, other_assets, entries, *module_graph)
1029                .to_resolved()
1030                .await?;
1031            let chunk_group_bootstrap_params =
1032                if this.shared_runtime && matches!(chunk_group, ChunkGroup::Entry(_)) {
1033                    Some(
1034                        evaluate_chunk
1035                            .chunk_group_bootstrap_params()
1036                            .owned()
1037                            .await?,
1038                    )
1039                } else {
1040                    assets.push(ResolvedVc::upcast(evaluate_chunk));
1041                    None
1042                };
1043
1044            // The shared runtime chunk must be the LAST asset of the group. It drains
1045            // the registration queue set up by the chunks above, so it has to load
1046            // after them: on a page it is the last `<script>`, and in a web worker the
1047            // bootstrap relies on it being last to load it after the module chunks and
1048            // to keep it out of `TURBOPACK_NEXT_CHUNK_URLS` (see
1049            // `EcmascriptBrowserWorkerEntrypoint`).
1050            //
1051            // Only emitted when `shared_runtime` is enabled; otherwise the runtime lives inline in
1052            // the evaluate chunk above.
1053            if this.shared_runtime {
1054                assets.push(ResolvedVc::upcast(
1055                    self.generate_runtime_chunk(*module_graph)
1056                        .await?
1057                        .to_resolved()
1058                        .await?,
1059                ));
1060            }
1061
1062            Ok(ChunkGroupResult {
1063                assets: ResolvedVc::cell(assets),
1064                referenced_assets: OutputAssets::empty_resolved(),
1065                references: ResolvedVc::cell(references),
1066                availability_info,
1067                chunk_group_bootstrap_params,
1068            }
1069            .cell())
1070        }
1071        .instrument(span)
1072        .await
1073    }
1074
1075    #[turbo_tasks::function]
1076    async fn hmr_chunk_list(
1077        self: Vc<Self>,
1078        ident: Vc<AssetIdent>,
1079        chunks: Vc<OutputAssets>,
1080        source: HmrChunkListSource,
1081    ) -> Result<Vc<OutputAssets>> {
1082        let this = self.await?;
1083        if !this.enable_hot_module_replacement {
1084            unreachable!("hmr_chunk_list called with enable_hot_module_replacement disabled");
1085        }
1086        if chunks.await?.is_empty() {
1087            return Ok(OutputAssets::empty());
1088        }
1089        Ok(Vc::cell(vec![
1090            self.generate_chunk_list_register_chunk(
1091                ident,
1092                EvaluatableAssets::empty(),
1093                chunks,
1094                match source {
1095                    HmrChunkListSource::Entry => EcmascriptDevChunkListSource::Entry,
1096                    HmrChunkListSource::Dynamic => EcmascriptDevChunkListSource::Dynamic,
1097                },
1098            )
1099            .to_resolved()
1100            .await?,
1101        ]))
1102    }
1103
1104    #[turbo_tasks::function]
1105    async fn entry_chunk_group(
1106        self: ResolvedVc<Self>,
1107        path: FileSystemPath,
1108        chunk_group: ChunkGroup,
1109        module_graph: ResolvedVc<ModuleGraph>,
1110        extra_chunks: Vc<OutputAssets>,
1111        extra_referenced_assets: Vc<OutputAssets>,
1112        availability_info: AvailabilityInfo,
1113    ) -> Result<Vc<EntryChunkGroupResult>> {
1114        if !self.await?.single_chunk {
1115            bail!("Browser chunking context only supports entry chunk groups in single-chunk mode");
1116        }
1117
1118        if !extra_chunks.await?.is_empty() {
1119            bail!("single-chunk entry does not support extra chunks");
1120        }
1121
1122        let span = tracing::info_span!(
1123            "chunking",
1124            name = display(path.to_string_ref().await?),
1125            chunking_type = "single-chunk entry",
1126        );
1127        async move {
1128            let MakeChunkGroupResult {
1129                chunks,
1130                references,
1131                availability_info,
1132            } = make_chunk_group(
1133                chunk_group.clone(),
1134                module_graph,
1135                ResolvedVc::upcast(self),
1136                availability_info,
1137            )
1138            .await?;
1139
1140            let chunks = chunks.await?;
1141
1142            let ecmascript_chunk = chunks
1143                .iter()
1144                .find_map(|chunk| ResolvedVc::try_downcast_type::<EcmascriptChunk>(*chunk));
1145
1146            if chunks.len() != 1 || ecmascript_chunk.is_none() {
1147                SingleChunkProducedMultipleChunksIssue {
1148                    path: path.clone(),
1149                    chunk_count: chunks.len(),
1150                }
1151                .resolved_cell()
1152                .emit();
1153            }
1154
1155            // use a stub if chunks == 0, we already emitted an issue
1156            let ecmascript_chunk = match ecmascript_chunk {
1157                Some(ecmascript_chunk) => ecmascript_chunk,
1158                None => {
1159                    EcmascriptChunk::new(
1160                        Vc::upcast(*self),
1161                        EcmascriptChunkContent {
1162                            chunk_items: Vec::new(),
1163                            batch_groups: Vec::new(),
1164                        }
1165                        .cell(),
1166                        Vec::new(),
1167                    )
1168                    .to_resolved()
1169                    .await?
1170                }
1171            };
1172
1173            let evaluatable_assets = chunk_group
1174                .entries()
1175                .map(|entry| {
1176                    ResolvedVc::try_sidecast::<Box<dyn EvaluatableAsset>>(entry)
1177                        .context("entry_chunk_group entries must be evaluatable assets")
1178                })
1179                .collect::<Result<Vec<_>>>()?;
1180
1181            let asset = ResolvedVc::upcast(
1182                EcmascriptBrowserSingleEntryChunk::new(
1183                    *self,
1184                    path,
1185                    *ecmascript_chunk,
1186                    Vc::cell(evaluatable_assets),
1187                    extra_referenced_assets,
1188                    Vc::cell(references),
1189                    *module_graph,
1190                )
1191                .to_resolved()
1192                .await?,
1193            );
1194
1195            Ok(EntryChunkGroupResult {
1196                asset,
1197                availability_info,
1198            }
1199            .cell())
1200        }
1201        .instrument(span)
1202        .await
1203    }
1204
1205    #[turbo_tasks::function]
1206    fn chunk_item_id_strategy(&self) -> Vc<ModuleIdStrategy> {
1207        *self
1208            .module_id_strategy
1209            .unwrap_or_else(|| ModuleIdStrategy::default().resolved_cell())
1210    }
1211
1212    #[turbo_tasks::function]
1213    async fn async_loader_chunk_item(
1214        self: ResolvedVc<Self>,
1215        module: Vc<Box<dyn ChunkableModule>>,
1216        module_graph: Vc<ModuleGraph>,
1217        availability_info: AvailabilityInfo,
1218    ) -> Result<Vc<Box<dyn ChunkItem>>> {
1219        let chunking_context = ResolvedVc::upcast::<Box<dyn ChunkingContext>>(self);
1220        if self.await?.single_chunk {
1221            // Single-chunk (eg. service-workers) entries cannot split a
1222            // separate async chunk.
1223            SingleChunkAsyncLoaderIssue {
1224                path: module.ident().await?.path.clone(),
1225            }
1226            .resolved_cell()
1227            .emit();
1228            return Ok(module.as_chunk_item(module_graph, *chunking_context));
1229        }
1230        let use_manifest = self.await?.manifest_chunks
1231            // This guard is in place so that only javascript goes
1232            // this path for lazy loading dynamic imports not things like css.
1233            && ResolvedVc::try_downcast::<Box<dyn EcmascriptChunkPlaceable>>(
1234                module.to_resolved().await?,
1235            )
1236            .is_some();
1237        Ok(if use_manifest {
1238            let manifest_asset = ManifestAsyncModule::new(
1239                module,
1240                module_graph,
1241                *chunking_context,
1242                availability_info,
1243            );
1244            let loader_module = ManifestLoaderModule::new(manifest_asset);
1245            loader_module.as_chunk_item(module_graph, *chunking_context)
1246        } else {
1247            let module = AsyncLoaderModule::new(module, *chunking_context, availability_info);
1248            module.as_chunk_item(module_graph, *chunking_context)
1249        })
1250    }
1251
1252    #[turbo_tasks::function]
1253    async fn standalone_chunk(
1254        self: Vc<Self>,
1255        chunk_item: ResolvedVc<Box<dyn ChunkItem>>,
1256    ) -> Result<Vc<Box<dyn OutputAsset>>> {
1257        let chunk_type = chunk_item
1258            .into_trait_ref()
1259            .await?
1260            .ty()
1261            .to_resolved()
1262            .await?;
1263        let chunk = chunk_type
1264            .chunk(
1265                Vc::upcast(self),
1266                vec![ChunkItemOrBatchWithAsyncModuleInfo::ChunkItem(
1267                    ChunkItemWithAsyncModuleInfo {
1268                        chunk_item,
1269                        chunk_type,
1270                        module: None,
1271                        async_info: None,
1272                    },
1273                )],
1274                Vec::new(),
1275                Vec::new(),
1276            )
1277            .to_resolved()
1278            .await?;
1279        Ok(*self.generate_chunk(chunk).await?)
1280    }
1281
1282    #[turbo_tasks::function]
1283    async fn async_loader_chunk_item_ident(
1284        self: Vc<Self>,
1285        module: Vc<Box<dyn ChunkableModule>>,
1286    ) -> Result<Vc<AssetIdent>> {
1287        let use_manifest = self.await?.manifest_chunks
1288            // This guard is in place so that only javascript goes
1289            // this path for lazy loading dynamic imports not things like css.
1290            && ResolvedVc::try_downcast::<Box<dyn EcmascriptChunkPlaceable>>(
1291                module.to_resolved().await?,
1292            )
1293            .is_some();
1294        Ok(if use_manifest {
1295            ManifestLoaderModule::asset_ident_for(module)
1296        } else {
1297            AsyncLoaderModule::asset_ident_for(module)
1298        })
1299    }
1300
1301    #[turbo_tasks::function]
1302    async fn module_export_usage(
1303        &self,
1304        module: ResolvedVc<Box<dyn Module>>,
1305    ) -> Result<Vc<ModuleExportUsage>> {
1306        if let Some(export_usage) = self.export_usage {
1307            Ok(export_usage.await?.used_exports(module).await?)
1308        } else {
1309            Ok(ModuleExportUsage::unknown())
1310        }
1311    }
1312
1313    #[turbo_tasks::function]
1314    fn unused_references(&self) -> Vc<UnusedReferences> {
1315        if let Some(unused_references) = self.unused_references {
1316            *unused_references
1317        } else {
1318            Vc::cell(Default::default())
1319        }
1320    }
1321
1322    #[turbo_tasks::function]
1323    async fn debug_ids_enabled(self: Vc<Self>) -> Result<Vc<bool>> {
1324        Ok(Vc::cell(self.await?.debug_ids))
1325    }
1326
1327    #[turbo_tasks::function]
1328    fn worker_configuration_options(&self) -> Vc<WorkerConfigurationOptions> {
1329        WorkerConfigurationOptions {
1330            asset_prefix: self.worker_asset_prefix.clone(),
1331            forwarded_globals: self.worker_forwarded_globals.clone(),
1332        }
1333        .cell()
1334    }
1335
1336    #[turbo_tasks::function]
1337    async fn worker_entrypoint(self: Vc<Self>) -> Result<Vc<Box<dyn OutputAsset>>> {
1338        let chunking_context: Vc<Box<dyn ChunkingContext>> = Vc::upcast(self);
1339        let resolved = chunking_context.to_resolved().await?;
1340        let forwarded_globals = Vc::cell(self.await?.worker_forwarded_globals.clone());
1341        let entrypoint = EcmascriptBrowserWorkerEntrypoint::new(*resolved, forwarded_globals);
1342        Ok(Vc::upcast(entrypoint))
1343    }
1344
1345    #[turbo_tasks::function]
1346    fn chunk_loading(&self) -> Vc<ChunkLoading> {
1347        if self.single_chunk {
1348            ChunkLoading::SingleChunk.cell()
1349        } else {
1350            self.environment.chunk_loading()
1351        }
1352    }
1353}
1354
1355#[turbo_tasks::value]
1356struct ChunkPathInfo {
1357    root_path: FileSystemPath,
1358    chunk_root_path: FileSystemPath,
1359    chunk_content_hashing: Option<ContentHashing>,
1360}
1361
1362#[turbo_tasks::value(shared)]
1363struct SingleChunkProducedMultipleChunksIssue {
1364    path: FileSystemPath,
1365    chunk_count: usize,
1366}
1367
1368#[async_trait]
1369#[turbo_tasks::value_impl]
1370impl Issue for SingleChunkProducedMultipleChunksIssue {
1371    fn severity(&self) -> IssueSeverity {
1372        IssueSeverity::Error
1373    }
1374
1375    async fn file_path(&self) -> Result<FileSystemPath> {
1376        Ok(self.path.clone())
1377    }
1378
1379    fn stage(&self) -> IssueStage {
1380        IssueStage::CodeGen
1381    }
1382
1383    async fn title(&self) -> Result<StyledString> {
1384        Ok(StyledString::Text(rcstr!(
1385            "Single-chunk entry could not be reduced to a single ECMAScript chunk"
1386        )))
1387    }
1388
1389    async fn description(&self) -> Result<Option<StyledString>> {
1390        Ok(Some(StyledString::Stack(vec![
1391            StyledString::Line(vec![
1392                StyledString::Text(rcstr!(
1393                    "A single-chunk entry must produce exactly one ECMAScript chunk, but it \
1394                     produced "
1395                )),
1396                StyledString::Strong(RcStr::from(format!("{}", self.chunk_count))),
1397                StyledString::Text(rcstr!(" chunk(s).")),
1398            ]),
1399            StyledString::Text(rcstr!(
1400                "This usually means the module graph contains non-ECMAScript chunkable items \
1401                 (e.g. CSS, image or font imports), which cannot be inlined into a single chunk."
1402            )),
1403        ])))
1404    }
1405}
1406
1407#[turbo_tasks::value(shared)]
1408struct SingleChunkAsyncLoaderIssue {
1409    path: FileSystemPath,
1410}
1411
1412#[async_trait]
1413#[turbo_tasks::value_impl]
1414impl Issue for SingleChunkAsyncLoaderIssue {
1415    fn severity(&self) -> IssueSeverity {
1416        IssueSeverity::Error
1417    }
1418
1419    async fn file_path(&self) -> Result<FileSystemPath> {
1420        Ok(self.path.clone())
1421    }
1422
1423    fn stage(&self) -> IssueStage {
1424        IssueStage::CodeGen
1425    }
1426
1427    async fn title(&self) -> Result<StyledString> {
1428        Ok(StyledString::Text(rcstr!(
1429            "Async loaders are not supported in single-chunk mode"
1430        )))
1431    }
1432
1433    async fn description(&self) -> Result<Option<StyledString>> {
1434        Ok(Some(StyledString::Stack(vec![StyledString::Text(rcstr!(
1435            "The dynamically imported module is inlined into the single chunk and cannot be \
1436             code-split. Remove the dynamic import (import the module statically) if separate \
1437             loading is not required."
1438        ))])))
1439    }
1440}