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(ty), chunking_config));
112 self
113 }
114
115 pub fn build(self) -> Vc<NodeJsChunkingContext> {
117 NodeJsChunkingContext::cell(self.chunking_context)
118 }
119}
120
121#[turbo_tasks::value]
123#[derive(Debug, Clone, Hash, TaskInput)]
124pub struct NodeJsChunkingContext {
125 root_path: FileSystemPath,
127 output_root: FileSystemPath,
129 output_root_to_root_path: RcStr,
131 client_root: FileSystemPath,
133 chunk_root_path: FileSystemPath,
135 asset_root_path: FileSystemPath,
137 asset_prefix: Option<RcStr>,
139 environment: ResolvedVc<Environment>,
141 runtime_type: RuntimeType,
143 enable_file_tracing: bool,
145 enable_module_merging: bool,
147 enable_dynamic_chunk_content_loading: bool,
149 minify_type: MinifyType,
151 source_maps_type: SourceMapsType,
153 manifest_chunks: bool,
155 module_id_strategy: ResolvedVc<Box<dyn ModuleIdStrategy>>,
157 export_usage: Option<ResolvedVc<ExportUsageInfo>>,
159 should_use_file_source_map_uris: bool,
161 chunking_configs: Vec<(ResolvedVc<Box<dyn ChunkType>>, ChunkingConfig)>,
163}
164
165impl NodeJsChunkingContext {
166 pub fn builder(
168 root_path: FileSystemPath,
169 output_root: FileSystemPath,
170 output_root_to_root_path: RcStr,
171 client_root: FileSystemPath,
172 chunk_root_path: FileSystemPath,
173 asset_root_path: FileSystemPath,
174 environment: ResolvedVc<Environment>,
175 runtime_type: RuntimeType,
176 ) -> NodeJsChunkingContextBuilder {
177 NodeJsChunkingContextBuilder {
178 chunking_context: NodeJsChunkingContext {
179 root_path,
180 output_root,
181 output_root_to_root_path,
182 client_root,
183 chunk_root_path,
184 asset_root_path,
185 asset_prefix: None,
186 enable_file_tracing: false,
187 enable_module_merging: false,
188 enable_dynamic_chunk_content_loading: false,
189 environment,
190 runtime_type,
191 minify_type: MinifyType::NoMinify,
192 source_maps_type: SourceMapsType::Full,
193 manifest_chunks: false,
194 should_use_file_source_map_uris: false,
195 module_id_strategy: ResolvedVc::upcast(DevModuleIdStrategy::new_resolved()),
196 export_usage: None,
197 chunking_configs: Default::default(),
198 },
199 }
200 }
201}
202
203#[turbo_tasks::value_impl]
204impl NodeJsChunkingContext {
205 #[turbo_tasks::function]
206 async fn generate_chunk(
207 self: Vc<Self>,
208 chunk: Vc<Box<dyn Chunk>>,
209 ) -> Result<Vc<Box<dyn OutputAsset>>> {
210 Ok(
211 if let Some(ecmascript_chunk) =
212 Vc::try_resolve_downcast_type::<EcmascriptChunk>(chunk).await?
213 {
214 Vc::upcast(EcmascriptBuildNodeChunk::new(self, ecmascript_chunk))
215 } else if let Some(output_asset) =
216 Vc::try_resolve_sidecast::<Box<dyn OutputAsset>>(chunk).await?
217 {
218 output_asset
219 } else {
220 bail!("Unable to generate output asset for chunk");
221 },
222 )
223 }
224
225 #[turbo_tasks::function]
230 pub fn runtime_type(&self) -> Vc<RuntimeType> {
231 self.runtime_type.cell()
232 }
233
234 #[turbo_tasks::function]
236 pub fn minify_type(&self) -> Vc<MinifyType> {
237 self.minify_type.cell()
238 }
239
240 #[turbo_tasks::function]
241 pub fn asset_prefix(&self) -> Vc<Option<RcStr>> {
242 Vc::cell(self.asset_prefix.clone())
243 }
244}
245
246#[turbo_tasks::value_impl]
247impl ChunkingContext for NodeJsChunkingContext {
248 #[turbo_tasks::function]
249 fn name(&self) -> Vc<RcStr> {
250 Vc::cell(rcstr!("unknown"))
251 }
252
253 #[turbo_tasks::function]
254 fn root_path(&self) -> Vc<FileSystemPath> {
255 self.root_path.clone().cell()
256 }
257
258 #[turbo_tasks::function]
259 fn output_root(&self) -> Vc<FileSystemPath> {
260 self.output_root.clone().cell()
261 }
262
263 #[turbo_tasks::function]
264 fn output_root_to_root_path(&self) -> Vc<RcStr> {
265 Vc::cell(self.output_root_to_root_path.clone())
266 }
267
268 #[turbo_tasks::function]
269 fn environment(&self) -> Vc<Environment> {
270 *self.environment
271 }
272
273 #[turbo_tasks::function]
274 fn is_tracing_enabled(&self) -> Vc<bool> {
275 Vc::cell(self.enable_file_tracing)
276 }
277
278 #[turbo_tasks::function]
279 fn is_module_merging_enabled(&self) -> Vc<bool> {
280 Vc::cell(self.enable_module_merging)
281 }
282
283 #[turbo_tasks::function]
284 fn is_dynamic_chunk_content_loading_enabled(&self) -> Vc<bool> {
285 Vc::cell(self.enable_dynamic_chunk_content_loading)
286 }
287
288 #[turbo_tasks::function]
289 pub fn minify_type(&self) -> Vc<MinifyType> {
290 self.minify_type.cell()
291 }
292
293 #[turbo_tasks::function]
294 async fn asset_url(&self, ident: FileSystemPath) -> Result<Vc<RcStr>> {
295 let asset_path = ident.to_string();
296 let asset_path = asset_path
297 .strip_prefix(&format!("{}/", self.client_root.path))
298 .context("expected client root to contain asset path")?;
299
300 Ok(Vc::cell(
301 format!(
302 "{}{}",
303 self.asset_prefix.clone().unwrap_or(rcstr!("/")),
304 asset_path
305 )
306 .into(),
307 ))
308 }
309
310 #[turbo_tasks::function]
311 fn chunk_root_path(&self) -> Vc<FileSystemPath> {
312 self.chunk_root_path.clone().cell()
313 }
314
315 #[turbo_tasks::function]
316 async fn chunk_path(
317 &self,
318 _asset: Option<Vc<Box<dyn Asset>>>,
319 ident: Vc<AssetIdent>,
320 prefix: Option<RcStr>,
321 extension: RcStr,
322 ) -> Result<Vc<FileSystemPath>> {
323 let root_path = self.chunk_root_path.clone();
324 let name = ident
325 .output_name(self.root_path.clone(), prefix, extension)
326 .owned()
327 .await?;
328 Ok(root_path.join(&name)?.cell())
329 }
330
331 #[turbo_tasks::function]
332 fn reference_chunk_source_maps(&self, _chunk: Vc<Box<dyn OutputAsset>>) -> Vc<bool> {
333 Vc::cell(match self.source_maps_type {
334 SourceMapsType::Full => true,
335 SourceMapsType::None => false,
336 })
337 }
338
339 #[turbo_tasks::function]
340 fn reference_module_source_maps(&self, _module: Vc<Box<dyn Module>>) -> Vc<bool> {
341 Vc::cell(match self.source_maps_type {
342 SourceMapsType::Full => true,
343 SourceMapsType::None => false,
344 })
345 }
346
347 #[turbo_tasks::function]
348 fn should_use_file_source_map_uris(&self) -> Vc<bool> {
349 Vc::cell(self.should_use_file_source_map_uris)
350 }
351
352 #[turbo_tasks::function]
353 fn chunking_configs(&self) -> Result<Vc<ChunkingConfigs>> {
354 Ok(Vc::cell(self.chunking_configs.iter().cloned().collect()))
355 }
356
357 #[turbo_tasks::function]
358 async fn asset_path(
359 &self,
360 content_hash: RcStr,
361 original_asset_ident: Vc<AssetIdent>,
362 ) -> Result<Vc<FileSystemPath>> {
363 let source_path = original_asset_ident.path().await?;
364 let basename = source_path.file_name();
365 let asset_path = match source_path.extension_ref() {
366 Some(ext) => format!(
367 "{basename}.{content_hash}.{ext}",
368 basename = &basename[..basename.len() - ext.len() - 1],
369 content_hash = &content_hash[..8]
370 ),
371 None => format!(
372 "{basename}.{content_hash}",
373 content_hash = &content_hash[..8]
374 ),
375 };
376 Ok(self.asset_root_path.join(&asset_path)?.cell())
377 }
378
379 #[turbo_tasks::function]
380 async fn chunk_group(
381 self: ResolvedVc<Self>,
382 ident: Vc<AssetIdent>,
383 chunk_group: ChunkGroup,
384 module_graph: Vc<ModuleGraph>,
385 availability_info: AvailabilityInfo,
386 ) -> Result<Vc<ChunkGroupResult>> {
387 let span = tracing::info_span!("chunking", name = ident.to_string().await?.to_string());
388 async move {
389 let modules = chunk_group.entries();
390 let MakeChunkGroupResult {
391 chunks,
392 availability_info,
393 } = make_chunk_group(
394 modules,
395 module_graph,
396 ResolvedVc::upcast(self),
397 availability_info,
398 )
399 .await?;
400
401 let assets = chunks
402 .iter()
403 .map(|chunk| self.generate_chunk(**chunk).to_resolved())
404 .try_join()
405 .await?;
406
407 Ok(ChunkGroupResult {
408 assets: ResolvedVc::cell(assets),
409 availability_info,
410 }
411 .cell())
412 }
413 .instrument(span)
414 .await
415 }
416
417 #[turbo_tasks::function]
418 pub async fn entry_chunk_group(
419 self: ResolvedVc<Self>,
420 path: FileSystemPath,
421 evaluatable_assets: Vc<EvaluatableAssets>,
422 module_graph: Vc<ModuleGraph>,
423 extra_chunks: Vc<OutputAssets>,
424 availability_info: AvailabilityInfo,
425 ) -> Result<Vc<EntryChunkGroupResult>> {
426 let evaluatable_assets_ref = evaluatable_assets.await?;
427 let entries = evaluatable_assets_ref
428 .iter()
429 .map(|&asset| ResolvedVc::upcast::<Box<dyn Module>>(asset));
430
431 let MakeChunkGroupResult {
432 chunks,
433 availability_info,
434 } = make_chunk_group(
435 entries,
436 module_graph,
437 ResolvedVc::upcast(self),
438 availability_info,
439 )
440 .await?;
441
442 let extra_chunks = extra_chunks.await?;
443 let other_chunks: Vec<_> = extra_chunks
444 .iter()
445 .copied()
446 .chain(
447 chunks
448 .iter()
449 .map(|chunk| self.generate_chunk(**chunk).to_resolved())
450 .try_join()
451 .await?,
452 )
453 .collect();
454
455 let Some(module) = ResolvedVc::try_sidecast(*evaluatable_assets_ref.last().unwrap()) else {
456 bail!("module must be placeable in an ecmascript chunk");
457 };
458
459 let asset = ResolvedVc::upcast(
460 EcmascriptBuildNodeEntryChunk::new(
461 path,
462 Vc::cell(other_chunks),
463 evaluatable_assets,
464 *module,
465 module_graph,
466 *self,
467 )
468 .to_resolved()
469 .await?,
470 );
471
472 Ok(EntryChunkGroupResult {
473 asset,
474 availability_info,
475 }
476 .cell())
477 }
478
479 #[turbo_tasks::function]
480 fn evaluated_chunk_group(
481 self: Vc<Self>,
482 _ident: Vc<AssetIdent>,
483 _chunk_group: ChunkGroup,
484 _module_graph: Vc<ModuleGraph>,
485 _availability_info: AvailabilityInfo,
486 ) -> Result<Vc<ChunkGroupResult>> {
487 bail!("the Node.js chunking context does not support evaluated chunk groups")
488 }
489
490 #[turbo_tasks::function]
491 fn chunk_item_id_from_ident(&self, ident: Vc<AssetIdent>) -> Vc<ModuleId> {
492 self.module_id_strategy.get_module_id(ident)
493 }
494
495 #[turbo_tasks::function]
496 async fn async_loader_chunk_item(
497 self: Vc<Self>,
498 module: Vc<Box<dyn ChunkableModule>>,
499 module_graph: Vc<ModuleGraph>,
500 availability_info: AvailabilityInfo,
501 ) -> Result<Vc<Box<dyn ChunkItem>>> {
502 Ok(if self.await?.manifest_chunks {
503 let manifest_asset =
504 ManifestAsyncModule::new(module, module_graph, Vc::upcast(self), availability_info);
505 Vc::upcast(ManifestLoaderChunkItem::new(
506 manifest_asset,
507 module_graph,
508 Vc::upcast(self),
509 ))
510 } else {
511 let module = AsyncLoaderModule::new(module, Vc::upcast(self), availability_info);
512 Vc::upcast(module.as_chunk_item(module_graph, Vc::upcast(self)))
513 })
514 }
515
516 #[turbo_tasks::function]
517 async fn async_loader_chunk_item_id(
518 self: Vc<Self>,
519 module: Vc<Box<dyn ChunkableModule>>,
520 ) -> Result<Vc<ModuleId>> {
521 Ok(if self.await?.manifest_chunks {
522 self.chunk_item_id_from_ident(ManifestLoaderChunkItem::asset_ident_for(module))
523 } else {
524 self.chunk_item_id_from_ident(AsyncLoaderModule::asset_ident_for(module))
525 })
526 }
527
528 #[turbo_tasks::function]
529 async fn module_export_usage(
530 self: Vc<Self>,
531 module: ResolvedVc<Box<dyn Module>>,
532 ) -> Result<Vc<ModuleExportUsage>> {
533 if let Some(export_usage) = self.await?.export_usage {
534 Ok(export_usage.await?.used_exports(module).await?)
535 } else {
536 Ok(ModuleExportUsage::all())
539 }
540 }
541}