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 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 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#[turbo_tasks::value]
299#[derive(Debug, Clone)]
300pub struct BrowserChunkingContext {
301 name: Option<RcStr>,
302 root_path: FileSystemPath,
304 source_map_source_type: SourceMapSourceType,
306 output_root: FileSystemPath,
308 output_root_to_root_path: RcStr,
310 client_root: FileSystemPath,
312 #[bincode(with = "turbo_bincode::indexmap")]
314 client_roots: FxIndexMap<RcStr, FileSystemPath>,
315 chunk_root_path: FileSystemPath,
317 asset_root_path: FileSystemPath,
319 #[bincode(with = "turbo_bincode::indexmap")]
321 asset_root_paths: FxIndexMap<RcStr, FileSystemPath>,
322 chunk_base_path: Option<RcStr>,
325 worker_asset_prefix: Option<RcStr>,
333 asset_suffix: Option<ResolvedVc<AssetSuffix>>,
336 asset_base_path: Option<RcStr>,
339 #[bincode(with = "turbo_bincode::indexmap")]
342 asset_base_paths: FxIndexMap<RcStr, RcStr>,
343 service_worker_scope_base_path: Option<RcStr>,
346 #[bincode(with = "turbo_bincode::indexmap")]
348 url_behaviors: FxIndexMap<RcStr, UrlBehavior>,
349 default_url_behavior: Option<UrlBehavior>,
351 enable_hot_module_replacement: bool,
353 enable_nested_async_availability: bool,
355 enable_module_merging: bool,
357 enable_dynamic_chunk_content_loading: bool,
359 debug_ids: bool,
361 shared_runtime: bool,
365 shared_runtime_chunk: bool,
368 environment: ResolvedVc<Environment>,
370 runtime_type: RuntimeType,
372 minify_type: MinifyType,
374 chunk_content_hashing: Option<ContentHashing>,
376 asset_content_hashing: ContentHashing,
378 source_maps_type: SourceMapsType,
380 current_chunk_method: CurrentChunkMethod,
382 manifest_chunks: bool,
384 module_id_strategy: Option<ResolvedVc<ModuleIdStrategy>>,
386 export_usage: Option<ResolvedVc<BindingUsageInfo>>,
388 unused_references: Option<ResolvedVc<UnusedReferences>>,
390 chunking_configs: Vec<(ResolvedVc<Box<dyn ChunkType>>, ChunkingConfig)>,
392 should_use_absolute_url_references: bool,
394 worker_forwarded_globals: Vec<RcStr>,
396 chunk_loading_global: Option<RcStr>,
399 hash_salt: ResolvedVc<RcStr>,
401 cross_origin: CrossOrigin,
403 chunk_load_retry: ChunkLoadRetry,
405 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 pub(crate) async fn generate_runtime_chunk(
493 self: Vc<Self>,
494 module_graph: Vc<ModuleGraph>,
495 ) -> Result<Vc<EcmascriptBrowserRuntimeChunk>> {
496 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 #[turbo_tasks::function]
562 pub fn runtime_type(&self) -> Vc<RuntimeType> {
563 self.runtime_type.cell()
564 }
565
566 #[turbo_tasks::function]
570 pub fn shared_runtime(&self) -> Vc<bool> {
571 Vc::cell(self.shared_runtime)
572 }
573
574 #[turbo_tasks::function]
578 pub fn shared_runtime_chunk(&self) -> Vc<bool> {
579 Vc::cell(self.shared_runtime_chunk)
580 }
581
582 #[turbo_tasks::function]
584 pub fn chunk_base_path(&self) -> Vc<Option<RcStr>> {
585 Vc::cell(self.chunk_base_path.clone())
586 }
587
588 #[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 #[turbo_tasks::function]
600 pub fn source_maps_type(&self) -> Vc<SourceMapsType> {
601 self.source_maps_type.cell()
602 }
603
604 #[turbo_tasks::function]
606 pub fn minify_type(&self) -> Vc<MinifyType> {
607 self.minify_type.cell()
608 }
609
610 #[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 #[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 #[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: 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 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 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 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 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 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 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 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}