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