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