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