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