Skip to main content

turbopack_browser/
chunking_context.rs

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