1use std::io::Write;
2
3use anyhow::{Result, bail};
4use bincode::{Decode, Encode};
5use smallvec::SmallVec;
6use turbo_rcstr::{RcStr, rcstr};
7use turbo_tasks::{
8 NonLocalValue, PrettyPrintError, ResolvedVc, TaskInput, Upcast, ValueToString, Vc,
9 trace::TraceRawVcs,
10};
11use turbo_tasks_fs::{FileSystemPath, rope::Rope};
12use turbopack_core::{
13 chunk::{
14 AsyncModuleInfo, ChunkItem, ChunkItemWithAsyncModuleInfo, ChunkType, ChunkingContext,
15 ChunkingContextExt, ModuleId, SourceMapSourceType,
16 },
17 code_builder::{Code, CodeBuilder},
18 ident::AssetIdent,
19 issue::{IssueExt, IssueSeverity, StyledString, code_gen::CodeGenerationIssue},
20 module::Module,
21 module_graph::ModuleGraph,
22 output::OutputAssetsReference,
23 source_map::utils::{absolute_fileify_source_map, relative_fileify_source_map},
24};
25
26use crate::{
27 EcmascriptModuleContent,
28 chunk::{chunk_type::EcmascriptChunkType, placeable::EcmascriptChunkPlaceable},
29 references::async_module::{AsyncModuleOptions, OptionAsyncModuleOptions},
30 runtime_functions::TURBOPACK_ASYNC_MODULE,
31 utils::StringifyJs,
32};
33
34#[derive(
35 Debug,
36 Clone,
37 PartialEq,
38 Eq,
39 Hash,
40 TraceRawVcs,
41 TaskInput,
42 NonLocalValue,
43 Default,
44 Encode,
45 Decode,
46)]
47pub enum RewriteSourcePath {
48 AbsoluteFilePath(FileSystemPath),
49 RelativeFilePath(FileSystemPath, RcStr),
50 #[default]
51 None,
52}
53
54#[turbo_tasks::value(shared, serialization = "none")]
57#[derive(Default, Clone)]
58pub struct EcmascriptChunkItemContent {
59 pub inner_code: Rope,
60 pub source_map: Option<Rope>,
61 pub additional_ids: SmallVec<[ModuleId; 1]>,
62 pub options: EcmascriptChunkItemOptions,
63 pub rewrite_source_path: RewriteSourcePath,
64 pub placeholder_for_future_extensions: (),
65}
66
67#[turbo_tasks::value_impl]
68impl EcmascriptChunkItemContent {
69 #[turbo_tasks::function]
70 pub async fn new(
71 content: Vc<EcmascriptModuleContent>,
72 chunking_context: Vc<Box<dyn ChunkingContext>>,
73 async_module_options: Vc<OptionAsyncModuleOptions>,
74 ) -> Result<Vc<Self>> {
75 let supports_arrow_functions = *chunking_context
76 .environment()
77 .runtime_versions()
78 .supports_arrow_functions()
79 .await?;
80 let externals = *chunking_context
81 .environment()
82 .supports_commonjs_externals()
83 .await?;
84
85 let content = content.await?;
86 let async_module = async_module_options.owned().await?;
87 let strict = content.strict;
88
89 Ok(EcmascriptChunkItemContent {
90 rewrite_source_path: match *chunking_context.source_map_source_type().await? {
91 SourceMapSourceType::AbsoluteFileUri => {
92 RewriteSourcePath::AbsoluteFilePath(chunking_context.root_path().owned().await?)
93 }
94 SourceMapSourceType::RelativeUri => RewriteSourcePath::RelativeFilePath(
95 chunking_context.root_path().owned().await?,
96 chunking_context
97 .relative_path_from_chunk_root_to_project_root()
98 .owned()
99 .await?,
100 ),
101 SourceMapSourceType::TurbopackUri => RewriteSourcePath::None,
102 },
103 inner_code: content.inner_code.clone(),
104 source_map: content.source_map.clone(),
105 additional_ids: content.additional_ids.clone(),
106 options: if content.is_esm {
107 EcmascriptChunkItemOptions {
108 strict: true,
109 externals,
110 async_module,
111 supports_arrow_functions,
112 ..Default::default()
113 }
114 } else {
115 if async_module.is_some() {
116 bail!("CJS module can't be async.");
117 }
118
119 EcmascriptChunkItemOptions {
120 strict,
121 externals,
122 supports_arrow_functions,
123 module_and_exports: true,
125 ..Default::default()
126 }
127 },
128 ..Default::default()
129 }
130 .cell())
131 }
132}
133
134impl EcmascriptChunkItemContent {
135 async fn module_factory(&self) -> Result<ResolvedVc<Code>> {
136 let mut code = CodeBuilder::default();
137 for additional_id in self.additional_ids.iter() {
138 writeln!(code, "{}, ", StringifyJs(&additional_id))?;
139 }
140
141 if self.options.supports_arrow_functions {
142 code += "((";
143 } else {
144 code += "(function(";
145 }
146 if self.options.module_and_exports {
147 code += "__turbopack_context__, module, exports";
148 } else {
149 code += "__turbopack_context__";
150 }
151 if self.options.supports_arrow_functions {
152 code += ") => {\n";
153 } else {
154 code += "){\n";
155 }
156
157 if self.options.strict {
158 code += "\"use strict\";\n\n";
159 } else {
160 code += "\n";
161 }
162
163 if self.options.async_module.is_some() {
164 write!(code, "return {TURBOPACK_ASYNC_MODULE}")?;
165 if self.options.supports_arrow_functions {
166 code += "(async (";
167 } else {
168 code += "(async function(";
169 }
170 code += "__turbopack_handle_async_dependencies__, __turbopack_async_result__";
171 if self.options.supports_arrow_functions {
172 code += ") => {";
173 } else {
174 code += "){";
175 }
176 code += " try {\n";
177 }
178
179 let source_map = match &self.rewrite_source_path {
180 RewriteSourcePath::AbsoluteFilePath(path) => {
181 absolute_fileify_source_map(self.source_map.as_ref(), path.clone()).await?
182 }
183 RewriteSourcePath::RelativeFilePath(path, relative_path) => {
184 relative_fileify_source_map(
185 self.source_map.as_ref(),
186 path.clone(),
187 relative_path.clone(),
188 )
189 .await?
190 }
191 RewriteSourcePath::None => self.source_map.clone(),
192 };
193
194 code.push_source(&self.inner_code, source_map);
195
196 if let Some(opts) = &self.options.async_module {
197 write!(
198 code,
199 "__turbopack_async_result__();\n}} catch(e) {{ __turbopack_async_result__(e); }} \
200 }}, {});",
201 opts.has_top_level_await
202 )?;
203 }
204
205 code += "})";
206
207 Ok(code.build().resolved_cell())
208 }
209}
210
211#[derive(PartialEq, Eq, Default, Debug, Clone, TraceRawVcs, NonLocalValue, Encode, Decode)]
212pub struct EcmascriptChunkItemOptions {
213 pub strict: bool,
215 pub module_and_exports: bool,
218 pub externals: bool,
221 pub async_module: Option<AsyncModuleOptions>,
224 pub supports_arrow_functions: bool,
226 pub placeholder_for_future_extensions: (),
227}
228
229#[derive(
230 Debug, Clone, PartialEq, Eq, Hash, TraceRawVcs, TaskInput, NonLocalValue, Encode, Decode,
231)]
232pub struct EcmascriptChunkItemWithAsyncInfo {
233 pub chunk_item: ResolvedVc<Box<dyn EcmascriptChunkItem>>,
234 pub async_info: Option<ResolvedVc<AsyncModuleInfo>>,
235}
236
237impl EcmascriptChunkItemWithAsyncInfo {
238 pub fn from_chunk_item(
239 chunk_item: &ChunkItemWithAsyncModuleInfo,
240 ) -> Result<EcmascriptChunkItemWithAsyncInfo> {
241 let ChunkItemWithAsyncModuleInfo {
242 chunk_item,
243 module: _,
244 async_info,
245 } = chunk_item;
246 let Some(chunk_item) =
247 ResolvedVc::try_downcast::<Box<dyn EcmascriptChunkItem>>(*chunk_item)
248 else {
249 bail!("Chunk item is not an ecmascript chunk item but reporting chunk type ecmascript");
250 };
251 Ok(EcmascriptChunkItemWithAsyncInfo {
252 chunk_item,
253 async_info: *async_info,
254 })
255 }
256}
257
258#[turbo_tasks::value_trait]
259pub trait EcmascriptChunkItem: ChunkItem + OutputAssetsReference {
260 #[turbo_tasks::function]
261 fn content(self: Vc<Self>) -> Vc<EcmascriptChunkItemContent>;
262
263 #[turbo_tasks::function]
268 fn content_with_async_module_info(
269 self: Vc<Self>,
270 _async_module_info: Option<Vc<AsyncModuleInfo>>,
271 _estimated: bool,
272 ) -> Vc<EcmascriptChunkItemContent> {
273 self.content()
274 }
275}
276
277pub trait EcmascriptChunkItemExt {
278 fn code(self: Vc<Self>, async_module_info: Option<Vc<AsyncModuleInfo>>) -> Vc<Code>;
280}
281
282impl<T> EcmascriptChunkItemExt for T
283where
284 T: Upcast<Box<dyn EcmascriptChunkItem>>,
285{
286 fn code(self: Vc<Self>, async_module_info: Option<Vc<AsyncModuleInfo>>) -> Vc<Code> {
288 module_factory_with_code_generation_issue(Vc::upcast_non_strict(self), async_module_info)
289 }
290}
291
292#[turbo_tasks::function]
293async fn module_factory_with_code_generation_issue(
294 chunk_item: Vc<Box<dyn EcmascriptChunkItem>>,
295 async_module_info: Option<Vc<AsyncModuleInfo>>,
296) -> Result<Vc<Code>> {
297 let content = match chunk_item
298 .content_with_async_module_info(async_module_info, false)
299 .await
300 {
301 Ok(item) => item.module_factory().await,
302 Err(err) => Err(err),
303 };
304 Ok(match content {
305 Ok(factory) => *factory,
306 Err(error) => {
307 let id = chunk_item.asset_ident().to_string().await;
308 let id = id.as_ref().map_or_else(|_| "unknown", |id| &**id);
309
310 let error = error.context(format!(
312 "An error occurred while generating the chunk item {id}"
313 ));
314 let error_message = format!("{}", PrettyPrintError(&error)).into();
315 let js_error_message = serde_json::to_string(&error_message)?;
316 CodeGenerationIssue {
317 severity: IssueSeverity::Error,
318 path: chunk_item.asset_ident().path().owned().await?,
319 title: StyledString::Text(rcstr!("Code generation for chunk item errored"))
320 .resolved_cell(),
321 message: StyledString::Text(error_message).resolved_cell(),
322 source: None,
323 }
324 .resolved_cell()
325 .emit();
326 let mut code = CodeBuilder::default();
327 code += "(() => {{\n\n";
328 writeln!(code, "throw new Error({error});", error = &js_error_message)?;
329 code += "\n}})";
330 code.build().cell()
331 }
332 })
333}
334
335#[turbo_tasks::value]
338pub struct EcmascriptModuleChunkItem {
339 module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
340 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
341 module_graph: ResolvedVc<ModuleGraph>,
342}
343
344pub fn ecmascript_chunk_item(
347 module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
348 module_graph: ResolvedVc<ModuleGraph>,
349 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
350) -> Vc<Box<dyn ChunkItem>> {
351 Vc::upcast(
352 EcmascriptModuleChunkItem {
353 module,
354 chunking_context,
355 module_graph,
356 }
357 .cell(),
358 )
359}
360
361#[turbo_tasks::value_impl]
362impl ChunkItem for EcmascriptModuleChunkItem {
363 #[turbo_tasks::function]
364 fn asset_ident(&self) -> Vc<AssetIdent> {
365 self.module.ident()
366 }
367
368 #[turbo_tasks::function]
369 fn content_ident(&self) -> Vc<AssetIdent> {
370 self.module
371 .chunk_item_content_ident(*self.chunking_context, *self.module_graph)
372 }
373
374 #[turbo_tasks::function]
375 fn ty(&self) -> Vc<Box<dyn ChunkType>> {
376 Vc::upcast(Vc::<EcmascriptChunkType>::default())
377 }
378
379 #[turbo_tasks::function]
380 fn module(&self) -> Vc<Box<dyn Module>> {
381 Vc::upcast(*self.module)
382 }
383
384 #[turbo_tasks::function]
385 fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>> {
386 *self.chunking_context
387 }
388}
389
390#[turbo_tasks::value_impl]
391impl OutputAssetsReference for EcmascriptModuleChunkItem {
392 #[turbo_tasks::function]
393 fn references(&self) -> Vc<turbopack_core::output::OutputAssetsWithReferenced> {
394 self.module
395 .chunk_item_output_assets(*self.chunking_context, *self.module_graph)
396 }
397}
398
399#[turbo_tasks::value_impl]
400impl EcmascriptChunkItem for EcmascriptModuleChunkItem {
401 #[turbo_tasks::function]
402 fn content(&self) -> Vc<EcmascriptChunkItemContent> {
403 self.module
404 .chunk_item_content(*self.chunking_context, *self.module_graph, None, false)
405 }
406
407 #[turbo_tasks::function]
408 fn content_with_async_module_info(
409 &self,
410 async_module_info: Option<Vc<AsyncModuleInfo>>,
411 estimated: bool,
412 ) -> Vc<EcmascriptChunkItemContent> {
413 self.module.chunk_item_content(
414 *self.chunking_context,
415 *self.module_graph,
416 async_module_info,
417 estimated,
418 )
419 }
420}