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 Chunk, ChunkGroupResult, ChunkItem, ChunkType, ChunkableModule, ChunkingConfig,
10 ChunkingConfigs, ChunkingContext, EntryChunkGroupResult, EvaluatableAssets, MinifyType,
11 SourceMapSourceType, SourceMapsType, UnusedReferences,
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 minify_type(mut self, minify_type: MinifyType) -> Self {
64 self.chunking_context.minify_type = minify_type;
65 self
66 }
67
68 pub fn source_maps(mut self, source_maps: SourceMapsType) -> Self {
69 self.chunking_context.source_maps_type = source_maps;
70 self
71 }
72
73 pub fn file_tracing(mut self, enable_tracing: bool) -> Self {
74 self.chunking_context.enable_file_tracing = enable_tracing;
75 self
76 }
77
78 pub fn nested_async_availability(mut self, enable_nested_async_availability: bool) -> Self {
79 self.chunking_context.enable_nested_async_availability = enable_nested_async_availability;
80 self
81 }
82
83 pub fn module_merging(mut self, enable_module_merging: bool) -> Self {
84 self.chunking_context.enable_module_merging = enable_module_merging;
85 self
86 }
87
88 pub fn dynamic_chunk_content_loading(
89 mut self,
90 enable_dynamic_chunk_content_loading: bool,
91 ) -> Self {
92 self.chunking_context.enable_dynamic_chunk_content_loading =
93 enable_dynamic_chunk_content_loading;
94 self
95 }
96
97 pub fn runtime_type(mut self, runtime_type: RuntimeType) -> Self {
98 self.chunking_context.runtime_type = runtime_type;
99 self
100 }
101
102 pub fn manifest_chunks(mut self, manifest_chunks: bool) -> Self {
103 self.chunking_context.manifest_chunks = manifest_chunks;
104 self
105 }
106
107 pub fn source_map_source_type(mut self, source_map_source_type: SourceMapSourceType) -> Self {
108 self.chunking_context.source_map_source_type = source_map_source_type;
109 self
110 }
111
112 pub fn module_id_strategy(mut self, module_id_strategy: ResolvedVc<ModuleIdStrategy>) -> Self {
113 self.chunking_context.module_id_strategy = Some(module_id_strategy);
114 self
115 }
116
117 pub fn export_usage(mut self, export_usage: Option<ResolvedVc<BindingUsageInfo>>) -> Self {
118 self.chunking_context.export_usage = export_usage;
119 self
120 }
121
122 pub fn unused_references(mut self, unused_references: ResolvedVc<UnusedReferences>) -> Self {
123 self.chunking_context.unused_references = Some(unused_references);
124 self
125 }
126
127 pub fn chunking_config<T>(mut self, ty: ResolvedVc<T>, chunking_config: ChunkingConfig) -> Self
128 where
129 T: Upcast<Box<dyn ChunkType>>,
130 {
131 self.chunking_context
132 .chunking_configs
133 .push((ResolvedVc::upcast_non_strict(ty), chunking_config));
134 self
135 }
136
137 pub fn debug_ids(mut self, debug_ids: bool) -> Self {
138 self.chunking_context.debug_ids = debug_ids;
139 self
140 }
141
142 pub fn build(self) -> Vc<NodeJsChunkingContext> {
144 NodeJsChunkingContext::cell(self.chunking_context)
145 }
146}
147
148#[turbo_tasks::value]
150#[derive(Debug, Clone)]
151pub struct NodeJsChunkingContext {
152 root_path: FileSystemPath,
154 output_root: FileSystemPath,
156 output_root_to_root_path: RcStr,
158 client_root: FileSystemPath,
160 #[bincode(with = "turbo_bincode::indexmap")]
162 client_roots: FxIndexMap<RcStr, FileSystemPath>,
163 chunk_root_path: FileSystemPath,
165 asset_root_path: FileSystemPath,
167 #[bincode(with = "turbo_bincode::indexmap")]
169 asset_root_paths: FxIndexMap<RcStr, FileSystemPath>,
170 asset_prefix: Option<RcStr>,
172 #[bincode(with = "turbo_bincode::indexmap")]
174 asset_prefixes: FxIndexMap<RcStr, RcStr>,
175 environment: ResolvedVc<Environment>,
177 runtime_type: RuntimeType,
179 enable_file_tracing: bool,
181 enable_nested_async_availability: bool,
183 enable_module_merging: bool,
185 enable_dynamic_chunk_content_loading: bool,
187 minify_type: MinifyType,
189 source_maps_type: SourceMapsType,
191 manifest_chunks: bool,
193 module_id_strategy: Option<ResolvedVc<ModuleIdStrategy>>,
195 export_usage: Option<ResolvedVc<BindingUsageInfo>>,
197 unused_references: Option<ResolvedVc<UnusedReferences>>,
199 source_map_source_type: SourceMapSourceType,
201 chunking_configs: Vec<(ResolvedVc<Box<dyn ChunkType>>, ChunkingConfig)>,
203 debug_ids: bool,
205}
206
207impl NodeJsChunkingContext {
208 pub fn builder(
210 root_path: FileSystemPath,
211 output_root: FileSystemPath,
212 output_root_to_root_path: RcStr,
213 client_root: FileSystemPath,
214 chunk_root_path: FileSystemPath,
215 asset_root_path: FileSystemPath,
216 environment: ResolvedVc<Environment>,
217 runtime_type: RuntimeType,
218 ) -> NodeJsChunkingContextBuilder {
219 NodeJsChunkingContextBuilder {
220 chunking_context: NodeJsChunkingContext {
221 root_path,
222 output_root,
223 output_root_to_root_path,
224 client_root,
225 client_roots: Default::default(),
226 chunk_root_path,
227 asset_root_path,
228 asset_root_paths: Default::default(),
229 asset_prefix: None,
230 asset_prefixes: Default::default(),
231 enable_file_tracing: false,
232 enable_nested_async_availability: false,
233 enable_module_merging: false,
234 enable_dynamic_chunk_content_loading: false,
235 environment,
236 runtime_type,
237 minify_type: MinifyType::NoMinify,
238 source_maps_type: SourceMapsType::Full,
239 manifest_chunks: false,
240 source_map_source_type: SourceMapSourceType::TurbopackUri,
241 module_id_strategy: None,
242 export_usage: None,
243 unused_references: None,
244 chunking_configs: Default::default(),
245 debug_ids: false,
246 },
247 }
248 }
249}
250
251#[turbo_tasks::value_impl]
252impl NodeJsChunkingContext {
253 #[turbo_tasks::function]
254 async fn generate_chunk(
255 self: Vc<Self>,
256 chunk: ResolvedVc<Box<dyn Chunk>>,
257 ) -> Result<Vc<Box<dyn OutputAsset>>> {
258 Ok(
259 if let Some(ecmascript_chunk) = ResolvedVc::try_downcast_type::<EcmascriptChunk>(chunk)
260 {
261 Vc::upcast(EcmascriptBuildNodeChunk::new(self, *ecmascript_chunk))
262 } else if let Some(output_asset) =
263 ResolvedVc::try_sidecast::<Box<dyn OutputAsset>>(chunk)
264 {
265 *output_asset
266 } else {
267 bail!("Unable to generate output asset for chunk");
268 },
269 )
270 }
271
272 #[turbo_tasks::function]
277 pub fn runtime_type(&self) -> Vc<RuntimeType> {
278 self.runtime_type.cell()
279 }
280
281 #[turbo_tasks::function]
283 pub fn minify_type(&self) -> Vc<MinifyType> {
284 self.minify_type.cell()
285 }
286
287 #[turbo_tasks::function]
288 pub fn asset_prefix(&self) -> Vc<Option<RcStr>> {
289 Vc::cell(self.asset_prefix.clone())
290 }
291}
292
293#[turbo_tasks::value_impl]
294impl ChunkingContext for NodeJsChunkingContext {
295 #[turbo_tasks::function]
296 fn name(&self) -> Vc<RcStr> {
297 Vc::cell(rcstr!("unknown"))
298 }
299
300 #[turbo_tasks::function]
301 fn root_path(&self) -> Vc<FileSystemPath> {
302 self.root_path.clone().cell()
303 }
304
305 #[turbo_tasks::function]
306 fn output_root(&self) -> Vc<FileSystemPath> {
307 self.output_root.clone().cell()
308 }
309
310 #[turbo_tasks::function]
311 fn output_root_to_root_path(&self) -> Vc<RcStr> {
312 Vc::cell(self.output_root_to_root_path.clone())
313 }
314
315 #[turbo_tasks::function]
316 fn environment(&self) -> Vc<Environment> {
317 *self.environment
318 }
319
320 #[turbo_tasks::function]
321 fn is_tracing_enabled(&self) -> Vc<bool> {
322 Vc::cell(self.enable_file_tracing)
323 }
324
325 #[turbo_tasks::function]
326 fn is_nested_async_availability_enabled(&self) -> Vc<bool> {
327 Vc::cell(self.enable_nested_async_availability)
328 }
329
330 #[turbo_tasks::function]
331 fn is_module_merging_enabled(&self) -> Vc<bool> {
332 Vc::cell(self.enable_module_merging)
333 }
334
335 #[turbo_tasks::function]
336 fn is_dynamic_chunk_content_loading_enabled(&self) -> Vc<bool> {
337 Vc::cell(self.enable_dynamic_chunk_content_loading)
338 }
339
340 #[turbo_tasks::function]
341 pub fn minify_type(&self) -> Vc<MinifyType> {
342 self.minify_type.cell()
343 }
344
345 #[turbo_tasks::function]
346 async fn asset_url(&self, ident: FileSystemPath, tag: Option<RcStr>) -> Result<Vc<RcStr>> {
347 let asset_path = ident.to_string();
348
349 let client_root = tag
350 .as_ref()
351 .and_then(|tag| self.client_roots.get(tag))
352 .unwrap_or(&self.client_root);
353
354 let asset_prefix = tag
355 .as_ref()
356 .and_then(|tag| self.asset_prefixes.get(tag))
357 .or(self.asset_prefix.as_ref());
358
359 let asset_path = asset_path
360 .strip_prefix(&format!("{}/", client_root.path))
361 .context("expected client root to contain asset path")?;
362
363 Ok(Vc::cell(
364 format!(
365 "{}{}",
366 asset_prefix.map(|s| s.as_str()).unwrap_or("/"),
367 asset_path
368 )
369 .into(),
370 ))
371 }
372
373 #[turbo_tasks::function]
374 fn chunk_root_path(&self) -> Vc<FileSystemPath> {
375 self.chunk_root_path.clone().cell()
376 }
377
378 #[turbo_tasks::function]
379 async fn chunk_path(
380 &self,
381 _asset: Option<Vc<Box<dyn Asset>>>,
382 ident: Vc<AssetIdent>,
383 prefix: Option<RcStr>,
384 extension: RcStr,
385 ) -> Result<Vc<FileSystemPath>> {
386 let root_path = self.chunk_root_path.clone();
387 let name = ident
388 .output_name(self.root_path.clone(), prefix, extension)
389 .owned()
390 .await?;
391 Ok(root_path.join(&name)?.cell())
392 }
393
394 #[turbo_tasks::function]
395 fn reference_chunk_source_maps(&self, _chunk: Vc<Box<dyn OutputAsset>>) -> Vc<bool> {
396 Vc::cell(match self.source_maps_type {
397 SourceMapsType::Full => true,
398 SourceMapsType::Partial => true,
399 SourceMapsType::None => false,
400 })
401 }
402
403 #[turbo_tasks::function]
404 fn reference_module_source_maps(&self, _module: Vc<Box<dyn Module>>) -> Vc<bool> {
405 Vc::cell(match self.source_maps_type {
406 SourceMapsType::Full => true,
407 SourceMapsType::Partial => true,
408 SourceMapsType::None => false,
409 })
410 }
411
412 #[turbo_tasks::function]
413 fn source_map_source_type(&self) -> Vc<SourceMapSourceType> {
414 self.source_map_source_type.cell()
415 }
416
417 #[turbo_tasks::function]
418 fn chunking_configs(&self) -> Result<Vc<ChunkingConfigs>> {
419 Ok(Vc::cell(self.chunking_configs.iter().cloned().collect()))
420 }
421
422 #[turbo_tasks::function]
423 async fn asset_path(
424 &self,
425 content_hash: RcStr,
426 original_asset_ident: Vc<AssetIdent>,
427 tag: Option<RcStr>,
428 ) -> Result<Vc<FileSystemPath>> {
429 let source_path = original_asset_ident.path().await?;
430 let basename = source_path.file_name();
431 let asset_path = match source_path.extension_ref() {
432 Some(ext) => format!(
433 "{basename}.{content_hash}.{ext}",
434 basename = &basename[..basename.len() - ext.len() - 1],
435 content_hash = &content_hash[..8]
436 ),
437 None => format!(
438 "{basename}.{content_hash}",
439 content_hash = &content_hash[..8]
440 ),
441 };
442
443 let asset_root_path = tag
444 .as_ref()
445 .and_then(|tag| self.asset_root_paths.get(tag))
446 .unwrap_or(&self.asset_root_path);
447
448 Ok(asset_root_path.join(&asset_path)?.cell())
449 }
450
451 #[turbo_tasks::function]
452 async fn chunk_group(
453 self: ResolvedVc<Self>,
454 ident: Vc<AssetIdent>,
455 chunk_group: ChunkGroup,
456 module_graph: Vc<ModuleGraph>,
457 availability_info: AvailabilityInfo,
458 ) -> Result<Vc<ChunkGroupResult>> {
459 let span = tracing::info_span!("chunking", name = display(ident.to_string().await?));
460 async move {
461 let modules = chunk_group.entries();
462 let MakeChunkGroupResult {
463 chunks,
464 referenced_output_assets,
465 references,
466 availability_info,
467 } = make_chunk_group(
468 modules,
469 module_graph,
470 ResolvedVc::upcast(self),
471 availability_info,
472 )
473 .await?;
474
475 let chunks = chunks.await?;
476
477 let assets = chunks
478 .iter()
479 .map(|chunk| self.generate_chunk(**chunk).to_resolved())
480 .try_join()
481 .await?;
482
483 Ok(ChunkGroupResult {
484 assets: ResolvedVc::cell(assets),
485 referenced_assets: ResolvedVc::cell(referenced_output_assets),
486 references: ResolvedVc::cell(references),
487 availability_info,
488 }
489 .cell())
490 }
491 .instrument(span)
492 .await
493 }
494
495 #[turbo_tasks::function]
496 pub async fn entry_chunk_group(
497 self: ResolvedVc<Self>,
498 path: FileSystemPath,
499 evaluatable_assets: Vc<EvaluatableAssets>,
500 module_graph: Vc<ModuleGraph>,
501 extra_chunks: Vc<OutputAssets>,
502 extra_referenced_assets: Vc<OutputAssets>,
503 availability_info: AvailabilityInfo,
504 ) -> Result<Vc<EntryChunkGroupResult>> {
505 let span = tracing::info_span!(
506 "chunking",
507 name = display(path.value_to_string().await?),
508 chunking_type = "entry",
509 );
510 async move {
511 let evaluatable_assets_ref = evaluatable_assets.await?;
512 let entries = evaluatable_assets_ref
513 .iter()
514 .map(|&asset| ResolvedVc::upcast::<Box<dyn Module>>(asset));
515
516 let MakeChunkGroupResult {
517 chunks,
518 mut referenced_output_assets,
519 references,
520 availability_info,
521 } = make_chunk_group(
522 entries,
523 module_graph,
524 ResolvedVc::upcast(self),
525 availability_info,
526 )
527 .await?;
528
529 let chunks = chunks.await?;
530
531 let extra_chunks = extra_chunks.await?;
532 let mut other_chunks = chunks
533 .iter()
534 .map(|chunk| self.generate_chunk(**chunk).to_resolved())
535 .try_join()
536 .await?;
537 other_chunks.extend(extra_chunks.iter().copied());
538
539 referenced_output_assets.extend(extra_referenced_assets.await?.iter().copied());
540
541 let Some(module) = ResolvedVc::try_sidecast(*evaluatable_assets_ref.last().unwrap())
542 else {
543 bail!("module must be placeable in an ecmascript chunk");
544 };
545
546 let asset = ResolvedVc::upcast(
547 EcmascriptBuildNodeEntryChunk::new(
548 path,
549 Vc::cell(other_chunks),
550 evaluatable_assets,
551 *module,
552 Vc::cell(referenced_output_assets),
553 Vc::cell(references),
554 module_graph,
555 *self,
556 )
557 .to_resolved()
558 .await?,
559 );
560
561 Ok(EntryChunkGroupResult {
562 asset,
563 availability_info,
564 }
565 .cell())
566 }
567 .instrument(span)
568 .await
569 }
570
571 #[turbo_tasks::function]
572 fn evaluated_chunk_group(
573 self: Vc<Self>,
574 _ident: Vc<AssetIdent>,
575 _chunk_group: ChunkGroup,
576 _module_graph: Vc<ModuleGraph>,
577 _availability_info: AvailabilityInfo,
578 ) -> Result<Vc<ChunkGroupResult>> {
579 bail!("the Node.js chunking context does not support evaluated chunk groups")
580 }
581
582 #[turbo_tasks::function]
583 fn chunk_item_id_strategy(&self) -> Vc<ModuleIdStrategy> {
584 *self
585 .module_id_strategy
586 .unwrap_or_else(|| ModuleIdStrategy::default().resolved_cell())
587 }
588
589 #[turbo_tasks::function]
590 async fn async_loader_chunk_item(
591 self: Vc<Self>,
592 module: Vc<Box<dyn ChunkableModule>>,
593 module_graph: Vc<ModuleGraph>,
594 availability_info: AvailabilityInfo,
595 ) -> Result<Vc<Box<dyn ChunkItem>>> {
596 Ok(if self.await?.manifest_chunks {
597 let manifest_asset =
598 ManifestAsyncModule::new(module, module_graph, Vc::upcast(self), availability_info);
599 Vc::upcast(ManifestLoaderChunkItem::new(
600 manifest_asset,
601 module_graph,
602 Vc::upcast(self),
603 ))
604 } else {
605 let module = AsyncLoaderModule::new(module, Vc::upcast(self), availability_info);
606 module.as_chunk_item(module_graph, Vc::upcast(self))
607 })
608 }
609
610 #[turbo_tasks::function]
611 async fn async_loader_chunk_item_ident(
612 self: Vc<Self>,
613 module: Vc<Box<dyn ChunkableModule>>,
614 ) -> Result<Vc<AssetIdent>> {
615 Ok(if self.await?.manifest_chunks {
616 ManifestLoaderChunkItem::asset_ident_for(module)
617 } else {
618 AsyncLoaderModule::asset_ident_for(module)
619 })
620 }
621
622 #[turbo_tasks::function]
623 async fn module_export_usage(
624 &self,
625 module: ResolvedVc<Box<dyn Module>>,
626 ) -> Result<Vc<ModuleExportUsage>> {
627 if let Some(export_usage) = self.export_usage {
628 Ok(export_usage.await?.used_exports(module).await?)
629 } else {
630 Ok(ModuleExportUsage::all())
631 }
632 }
633
634 #[turbo_tasks::function]
635 fn unused_references(&self) -> Vc<UnusedReferences> {
636 if let Some(unused_references) = self.unused_references {
637 *unused_references
638 } else {
639 Vc::cell(Default::default())
640 }
641 }
642
643 #[turbo_tasks::function]
644 fn debug_ids_enabled(&self) -> Vc<bool> {
645 Vc::cell(self.debug_ids)
646 }
647}