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_item::ManifestLoaderChunkItem},
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 build(self) -> Vc<NodeJsChunkingContext> {
154 NodeJsChunkingContext::cell(self.chunking_context)
155 }
156}
157
158#[turbo_tasks::value]
160#[derive(Debug, Clone)]
161pub struct NodeJsChunkingContext {
162 root_path: FileSystemPath,
164 output_root: FileSystemPath,
166 output_root_to_root_path: RcStr,
168 client_root: FileSystemPath,
170 #[bincode(with = "turbo_bincode::indexmap")]
172 client_roots: FxIndexMap<RcStr, FileSystemPath>,
173 chunk_root_path: FileSystemPath,
175 asset_root_path: FileSystemPath,
177 #[bincode(with = "turbo_bincode::indexmap")]
179 asset_root_paths: FxIndexMap<RcStr, FileSystemPath>,
180 asset_prefix: Option<RcStr>,
182 #[bincode(with = "turbo_bincode::indexmap")]
184 asset_prefixes: FxIndexMap<RcStr, RcStr>,
185 #[bincode(with = "turbo_bincode::indexmap")]
187 url_behaviors: FxIndexMap<RcStr, UrlBehavior>,
188 default_url_behavior: Option<UrlBehavior>,
190 environment: ResolvedVc<Environment>,
192 runtime_type: RuntimeType,
194 enable_file_tracing: bool,
196 enable_nested_async_availability: bool,
198 enable_module_merging: bool,
200 enable_dynamic_chunk_content_loading: bool,
202 minify_type: MinifyType,
204 source_maps_type: SourceMapsType,
206 manifest_chunks: bool,
208 module_id_strategy: Option<ResolvedVc<ModuleIdStrategy>>,
210 export_usage: Option<ResolvedVc<BindingUsageInfo>>,
212 unused_references: Option<ResolvedVc<UnusedReferences>>,
214 source_map_source_type: SourceMapSourceType,
216 chunking_configs: Vec<(ResolvedVc<Box<dyn ChunkType>>, ChunkingConfig)>,
218 debug_ids: bool,
220}
221
222impl NodeJsChunkingContext {
223 pub fn builder(
225 root_path: FileSystemPath,
226 output_root: FileSystemPath,
227 output_root_to_root_path: RcStr,
228 client_root: FileSystemPath,
229 chunk_root_path: FileSystemPath,
230 asset_root_path: FileSystemPath,
231 environment: ResolvedVc<Environment>,
232 runtime_type: RuntimeType,
233 ) -> NodeJsChunkingContextBuilder {
234 NodeJsChunkingContextBuilder {
235 chunking_context: NodeJsChunkingContext {
236 root_path,
237 output_root,
238 output_root_to_root_path,
239 client_root,
240 client_roots: Default::default(),
241 chunk_root_path,
242 asset_root_path,
243 asset_root_paths: Default::default(),
244 asset_prefix: None,
245 asset_prefixes: Default::default(),
246 url_behaviors: Default::default(),
247 default_url_behavior: None,
248 enable_file_tracing: false,
249 enable_nested_async_availability: false,
250 enable_module_merging: false,
251 enable_dynamic_chunk_content_loading: false,
252 environment,
253 runtime_type,
254 minify_type: MinifyType::NoMinify,
255 source_maps_type: SourceMapsType::Full,
256 manifest_chunks: false,
257 source_map_source_type: SourceMapSourceType::TurbopackUri,
258 module_id_strategy: None,
259 export_usage: None,
260 unused_references: None,
261 chunking_configs: Default::default(),
262 debug_ids: false,
263 },
264 }
265 }
266}
267
268#[turbo_tasks::value_impl]
269impl NodeJsChunkingContext {
270 #[turbo_tasks::function]
275 pub fn runtime_type(&self) -> Vc<RuntimeType> {
276 self.runtime_type.cell()
277 }
278
279 #[turbo_tasks::function]
281 pub fn minify_type(&self) -> Vc<MinifyType> {
282 self.minify_type.cell()
283 }
284
285 #[turbo_tasks::function]
286 pub fn asset_prefix(&self) -> Vc<Option<RcStr>> {
287 Vc::cell(self.asset_prefix.clone())
288 }
289}
290
291impl NodeJsChunkingContext {
292 async fn generate_chunk(
293 self: Vc<Self>,
294 chunk: ResolvedVc<Box<dyn Chunk>>,
295 ) -> Result<ResolvedVc<Box<dyn OutputAsset>>> {
296 Ok(
297 if let Some(ecmascript_chunk) = ResolvedVc::try_downcast_type::<EcmascriptChunk>(chunk)
298 {
299 ResolvedVc::upcast(
300 EcmascriptBuildNodeChunk::new(self, *ecmascript_chunk)
301 .to_resolved()
302 .await?,
303 )
304 } else if let Some(output_asset) =
305 ResolvedVc::try_sidecast::<Box<dyn OutputAsset>>(chunk)
306 {
307 output_asset
308 } else {
309 bail!("Unable to generate output asset for chunk");
310 },
311 )
312 }
313}
314
315#[turbo_tasks::value_impl]
316impl ChunkingContext for NodeJsChunkingContext {
317 #[turbo_tasks::function]
318 fn name(&self) -> Vc<RcStr> {
319 Vc::cell(rcstr!("unknown"))
320 }
321
322 #[turbo_tasks::function]
323 fn root_path(&self) -> Vc<FileSystemPath> {
324 self.root_path.clone().cell()
325 }
326
327 #[turbo_tasks::function]
328 fn output_root(&self) -> Vc<FileSystemPath> {
329 self.output_root.clone().cell()
330 }
331
332 #[turbo_tasks::function]
333 fn output_root_to_root_path(&self) -> Vc<RcStr> {
334 Vc::cell(self.output_root_to_root_path.clone())
335 }
336
337 #[turbo_tasks::function]
338 fn environment(&self) -> Vc<Environment> {
339 *self.environment
340 }
341
342 #[turbo_tasks::function]
343 fn is_tracing_enabled(&self) -> Vc<bool> {
344 Vc::cell(self.enable_file_tracing)
345 }
346
347 #[turbo_tasks::function]
348 fn is_nested_async_availability_enabled(&self) -> Vc<bool> {
349 Vc::cell(self.enable_nested_async_availability)
350 }
351
352 #[turbo_tasks::function]
353 fn is_module_merging_enabled(&self) -> Vc<bool> {
354 Vc::cell(self.enable_module_merging)
355 }
356
357 #[turbo_tasks::function]
358 fn is_dynamic_chunk_content_loading_enabled(&self) -> Vc<bool> {
359 Vc::cell(self.enable_dynamic_chunk_content_loading)
360 }
361
362 #[turbo_tasks::function]
363 pub fn minify_type(&self) -> Vc<MinifyType> {
364 self.minify_type.cell()
365 }
366
367 #[turbo_tasks::function]
368 async fn asset_url(&self, ident: FileSystemPath, tag: Option<RcStr>) -> Result<Vc<RcStr>> {
369 let asset_path = ident.to_string();
370
371 let client_root = tag
372 .as_ref()
373 .and_then(|tag| self.client_roots.get(tag))
374 .unwrap_or(&self.client_root);
375
376 let asset_prefix = tag
377 .as_ref()
378 .and_then(|tag| self.asset_prefixes.get(tag))
379 .or(self.asset_prefix.as_ref());
380
381 let asset_path = asset_path
382 .strip_prefix(&format!("{}/", client_root.path))
383 .context("expected client root to contain asset path")?;
384
385 Ok(Vc::cell(
386 format!(
387 "{}{}",
388 asset_prefix.map(|s| s.as_str()).unwrap_or("/"),
389 asset_path
390 )
391 .into(),
392 ))
393 }
394
395 #[turbo_tasks::function]
396 fn chunk_root_path(&self) -> Vc<FileSystemPath> {
397 self.chunk_root_path.clone().cell()
398 }
399
400 #[turbo_tasks::function]
401 async fn chunk_path(
402 &self,
403 _asset: Option<Vc<Box<dyn Asset>>>,
404 ident: Vc<AssetIdent>,
405 prefix: Option<RcStr>,
406 extension: RcStr,
407 ) -> Result<Vc<FileSystemPath>> {
408 let root_path = self.chunk_root_path.clone();
409 let name = ident
410 .output_name(self.root_path.clone(), prefix, extension)
411 .owned()
412 .await?;
413 Ok(root_path.join(&name)?.cell())
414 }
415
416 #[turbo_tasks::function]
417 fn reference_chunk_source_maps(&self, _chunk: Vc<Box<dyn OutputAsset>>) -> Vc<bool> {
418 Vc::cell(match self.source_maps_type {
419 SourceMapsType::Full => true,
420 SourceMapsType::Partial => true,
421 SourceMapsType::None => false,
422 })
423 }
424
425 #[turbo_tasks::function]
426 fn reference_module_source_maps(&self, _module: Vc<Box<dyn Module>>) -> Vc<bool> {
427 Vc::cell(match self.source_maps_type {
428 SourceMapsType::Full => true,
429 SourceMapsType::Partial => true,
430 SourceMapsType::None => false,
431 })
432 }
433
434 #[turbo_tasks::function]
435 fn source_map_source_type(&self) -> Vc<SourceMapSourceType> {
436 self.source_map_source_type.cell()
437 }
438
439 #[turbo_tasks::function]
440 fn chunking_configs(&self) -> Result<Vc<ChunkingConfigs>> {
441 Ok(Vc::cell(self.chunking_configs.iter().cloned().collect()))
442 }
443
444 #[turbo_tasks::function]
445 async fn asset_path(
446 &self,
447 content_hash: RcStr,
448 original_asset_ident: Vc<AssetIdent>,
449 tag: Option<RcStr>,
450 ) -> Result<Vc<FileSystemPath>> {
451 let source_path = original_asset_ident.path().await?;
452 let basename = source_path.file_name();
453 let asset_path = match source_path.extension_ref() {
454 Some(ext) => format!(
455 "{basename}.{content_hash}.{ext}",
456 basename = &basename[..basename.len() - ext.len() - 1],
457 content_hash = &content_hash[..8]
458 ),
459 None => format!(
460 "{basename}.{content_hash}",
461 content_hash = &content_hash[..8]
462 ),
463 };
464
465 let asset_root_path = tag
466 .as_ref()
467 .and_then(|tag| self.asset_root_paths.get(tag))
468 .unwrap_or(&self.asset_root_path);
469
470 Ok(asset_root_path.join(&asset_path)?.cell())
471 }
472
473 #[turbo_tasks::function]
474 fn url_behavior(&self, tag: Option<RcStr>) -> Vc<UrlBehavior> {
475 tag.as_ref()
476 .and_then(|tag| self.url_behaviors.get(tag))
477 .cloned()
478 .or_else(|| self.default_url_behavior.clone())
479 .unwrap_or(UrlBehavior {
480 suffix: AssetSuffix::Inferred,
481 })
482 .cell()
483 }
484
485 #[turbo_tasks::function]
486 async fn chunk_group(
487 self: ResolvedVc<Self>,
488 ident: Vc<AssetIdent>,
489 chunk_group: ChunkGroup,
490 module_graph: Vc<ModuleGraph>,
491 availability_info: AvailabilityInfo,
492 ) -> Result<Vc<ChunkGroupResult>> {
493 let span = tracing::info_span!("chunking", name = display(ident.to_string().await?));
494 async move {
495 let modules = chunk_group.entries();
496 let MakeChunkGroupResult {
497 chunks,
498 referenced_output_assets,
499 references,
500 availability_info,
501 } = make_chunk_group(
502 modules,
503 module_graph,
504 ResolvedVc::upcast(self),
505 availability_info,
506 )
507 .await?;
508
509 let chunks = chunks.await?;
510
511 let assets = chunks
512 .iter()
513 .map(|chunk| self.generate_chunk(*chunk))
514 .try_join()
515 .await?;
516
517 Ok(ChunkGroupResult {
518 assets: ResolvedVc::cell(assets),
519 referenced_assets: ResolvedVc::cell(referenced_output_assets),
520 references: ResolvedVc::cell(references),
521 availability_info,
522 }
523 .cell())
524 }
525 .instrument(span)
526 .await
527 }
528
529 #[turbo_tasks::function]
530 pub async fn entry_chunk_group(
531 self: ResolvedVc<Self>,
532 path: FileSystemPath,
533 evaluatable_assets: Vc<EvaluatableAssets>,
534 module_graph: Vc<ModuleGraph>,
535 extra_chunks: Vc<OutputAssets>,
536 extra_referenced_assets: Vc<OutputAssets>,
537 availability_info: AvailabilityInfo,
538 ) -> Result<Vc<EntryChunkGroupResult>> {
539 let span = tracing::info_span!(
540 "chunking",
541 name = display(path.value_to_string().await?),
542 chunking_type = "entry",
543 );
544 async move {
545 let evaluatable_assets_ref = evaluatable_assets.await?;
546 let entries = evaluatable_assets_ref
547 .iter()
548 .map(|&asset| ResolvedVc::upcast::<Box<dyn Module>>(asset));
549
550 let MakeChunkGroupResult {
551 chunks,
552 mut referenced_output_assets,
553 references,
554 availability_info,
555 } = make_chunk_group(
556 entries,
557 module_graph,
558 ResolvedVc::upcast(self),
559 availability_info,
560 )
561 .await?;
562
563 let chunks = chunks.await?;
564
565 let extra_chunks = extra_chunks.await?;
566 let mut other_chunks = chunks
567 .iter()
568 .map(|chunk| self.generate_chunk(*chunk))
569 .try_join()
570 .await?;
571 other_chunks.extend(extra_chunks.iter().copied());
572
573 referenced_output_assets.extend(extra_referenced_assets.await?.iter().copied());
574
575 let Some(module) = ResolvedVc::try_sidecast(*evaluatable_assets_ref.last().unwrap())
576 else {
577 bail!("module must be placeable in an ecmascript chunk");
578 };
579
580 let asset = ResolvedVc::upcast(
581 EcmascriptBuildNodeEntryChunk::new(
582 path,
583 Vc::cell(other_chunks),
584 evaluatable_assets,
585 *module,
586 Vc::cell(referenced_output_assets),
587 Vc::cell(references),
588 module_graph,
589 *self,
590 )
591 .to_resolved()
592 .await?,
593 );
594
595 Ok(EntryChunkGroupResult {
596 asset,
597 availability_info,
598 }
599 .cell())
600 }
601 .instrument(span)
602 .await
603 }
604
605 #[turbo_tasks::function]
606 fn evaluated_chunk_group(
607 self: Vc<Self>,
608 _ident: Vc<AssetIdent>,
609 _chunk_group: ChunkGroup,
610 _module_graph: Vc<ModuleGraph>,
611 _availability_info: AvailabilityInfo,
612 ) -> Result<Vc<ChunkGroupResult>> {
613 bail!("the Node.js chunking context does not support evaluated chunk groups")
614 }
615
616 #[turbo_tasks::function]
617 fn chunk_item_id_strategy(&self) -> Vc<ModuleIdStrategy> {
618 *self
619 .module_id_strategy
620 .unwrap_or_else(|| ModuleIdStrategy::default().resolved_cell())
621 }
622
623 #[turbo_tasks::function]
624 async fn async_loader_chunk_item(
625 self: Vc<Self>,
626 module: Vc<Box<dyn ChunkableModule>>,
627 module_graph: Vc<ModuleGraph>,
628 availability_info: AvailabilityInfo,
629 ) -> Result<Vc<Box<dyn ChunkItem>>> {
630 Ok(if self.await?.manifest_chunks {
631 let manifest_asset =
632 ManifestAsyncModule::new(module, module_graph, Vc::upcast(self), availability_info);
633 Vc::upcast(ManifestLoaderChunkItem::new(
634 manifest_asset,
635 module_graph,
636 Vc::upcast(self),
637 ))
638 } else {
639 let module = AsyncLoaderModule::new(module, Vc::upcast(self), availability_info);
640 module.as_chunk_item(module_graph, Vc::upcast(self))
641 })
642 }
643
644 #[turbo_tasks::function]
645 async fn async_loader_chunk_item_ident(
646 self: Vc<Self>,
647 module: Vc<Box<dyn ChunkableModule>>,
648 ) -> Result<Vc<AssetIdent>> {
649 Ok(if self.await?.manifest_chunks {
650 ManifestLoaderChunkItem::asset_ident_for(module)
651 } else {
652 AsyncLoaderModule::asset_ident_for(module)
653 })
654 }
655
656 #[turbo_tasks::function]
657 async fn module_export_usage(
658 &self,
659 module: ResolvedVc<Box<dyn Module>>,
660 ) -> Result<Vc<ModuleExportUsage>> {
661 if let Some(export_usage) = self.export_usage {
662 Ok(export_usage.await?.used_exports(module).await?)
663 } else {
664 Ok(ModuleExportUsage::all())
665 }
666 }
667
668 #[turbo_tasks::function]
669 fn unused_references(&self) -> Vc<UnusedReferences> {
670 if let Some(unused_references) = self.unused_references {
671 *unused_references
672 } else {
673 Vc::cell(Default::default())
674 }
675 }
676
677 #[turbo_tasks::function]
678 fn debug_ids_enabled(&self) -> Vc<bool> {
679 Vc::cell(self.debug_ids)
680 }
681}