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::{ModuleGraph, chunk_group_info::ChunkGroup},
20 output::{OutputAsset, OutputAssets},
21};
22use turbopack_ecmascript::{
23 async_chunk::module::AsyncLoaderModule,
24 chunk::EcmascriptChunk,
25 manifest::{chunk_asset::ManifestAsyncModule, loader_item::ManifestLoaderChunkItem},
26};
27use turbopack_ecmascript_runtime::RuntimeType;
28
29use crate::ecmascript::node::{
30 chunk::EcmascriptBuildNodeChunk, entry::chunk::EcmascriptBuildNodeEntryChunk,
31};
32
33pub struct NodeJsChunkingContextBuilder {
35 chunking_context: NodeJsChunkingContext,
36}
37
38impl NodeJsChunkingContextBuilder {
39 pub fn asset_prefix(mut self, asset_prefix: Option<RcStr>) -> Self {
40 self.chunking_context.asset_prefix = asset_prefix;
41 self
42 }
43
44 pub fn minify_type(mut self, minify_type: MinifyType) -> Self {
45 self.chunking_context.minify_type = minify_type;
46 self
47 }
48
49 pub fn source_maps(mut self, source_maps: SourceMapsType) -> Self {
50 self.chunking_context.source_maps_type = source_maps;
51 self
52 }
53
54 pub fn file_tracing(mut self, enable_tracing: bool) -> Self {
55 self.chunking_context.enable_file_tracing = enable_tracing;
56 self
57 }
58
59 pub fn module_merging(mut self, enable_module_merging: bool) -> Self {
60 self.chunking_context.enable_module_merging = enable_module_merging;
61 self
62 }
63
64 pub fn runtime_type(mut self, runtime_type: RuntimeType) -> Self {
65 self.chunking_context.runtime_type = runtime_type;
66 self
67 }
68
69 pub fn manifest_chunks(mut self, manifest_chunks: bool) -> Self {
70 self.chunking_context.manifest_chunks = manifest_chunks;
71 self
72 }
73
74 pub fn use_file_source_map_uris(mut self) -> Self {
75 self.chunking_context.should_use_file_source_map_uris = true;
76 self
77 }
78
79 pub fn module_id_strategy(
80 mut self,
81 module_id_strategy: ResolvedVc<Box<dyn ModuleIdStrategy>>,
82 ) -> Self {
83 self.chunking_context.module_id_strategy = module_id_strategy;
84 self
85 }
86
87 pub fn chunking_config<T>(mut self, ty: ResolvedVc<T>, chunking_config: ChunkingConfig) -> Self
88 where
89 T: Upcast<Box<dyn ChunkType>>,
90 {
91 self.chunking_context
92 .chunking_configs
93 .push((ResolvedVc::upcast(ty), chunking_config));
94 self
95 }
96
97 pub fn build(self) -> Vc<NodeJsChunkingContext> {
99 NodeJsChunkingContext::cell(self.chunking_context)
100 }
101}
102
103#[turbo_tasks::value]
105#[derive(Debug, Clone, Hash, TaskInput)]
106pub struct NodeJsChunkingContext {
107 root_path: FileSystemPath,
109 output_root: FileSystemPath,
111 output_root_to_root_path: RcStr,
113 client_root: FileSystemPath,
115 chunk_root_path: FileSystemPath,
117 asset_root_path: FileSystemPath,
119 asset_prefix: Option<RcStr>,
121 environment: ResolvedVc<Environment>,
123 runtime_type: RuntimeType,
125 enable_file_tracing: bool,
127 enable_module_merging: bool,
129 minify_type: MinifyType,
131 source_maps_type: SourceMapsType,
133 manifest_chunks: bool,
135 module_id_strategy: ResolvedVc<Box<dyn ModuleIdStrategy>>,
137 should_use_file_source_map_uris: bool,
139 chunking_configs: Vec<(ResolvedVc<Box<dyn ChunkType>>, ChunkingConfig)>,
141}
142
143impl NodeJsChunkingContext {
144 pub fn builder(
146 root_path: FileSystemPath,
147 output_root: FileSystemPath,
148 output_root_to_root_path: RcStr,
149 client_root: FileSystemPath,
150 chunk_root_path: FileSystemPath,
151 asset_root_path: FileSystemPath,
152 environment: ResolvedVc<Environment>,
153 runtime_type: RuntimeType,
154 ) -> NodeJsChunkingContextBuilder {
155 NodeJsChunkingContextBuilder {
156 chunking_context: NodeJsChunkingContext {
157 root_path,
158 output_root,
159 output_root_to_root_path,
160 client_root,
161 chunk_root_path,
162 asset_root_path,
163 asset_prefix: None,
164 enable_file_tracing: false,
165 enable_module_merging: false,
166 environment,
167 runtime_type,
168 minify_type: MinifyType::NoMinify,
169 source_maps_type: SourceMapsType::Full,
170 manifest_chunks: false,
171 should_use_file_source_map_uris: false,
172 module_id_strategy: ResolvedVc::upcast(DevModuleIdStrategy::new_resolved()),
173 chunking_configs: Default::default(),
174 },
175 }
176 }
177}
178
179impl NodeJsChunkingContext {
180 pub fn runtime_type(&self) -> RuntimeType {
185 self.runtime_type
186 }
187
188 pub fn minify_type(&self) -> MinifyType {
190 self.minify_type
191 }
192}
193
194impl NodeJsChunkingContext {
195 pub async fn asset_prefix(self: Vc<Self>) -> Result<Option<RcStr>> {
196 Ok(self.await?.asset_prefix.clone())
197 }
198}
199
200#[turbo_tasks::value_impl]
201impl NodeJsChunkingContext {
202 #[turbo_tasks::function]
203 async fn generate_chunk(
204 self: Vc<Self>,
205 chunk: Vc<Box<dyn Chunk>>,
206 ) -> Result<Vc<Box<dyn OutputAsset>>> {
207 Ok(
208 if let Some(ecmascript_chunk) =
209 Vc::try_resolve_downcast_type::<EcmascriptChunk>(chunk).await?
210 {
211 Vc::upcast(EcmascriptBuildNodeChunk::new(self, ecmascript_chunk))
212 } else if let Some(output_asset) =
213 Vc::try_resolve_sidecast::<Box<dyn OutputAsset>>(chunk).await?
214 {
215 output_asset
216 } else {
217 bail!("Unable to generate output asset for chunk");
218 },
219 )
220 }
221}
222
223#[turbo_tasks::value_impl]
224impl ChunkingContext for NodeJsChunkingContext {
225 #[turbo_tasks::function]
226 fn name(&self) -> Vc<RcStr> {
227 Vc::cell(rcstr!("unknown"))
228 }
229
230 #[turbo_tasks::function]
231 fn root_path(&self) -> Vc<FileSystemPath> {
232 self.root_path.clone().cell()
233 }
234
235 #[turbo_tasks::function]
236 fn output_root(&self) -> Vc<FileSystemPath> {
237 self.output_root.clone().cell()
238 }
239
240 #[turbo_tasks::function]
241 fn output_root_to_root_path(&self) -> Vc<RcStr> {
242 Vc::cell(self.output_root_to_root_path.clone())
243 }
244
245 #[turbo_tasks::function]
246 fn environment(&self) -> Vc<Environment> {
247 *self.environment
248 }
249
250 #[turbo_tasks::function]
251 fn is_tracing_enabled(&self) -> Vc<bool> {
252 Vc::cell(self.enable_file_tracing)
253 }
254
255 #[turbo_tasks::function]
256 fn is_module_merging_enabled(&self) -> Vc<bool> {
257 Vc::cell(self.enable_module_merging)
258 }
259
260 #[turbo_tasks::function]
261 pub fn minify_type(&self) -> Vc<MinifyType> {
262 self.minify_type.cell()
263 }
264
265 #[turbo_tasks::function]
266 async fn asset_url(&self, ident: FileSystemPath) -> Result<Vc<RcStr>> {
267 let asset_path = ident.to_string();
268 let asset_path = asset_path
269 .strip_prefix(&format!("{}/", self.client_root.path))
270 .context("expected client root to contain asset path")?;
271
272 Ok(Vc::cell(
273 format!(
274 "{}{}",
275 self.asset_prefix.clone().unwrap_or(rcstr!("/")),
276 asset_path
277 )
278 .into(),
279 ))
280 }
281
282 #[turbo_tasks::function]
283 fn chunk_root_path(&self) -> Vc<FileSystemPath> {
284 self.chunk_root_path.clone().cell()
285 }
286
287 #[turbo_tasks::function]
288 async fn chunk_path(
289 &self,
290 _asset: Option<Vc<Box<dyn Asset>>>,
291 ident: Vc<AssetIdent>,
292 extension: RcStr,
293 ) -> Result<Vc<FileSystemPath>> {
294 let root_path = self.chunk_root_path.clone();
295 let name = ident
296 .output_name(self.root_path.clone(), extension)
297 .owned()
298 .await?;
299 Ok(root_path.join(&name)?.cell())
300 }
301
302 #[turbo_tasks::function]
303 fn reference_chunk_source_maps(&self, _chunk: Vc<Box<dyn OutputAsset>>) -> Vc<bool> {
304 Vc::cell(match self.source_maps_type {
305 SourceMapsType::Full => true,
306 SourceMapsType::None => false,
307 })
308 }
309
310 #[turbo_tasks::function]
311 fn reference_module_source_maps(&self, _module: Vc<Box<dyn Module>>) -> Vc<bool> {
312 Vc::cell(match self.source_maps_type {
313 SourceMapsType::Full => true,
314 SourceMapsType::None => false,
315 })
316 }
317
318 #[turbo_tasks::function]
319 fn should_use_file_source_map_uris(&self) -> Vc<bool> {
320 Vc::cell(self.should_use_file_source_map_uris)
321 }
322
323 #[turbo_tasks::function]
324 fn chunking_configs(&self) -> Result<Vc<ChunkingConfigs>> {
325 Ok(Vc::cell(self.chunking_configs.iter().cloned().collect()))
326 }
327
328 #[turbo_tasks::function]
329 async fn asset_path(
330 &self,
331 content_hash: RcStr,
332 original_asset_ident: Vc<AssetIdent>,
333 ) -> Result<Vc<FileSystemPath>> {
334 let source_path = original_asset_ident.path().await?;
335 let basename = source_path.file_name();
336 let asset_path = match source_path.extension_ref() {
337 Some(ext) => format!(
338 "{basename}.{content_hash}.{ext}",
339 basename = &basename[..basename.len() - ext.len() - 1],
340 content_hash = &content_hash[..8]
341 ),
342 None => format!(
343 "{basename}.{content_hash}",
344 content_hash = &content_hash[..8]
345 ),
346 };
347 Ok(self.asset_root_path.join(&asset_path)?.cell())
348 }
349
350 #[turbo_tasks::function]
351 async fn chunk_group(
352 self: ResolvedVc<Self>,
353 ident: Vc<AssetIdent>,
354 chunk_group: ChunkGroup,
355 module_graph: Vc<ModuleGraph>,
356 availability_info: AvailabilityInfo,
357 ) -> Result<Vc<ChunkGroupResult>> {
358 let span = tracing::info_span!("chunking", module = ident.to_string().await?.to_string());
359 async move {
360 let modules = chunk_group.entries();
361 let MakeChunkGroupResult {
362 chunks,
363 availability_info,
364 } = make_chunk_group(
365 modules,
366 module_graph,
367 ResolvedVc::upcast(self),
368 availability_info,
369 )
370 .await?;
371
372 let assets = chunks
373 .iter()
374 .map(|chunk| self.generate_chunk(**chunk).to_resolved())
375 .try_join()
376 .await?;
377
378 Ok(ChunkGroupResult {
379 assets: ResolvedVc::cell(assets),
380 availability_info,
381 }
382 .cell())
383 }
384 .instrument(span)
385 .await
386 }
387
388 #[turbo_tasks::function]
389 pub async fn entry_chunk_group(
390 self: ResolvedVc<Self>,
391 path: FileSystemPath,
392 evaluatable_assets: Vc<EvaluatableAssets>,
393 module_graph: Vc<ModuleGraph>,
394 extra_chunks: Vc<OutputAssets>,
395 availability_info: AvailabilityInfo,
396 ) -> Result<Vc<EntryChunkGroupResult>> {
397 let evaluatable_assets_ref = evaluatable_assets.await?;
398 let entries = evaluatable_assets_ref
399 .iter()
400 .map(|&asset| ResolvedVc::upcast::<Box<dyn Module>>(asset));
401
402 let MakeChunkGroupResult {
403 chunks,
404 availability_info,
405 } = make_chunk_group(
406 entries,
407 module_graph,
408 ResolvedVc::upcast(self),
409 availability_info,
410 )
411 .await?;
412
413 let extra_chunks = extra_chunks.await?;
414 let other_chunks: Vec<_> = extra_chunks
415 .iter()
416 .copied()
417 .chain(
418 chunks
419 .iter()
420 .map(|chunk| self.generate_chunk(**chunk).to_resolved())
421 .try_join()
422 .await?,
423 )
424 .collect();
425
426 let Some(module) = ResolvedVc::try_sidecast(*evaluatable_assets_ref.last().unwrap()) else {
427 bail!("module must be placeable in an ecmascript chunk");
428 };
429
430 let asset = ResolvedVc::upcast(
431 EcmascriptBuildNodeEntryChunk::new(
432 path,
433 Vc::cell(other_chunks),
434 evaluatable_assets,
435 *module,
436 module_graph,
437 *self,
438 )
439 .to_resolved()
440 .await?,
441 );
442
443 Ok(EntryChunkGroupResult {
444 asset,
445 availability_info,
446 }
447 .cell())
448 }
449
450 #[turbo_tasks::function]
451 fn evaluated_chunk_group(
452 self: Vc<Self>,
453 _ident: Vc<AssetIdent>,
454 _chunk_group: ChunkGroup,
455 _module_graph: Vc<ModuleGraph>,
456 _availability_info: AvailabilityInfo,
457 ) -> Result<Vc<ChunkGroupResult>> {
458 bail!("the build chunking context does not support evaluated chunk groups")
461 }
462
463 #[turbo_tasks::function]
464 fn chunk_item_id_from_ident(&self, ident: Vc<AssetIdent>) -> Vc<ModuleId> {
465 self.module_id_strategy.get_module_id(ident)
466 }
467
468 #[turbo_tasks::function]
469 async fn async_loader_chunk_item(
470 self: Vc<Self>,
471 module: Vc<Box<dyn ChunkableModule>>,
472 module_graph: Vc<ModuleGraph>,
473 availability_info: AvailabilityInfo,
474 ) -> Result<Vc<Box<dyn ChunkItem>>> {
475 Ok(if self.await?.manifest_chunks {
476 let manifest_asset =
477 ManifestAsyncModule::new(module, module_graph, Vc::upcast(self), availability_info);
478 Vc::upcast(ManifestLoaderChunkItem::new(
479 manifest_asset,
480 module_graph,
481 Vc::upcast(self),
482 ))
483 } else {
484 let module = AsyncLoaderModule::new(module, Vc::upcast(self), availability_info);
485 Vc::upcast(module.as_chunk_item(module_graph, Vc::upcast(self)))
486 })
487 }
488
489 #[turbo_tasks::function]
490 async fn async_loader_chunk_item_id(
491 self: Vc<Self>,
492 module: Vc<Box<dyn ChunkableModule>>,
493 ) -> Result<Vc<ModuleId>> {
494 Ok(if self.await?.manifest_chunks {
495 self.chunk_item_id_from_ident(ManifestLoaderChunkItem::asset_ident_for(module))
496 } else {
497 self.chunk_item_id_from_ident(AsyncLoaderModule::asset_ident_for(module))
498 })
499 }
500}