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