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