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