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