1use anyhow::{Context, Result, bail};
2use tracing::Instrument;
3use turbo_rcstr::{RcStr, rcstr};
4use turbo_tasks::{FxIndexMap, ResolvedVc, TryJoinIterExt, Upcast, ValueToString, Vc};
5use turbo_tasks_fs::FileSystemPath;
6use turbopack_core::{
7 asset::Asset,
8 chunk::{
9 AssetSuffix, Chunk, ChunkGroupResult, ChunkItem, ChunkType, ChunkableModule,
10 ChunkingConfig, ChunkingConfigs, ChunkingContext, EntryChunkGroupResult, EvaluatableAssets,
11 MinifyType, SourceMapSourceType, SourceMapsType, UnusedReferences, UrlBehavior,
12 availability_info::AvailabilityInfo,
13 chunk_group::{MakeChunkGroupResult, make_chunk_group},
14 chunk_id_strategy::ModuleIdStrategy,
15 },
16 environment::Environment,
17 ident::AssetIdent,
18 module::Module,
19 module_graph::{
20 ModuleGraph,
21 binding_usage_info::{BindingUsageInfo, ModuleExportUsage},
22 chunk_group_info::ChunkGroup,
23 },
24 output::{OutputAsset, OutputAssets},
25};
26use turbopack_ecmascript::{
27 async_chunk::module::AsyncLoaderModule,
28 chunk::EcmascriptChunk,
29 manifest::{chunk_asset::ManifestAsyncModule, loader_module::ManifestLoaderModule},
30};
31use turbopack_ecmascript_runtime::RuntimeType;
32
33use crate::ecmascript::node::{
34 chunk::EcmascriptBuildNodeChunk, entry::chunk::EcmascriptBuildNodeEntryChunk,
35};
36
37pub struct NodeJsChunkingContextBuilder {
39 chunking_context: NodeJsChunkingContext,
40}
41
42impl NodeJsChunkingContextBuilder {
43 pub fn asset_prefix(mut self, asset_prefix: Option<RcStr>) -> Self {
44 self.chunking_context.asset_prefix = asset_prefix;
45 self
46 }
47
48 pub fn asset_prefix_override(mut self, tag: RcStr, prefix: RcStr) -> Self {
49 self.chunking_context.asset_prefixes.insert(tag, prefix);
50 self
51 }
52
53 pub fn asset_root_path_override(mut self, tag: RcStr, path: FileSystemPath) -> Self {
54 self.chunking_context.asset_root_paths.insert(tag, path);
55 self
56 }
57
58 pub fn client_roots_override(mut self, tag: RcStr, path: FileSystemPath) -> Self {
59 self.chunking_context.client_roots.insert(tag, path);
60 self
61 }
62
63 pub fn url_behavior_override(mut self, tag: RcStr, behavior: UrlBehavior) -> Self {
64 self.chunking_context.url_behaviors.insert(tag, behavior);
65 self
66 }
67
68 pub fn default_url_behavior(mut self, behavior: UrlBehavior) -> Self {
69 self.chunking_context.default_url_behavior = Some(behavior);
70 self
71 }
72
73 pub fn minify_type(mut self, minify_type: MinifyType) -> Self {
74 self.chunking_context.minify_type = minify_type;
75 self
76 }
77
78 pub fn source_maps(mut self, source_maps: SourceMapsType) -> Self {
79 self.chunking_context.source_maps_type = source_maps;
80 self
81 }
82
83 pub fn file_tracing(mut self, enable_tracing: bool) -> Self {
84 self.chunking_context.enable_file_tracing = enable_tracing;
85 self
86 }
87
88 pub fn nested_async_availability(mut self, enable_nested_async_availability: bool) -> Self {
89 self.chunking_context.enable_nested_async_availability = enable_nested_async_availability;
90 self
91 }
92
93 pub fn module_merging(mut self, enable_module_merging: bool) -> Self {
94 self.chunking_context.enable_module_merging = enable_module_merging;
95 self
96 }
97
98 pub fn dynamic_chunk_content_loading(
99 mut self,
100 enable_dynamic_chunk_content_loading: bool,
101 ) -> Self {
102 self.chunking_context.enable_dynamic_chunk_content_loading =
103 enable_dynamic_chunk_content_loading;
104 self
105 }
106
107 pub fn runtime_type(mut self, runtime_type: RuntimeType) -> Self {
108 self.chunking_context.runtime_type = runtime_type;
109 self
110 }
111
112 pub fn manifest_chunks(mut self, manifest_chunks: bool) -> Self {
113 self.chunking_context.manifest_chunks = manifest_chunks;
114 self
115 }
116
117 pub fn source_map_source_type(mut self, source_map_source_type: SourceMapSourceType) -> Self {
118 self.chunking_context.source_map_source_type = source_map_source_type;
119 self
120 }
121
122 pub fn module_id_strategy(mut self, module_id_strategy: ResolvedVc<ModuleIdStrategy>) -> Self {
123 self.chunking_context.module_id_strategy = Some(module_id_strategy);
124 self
125 }
126
127 pub fn export_usage(mut self, export_usage: Option<ResolvedVc<BindingUsageInfo>>) -> Self {
128 self.chunking_context.export_usage = export_usage;
129 self
130 }
131
132 pub fn unused_references(mut self, unused_references: ResolvedVc<UnusedReferences>) -> Self {
133 self.chunking_context.unused_references = Some(unused_references);
134 self
135 }
136
137 pub fn chunking_config<T>(mut self, ty: ResolvedVc<T>, chunking_config: ChunkingConfig) -> Self
138 where
139 T: Upcast<Box<dyn ChunkType>>,
140 {
141 self.chunking_context
142 .chunking_configs
143 .push((ResolvedVc::upcast_non_strict(ty), chunking_config));
144 self
145 }
146
147 pub fn debug_ids(mut self, debug_ids: bool) -> Self {
148 self.chunking_context.debug_ids = debug_ids;
149 self
150 }
151
152 pub fn worker_forwarded_globals(mut self, globals: Vec<RcStr>) -> Self {
153 self.chunking_context
154 .worker_forwarded_globals
155 .extend(globals);
156 self
157 }
158
159 pub fn build(self) -> Vc<NodeJsChunkingContext> {
161 NodeJsChunkingContext::cell(self.chunking_context)
162 }
163}
164
165#[turbo_tasks::value]
167#[derive(Debug, Clone)]
168pub struct NodeJsChunkingContext {
169 root_path: FileSystemPath,
171 output_root: FileSystemPath,
173 output_root_to_root_path: RcStr,
175 client_root: FileSystemPath,
177 #[bincode(with = "turbo_bincode::indexmap")]
179 client_roots: FxIndexMap<RcStr, FileSystemPath>,
180 chunk_root_path: FileSystemPath,
182 asset_root_path: FileSystemPath,
184 #[bincode(with = "turbo_bincode::indexmap")]
186 asset_root_paths: FxIndexMap<RcStr, FileSystemPath>,
187 asset_prefix: Option<RcStr>,
189 #[bincode(with = "turbo_bincode::indexmap")]
191 asset_prefixes: FxIndexMap<RcStr, RcStr>,
192 #[bincode(with = "turbo_bincode::indexmap")]
194 url_behaviors: FxIndexMap<RcStr, UrlBehavior>,
195 default_url_behavior: Option<UrlBehavior>,
197 environment: ResolvedVc<Environment>,
199 runtime_type: RuntimeType,
201 enable_file_tracing: bool,
203 enable_nested_async_availability: bool,
205 enable_module_merging: bool,
207 enable_dynamic_chunk_content_loading: bool,
209 minify_type: MinifyType,
211 source_maps_type: SourceMapsType,
213 manifest_chunks: bool,
215 module_id_strategy: Option<ResolvedVc<ModuleIdStrategy>>,
217 export_usage: Option<ResolvedVc<BindingUsageInfo>>,
219 unused_references: Option<ResolvedVc<UnusedReferences>>,
221 source_map_source_type: SourceMapSourceType,
223 chunking_configs: Vec<(ResolvedVc<Box<dyn ChunkType>>, ChunkingConfig)>,
225 debug_ids: bool,
227 worker_forwarded_globals: Vec<RcStr>,
229}
230
231impl NodeJsChunkingContext {
232 pub fn builder(
234 root_path: FileSystemPath,
235 output_root: FileSystemPath,
236 output_root_to_root_path: RcStr,
237 client_root: FileSystemPath,
238 chunk_root_path: FileSystemPath,
239 asset_root_path: FileSystemPath,
240 environment: ResolvedVc<Environment>,
241 runtime_type: RuntimeType,
242 ) -> NodeJsChunkingContextBuilder {
243 NodeJsChunkingContextBuilder {
244 chunking_context: NodeJsChunkingContext {
245 root_path,
246 output_root,
247 output_root_to_root_path,
248 client_root,
249 client_roots: Default::default(),
250 chunk_root_path,
251 asset_root_path,
252 asset_root_paths: Default::default(),
253 asset_prefix: None,
254 asset_prefixes: Default::default(),
255 url_behaviors: Default::default(),
256 default_url_behavior: None,
257 enable_file_tracing: false,
258 enable_nested_async_availability: false,
259 enable_module_merging: false,
260 enable_dynamic_chunk_content_loading: false,
261 environment,
262 runtime_type,
263 minify_type: MinifyType::NoMinify,
264 source_maps_type: SourceMapsType::Full,
265 manifest_chunks: false,
266 source_map_source_type: SourceMapSourceType::TurbopackUri,
267 module_id_strategy: None,
268 export_usage: None,
269 unused_references: None,
270 chunking_configs: Default::default(),
271 debug_ids: false,
272 worker_forwarded_globals: vec![],
273 },
274 }
275 }
276}
277
278#[turbo_tasks::value_impl]
279impl NodeJsChunkingContext {
280 #[turbo_tasks::function]
285 pub fn runtime_type(&self) -> Vc<RuntimeType> {
286 self.runtime_type.cell()
287 }
288
289 #[turbo_tasks::function]
291 pub fn minify_type(&self) -> Vc<MinifyType> {
292 self.minify_type.cell()
293 }
294
295 #[turbo_tasks::function]
296 pub fn asset_prefix(&self) -> Vc<Option<RcStr>> {
297 Vc::cell(self.asset_prefix.clone())
298 }
299}
300
301impl NodeJsChunkingContext {
302 async fn generate_chunk(
303 self: Vc<Self>,
304 chunk: ResolvedVc<Box<dyn Chunk>>,
305 ) -> Result<ResolvedVc<Box<dyn OutputAsset>>> {
306 Ok(
307 if let Some(ecmascript_chunk) = ResolvedVc::try_downcast_type::<EcmascriptChunk>(chunk)
308 {
309 ResolvedVc::upcast(
310 EcmascriptBuildNodeChunk::new(self, *ecmascript_chunk)
311 .to_resolved()
312 .await?,
313 )
314 } else if let Some(output_asset) =
315 ResolvedVc::try_sidecast::<Box<dyn OutputAsset>>(chunk)
316 {
317 output_asset
318 } else {
319 bail!("Unable to generate output asset for chunk");
320 },
321 )
322 }
323}
324
325#[turbo_tasks::value_impl]
326impl ChunkingContext for NodeJsChunkingContext {
327 #[turbo_tasks::function]
328 fn name(&self) -> Vc<RcStr> {
329 Vc::cell(rcstr!("unknown"))
330 }
331
332 #[turbo_tasks::function]
333 fn root_path(&self) -> Vc<FileSystemPath> {
334 self.root_path.clone().cell()
335 }
336
337 #[turbo_tasks::function]
338 fn output_root(&self) -> Vc<FileSystemPath> {
339 self.output_root.clone().cell()
340 }
341
342 #[turbo_tasks::function]
343 fn output_root_to_root_path(&self) -> Vc<RcStr> {
344 Vc::cell(self.output_root_to_root_path.clone())
345 }
346
347 #[turbo_tasks::function]
348 fn environment(&self) -> Vc<Environment> {
349 *self.environment
350 }
351
352 #[turbo_tasks::function]
353 fn is_tracing_enabled(&self) -> Vc<bool> {
354 Vc::cell(self.enable_file_tracing)
355 }
356
357 #[turbo_tasks::function]
358 fn is_nested_async_availability_enabled(&self) -> Vc<bool> {
359 Vc::cell(self.enable_nested_async_availability)
360 }
361
362 #[turbo_tasks::function]
363 fn is_module_merging_enabled(&self) -> Vc<bool> {
364 Vc::cell(self.enable_module_merging)
365 }
366
367 #[turbo_tasks::function]
368 fn is_dynamic_chunk_content_loading_enabled(&self) -> Vc<bool> {
369 Vc::cell(self.enable_dynamic_chunk_content_loading)
370 }
371
372 #[turbo_tasks::function]
373 pub fn minify_type(&self) -> Vc<MinifyType> {
374 self.minify_type.cell()
375 }
376
377 #[turbo_tasks::function]
378 async fn asset_url(&self, ident: FileSystemPath, tag: Option<RcStr>) -> Result<Vc<RcStr>> {
379 let asset_path = ident.to_string();
380
381 let client_root = tag
382 .as_ref()
383 .and_then(|tag| self.client_roots.get(tag))
384 .unwrap_or(&self.client_root);
385
386 let asset_prefix = tag
387 .as_ref()
388 .and_then(|tag| self.asset_prefixes.get(tag))
389 .or(self.asset_prefix.as_ref());
390
391 let asset_path = asset_path
392 .strip_prefix(&format!("{}/", client_root.path))
393 .context("expected client root to contain asset path")?;
394
395 Ok(Vc::cell(
396 format!(
397 "{}{}",
398 asset_prefix.map(|s| s.as_str()).unwrap_or("/"),
399 asset_path
400 )
401 .into(),
402 ))
403 }
404
405 #[turbo_tasks::function]
406 fn chunk_root_path(&self) -> Vc<FileSystemPath> {
407 self.chunk_root_path.clone().cell()
408 }
409
410 #[turbo_tasks::function]
411 async fn chunk_path(
412 &self,
413 _asset: Option<Vc<Box<dyn Asset>>>,
414 ident: Vc<AssetIdent>,
415 prefix: Option<RcStr>,
416 extension: RcStr,
417 ) -> Result<Vc<FileSystemPath>> {
418 let root_path = self.chunk_root_path.clone();
419 let name = ident
420 .output_name(self.root_path.clone(), prefix, extension)
421 .owned()
422 .await?;
423 Ok(root_path.join(&name)?.cell())
424 }
425
426 #[turbo_tasks::function]
427 fn reference_chunk_source_maps(&self, _chunk: Vc<Box<dyn OutputAsset>>) -> Vc<bool> {
428 Vc::cell(match self.source_maps_type {
429 SourceMapsType::Full => true,
430 SourceMapsType::Partial => true,
431 SourceMapsType::None => false,
432 })
433 }
434
435 #[turbo_tasks::function]
436 fn reference_module_source_maps(&self, _module: Vc<Box<dyn Module>>) -> Vc<bool> {
437 Vc::cell(match self.source_maps_type {
438 SourceMapsType::Full => true,
439 SourceMapsType::Partial => true,
440 SourceMapsType::None => false,
441 })
442 }
443
444 #[turbo_tasks::function]
445 fn source_map_source_type(&self) -> Vc<SourceMapSourceType> {
446 self.source_map_source_type.cell()
447 }
448
449 #[turbo_tasks::function]
450 fn chunking_configs(&self) -> Result<Vc<ChunkingConfigs>> {
451 Ok(Vc::cell(self.chunking_configs.iter().cloned().collect()))
452 }
453
454 #[turbo_tasks::function]
455 async fn asset_path(
456 &self,
457 content_hash: RcStr,
458 original_asset_ident: Vc<AssetIdent>,
459 tag: Option<RcStr>,
460 ) -> Result<Vc<FileSystemPath>> {
461 let source_path = original_asset_ident.path().await?;
462 let basename = source_path.file_name();
463 let asset_path = match source_path.extension_ref() {
464 Some(ext) => format!(
465 "{basename}.{content_hash}.{ext}",
466 basename = &basename[..basename.len() - ext.len() - 1],
467 content_hash = &content_hash[..8]
468 ),
469 None => format!(
470 "{basename}.{content_hash}",
471 content_hash = &content_hash[..8]
472 ),
473 };
474
475 let asset_root_path = tag
476 .as_ref()
477 .and_then(|tag| self.asset_root_paths.get(tag))
478 .unwrap_or(&self.asset_root_path);
479
480 Ok(asset_root_path.join(&asset_path)?.cell())
481 }
482
483 #[turbo_tasks::function]
484 fn url_behavior(&self, tag: Option<RcStr>) -> Vc<UrlBehavior> {
485 tag.as_ref()
486 .and_then(|tag| self.url_behaviors.get(tag))
487 .cloned()
488 .or_else(|| self.default_url_behavior.clone())
489 .unwrap_or(UrlBehavior {
490 suffix: AssetSuffix::Inferred,
491 static_suffix: ResolvedVc::cell(None),
492 })
493 .cell()
494 }
495
496 #[turbo_tasks::function]
497 async fn chunk_group(
498 self: ResolvedVc<Self>,
499 ident: Vc<AssetIdent>,
500 chunk_group: ChunkGroup,
501 module_graph: Vc<ModuleGraph>,
502 availability_info: AvailabilityInfo,
503 ) -> Result<Vc<ChunkGroupResult>> {
504 let span = tracing::info_span!("chunking", name = display(ident.to_string().await?));
505 async move {
506 let modules = chunk_group.entries();
507 let MakeChunkGroupResult {
508 chunks,
509 referenced_output_assets,
510 references,
511 availability_info,
512 } = make_chunk_group(
513 modules,
514 module_graph,
515 ResolvedVc::upcast(self),
516 availability_info,
517 )
518 .await?;
519
520 let chunks = chunks.await?;
521
522 let assets = chunks
523 .iter()
524 .map(|chunk| self.generate_chunk(*chunk))
525 .try_join()
526 .await?;
527
528 Ok(ChunkGroupResult {
529 assets: ResolvedVc::cell(assets),
530 referenced_assets: ResolvedVc::cell(referenced_output_assets),
531 references: ResolvedVc::cell(references),
532 availability_info,
533 }
534 .cell())
535 }
536 .instrument(span)
537 .await
538 }
539
540 #[turbo_tasks::function]
541 pub async fn entry_chunk_group(
542 self: ResolvedVc<Self>,
543 path: FileSystemPath,
544 evaluatable_assets: Vc<EvaluatableAssets>,
545 module_graph: Vc<ModuleGraph>,
546 extra_chunks: Vc<OutputAssets>,
547 extra_referenced_assets: Vc<OutputAssets>,
548 availability_info: AvailabilityInfo,
549 ) -> Result<Vc<EntryChunkGroupResult>> {
550 let span = tracing::info_span!(
551 "chunking",
552 name = display(path.value_to_string().await?),
553 chunking_type = "entry",
554 );
555 async move {
556 let evaluatable_assets_ref = evaluatable_assets.await?;
557 let entries = evaluatable_assets_ref
558 .iter()
559 .map(|&asset| ResolvedVc::upcast::<Box<dyn Module>>(asset));
560
561 let MakeChunkGroupResult {
562 chunks,
563 mut referenced_output_assets,
564 references,
565 availability_info,
566 } = make_chunk_group(
567 entries,
568 module_graph,
569 ResolvedVc::upcast(self),
570 availability_info,
571 )
572 .await?;
573
574 let chunks = chunks.await?;
575
576 let extra_chunks = extra_chunks.await?;
577 let mut other_chunks = chunks
578 .iter()
579 .map(|chunk| self.generate_chunk(*chunk))
580 .try_join()
581 .await?;
582 other_chunks.extend(extra_chunks.iter().copied());
583
584 referenced_output_assets.extend(extra_referenced_assets.await?.iter().copied());
585
586 let Some(module) = ResolvedVc::try_sidecast(*evaluatable_assets_ref.last().unwrap())
587 else {
588 bail!("module must be placeable in an ecmascript chunk");
589 };
590
591 let asset = ResolvedVc::upcast(
592 EcmascriptBuildNodeEntryChunk::new(
593 path,
594 Vc::cell(other_chunks),
595 evaluatable_assets,
596 *module,
597 Vc::cell(referenced_output_assets),
598 Vc::cell(references),
599 module_graph,
600 *self,
601 )
602 .to_resolved()
603 .await?,
604 );
605
606 Ok(EntryChunkGroupResult {
607 asset,
608 availability_info,
609 }
610 .cell())
611 }
612 .instrument(span)
613 .await
614 }
615
616 #[turbo_tasks::function]
617 fn evaluated_chunk_group(
618 self: Vc<Self>,
619 _ident: Vc<AssetIdent>,
620 _chunk_group: ChunkGroup,
621 _module_graph: Vc<ModuleGraph>,
622 _availability_info: AvailabilityInfo,
623 ) -> Result<Vc<ChunkGroupResult>> {
624 bail!("the Node.js chunking context does not support evaluated chunk groups")
625 }
626
627 #[turbo_tasks::function]
628 fn chunk_item_id_strategy(&self) -> Vc<ModuleIdStrategy> {
629 *self
630 .module_id_strategy
631 .unwrap_or_else(|| ModuleIdStrategy::default().resolved_cell())
632 }
633
634 #[turbo_tasks::function]
635 async fn async_loader_chunk_item(
636 self: Vc<Self>,
637 module: Vc<Box<dyn ChunkableModule>>,
638 module_graph: Vc<ModuleGraph>,
639 availability_info: AvailabilityInfo,
640 ) -> Result<Vc<Box<dyn ChunkItem>>> {
641 let chunking_context: ResolvedVc<Box<dyn ChunkingContext>> =
642 Vc::upcast::<Box<dyn ChunkingContext>>(self)
643 .to_resolved()
644 .await?;
645 Ok(if self.await?.manifest_chunks {
646 let manifest_asset = ManifestAsyncModule::new(
647 module,
648 module_graph,
649 *chunking_context,
650 availability_info,
651 )
652 .to_resolved()
653 .await?;
654 let loader_module = ManifestLoaderModule::new(*manifest_asset);
655 loader_module.as_chunk_item(module_graph, *chunking_context)
656 } else {
657 let module = AsyncLoaderModule::new(module, *chunking_context, availability_info);
658 module.as_chunk_item(module_graph, *chunking_context)
659 })
660 }
661
662 #[turbo_tasks::function]
663 async fn async_loader_chunk_item_ident(
664 self: Vc<Self>,
665 module: Vc<Box<dyn ChunkableModule>>,
666 ) -> Result<Vc<AssetIdent>> {
667 Ok(if self.await?.manifest_chunks {
668 ManifestLoaderModule::asset_ident_for(module)
669 } else {
670 AsyncLoaderModule::asset_ident_for(module)
671 })
672 }
673
674 #[turbo_tasks::function]
675 async fn module_export_usage(
676 &self,
677 module: ResolvedVc<Box<dyn Module>>,
678 ) -> Result<Vc<ModuleExportUsage>> {
679 if let Some(export_usage) = self.export_usage {
680 Ok(export_usage.await?.used_exports(module).await?)
681 } else {
682 Ok(ModuleExportUsage::all())
683 }
684 }
685
686 #[turbo_tasks::function]
687 fn unused_references(&self) -> Vc<UnusedReferences> {
688 if let Some(unused_references) = self.unused_references {
689 *unused_references
690 } else {
691 Vc::cell(Default::default())
692 }
693 }
694
695 #[turbo_tasks::function]
696 fn debug_ids_enabled(&self) -> Vc<bool> {
697 Vc::cell(self.debug_ids)
698 }
699
700 #[turbo_tasks::function]
701 fn worker_forwarded_globals(&self) -> Vc<Vec<RcStr>> {
702 Vc::cell(self.worker_forwarded_globals.clone())
703 }
704}