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