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