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