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