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 externals = *chunking_context
76 .environment()
77 .supports_commonjs_externals()
78 .await?;
79
80 let content = content.await?;
81 let async_module = async_module_options.owned().await?;
82 let strict = content.strict;
83
84 Ok(EcmascriptChunkItemContent {
85 rewrite_source_path: match *chunking_context.source_map_source_type().await? {
86 SourceMapSourceType::AbsoluteFileUri => {
87 RewriteSourcePath::AbsoluteFilePath(chunking_context.root_path().owned().await?)
88 }
89 SourceMapSourceType::RelativeUri => RewriteSourcePath::RelativeFilePath(
90 chunking_context.root_path().owned().await?,
91 chunking_context
92 .relative_path_from_chunk_root_to_project_root()
93 .owned()
94 .await?,
95 ),
96 SourceMapSourceType::TurbopackUri => RewriteSourcePath::None,
97 },
98 inner_code: content.inner_code.clone(),
99 source_map: content.source_map.clone(),
100 additional_ids: content.additional_ids.clone(),
101 options: if content.is_esm {
102 EcmascriptChunkItemOptions {
103 strict: true,
104 externals,
105 async_module,
106 ..Default::default()
107 }
108 } else {
109 if async_module.is_some() {
110 bail!("CJS module can't be async.");
111 }
112
113 EcmascriptChunkItemOptions {
114 strict,
115 externals,
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<Code>> {
129 let mut code = CodeBuilder::default();
130 for additional_id in self.additional_ids.iter() {
131 writeln!(code, "{}, ", StringifyJs(&additional_id))?;
132 }
133 if self.options.module_and_exports {
134 code += "((__turbopack_context__, module, exports) => {\n";
135 } else {
136 code += "((__turbopack_context__) => {\n";
137 }
138 if self.options.strict {
139 code += "\"use strict\";\n\n";
140 } else {
141 code += "\n";
142 }
143
144 if self.options.async_module.is_some() {
145 writeln!(
146 code,
147 "return {TURBOPACK_ASYNC_MODULE}(async (__turbopack_handle_async_dependencies__, \
148 __turbopack_async_result__) => {{ try {{\n"
149 )?;
150 }
151
152 let source_map = match &self.rewrite_source_path {
153 RewriteSourcePath::AbsoluteFilePath(path) => {
154 absolute_fileify_source_map(self.source_map.as_ref(), path.clone()).await?
155 }
156 RewriteSourcePath::RelativeFilePath(path, relative_path) => {
157 relative_fileify_source_map(
158 self.source_map.as_ref(),
159 path.clone(),
160 relative_path.clone(),
161 )
162 .await?
163 }
164 RewriteSourcePath::None => self.source_map.clone(),
165 };
166
167 code.push_source(&self.inner_code, source_map);
168
169 if let Some(opts) = &self.options.async_module {
170 write!(
171 code,
172 "__turbopack_async_result__();\n}} catch(e) {{ __turbopack_async_result__(e); }} \
173 }}, {});",
174 opts.has_top_level_await
175 )?;
176 }
177
178 code += "})";
179
180 Ok(code.build().resolved_cell())
181 }
182}
183
184#[derive(PartialEq, Eq, Default, Debug, Clone, TraceRawVcs, NonLocalValue, Encode, Decode)]
185pub struct EcmascriptChunkItemOptions {
186 pub strict: bool,
188 pub module_and_exports: bool,
191 pub externals: bool,
194 pub async_module: Option<AsyncModuleOptions>,
197 pub placeholder_for_future_extensions: (),
198}
199
200#[derive(
201 Debug, Clone, PartialEq, Eq, Hash, TraceRawVcs, TaskInput, NonLocalValue, Encode, Decode,
202)]
203pub struct EcmascriptChunkItemWithAsyncInfo {
204 pub chunk_item: ResolvedVc<Box<dyn EcmascriptChunkItem>>,
205 pub async_info: Option<ResolvedVc<AsyncModuleInfo>>,
206}
207
208impl EcmascriptChunkItemWithAsyncInfo {
209 pub fn from_chunk_item(
210 chunk_item: &ChunkItemWithAsyncModuleInfo,
211 ) -> Result<EcmascriptChunkItemWithAsyncInfo> {
212 let ChunkItemWithAsyncModuleInfo {
213 chunk_item,
214 module: _,
215 async_info,
216 } = chunk_item;
217 let Some(chunk_item) =
218 ResolvedVc::try_downcast::<Box<dyn EcmascriptChunkItem>>(*chunk_item)
219 else {
220 bail!("Chunk item is not an ecmascript chunk item but reporting chunk type ecmascript");
221 };
222 Ok(EcmascriptChunkItemWithAsyncInfo {
223 chunk_item,
224 async_info: *async_info,
225 })
226 }
227}
228
229#[turbo_tasks::value_trait]
230pub trait EcmascriptChunkItem: ChunkItem + OutputAssetsReference {
231 #[turbo_tasks::function]
232 fn content(self: Vc<Self>) -> Vc<EcmascriptChunkItemContent>;
233
234 #[turbo_tasks::function]
239 fn content_with_async_module_info(
240 self: Vc<Self>,
241 _async_module_info: Option<Vc<AsyncModuleInfo>>,
242 _estimated: bool,
243 ) -> Vc<EcmascriptChunkItemContent> {
244 self.content()
245 }
246}
247
248pub trait EcmascriptChunkItemExt {
249 fn code(self: Vc<Self>, async_module_info: Option<Vc<AsyncModuleInfo>>) -> Vc<Code>;
251}
252
253impl<T> EcmascriptChunkItemExt for T
254where
255 T: Upcast<Box<dyn EcmascriptChunkItem>>,
256{
257 fn code(self: Vc<Self>, async_module_info: Option<Vc<AsyncModuleInfo>>) -> Vc<Code> {
259 module_factory_with_code_generation_issue(Vc::upcast_non_strict(self), async_module_info)
260 }
261}
262
263#[turbo_tasks::function]
264async fn module_factory_with_code_generation_issue(
265 chunk_item: Vc<Box<dyn EcmascriptChunkItem>>,
266 async_module_info: Option<Vc<AsyncModuleInfo>>,
267) -> Result<Vc<Code>> {
268 let content = match chunk_item
269 .content_with_async_module_info(async_module_info, false)
270 .await
271 {
272 Ok(item) => item.module_factory().await,
273 Err(err) => Err(err),
274 };
275 Ok(match content {
276 Ok(factory) => *factory,
277 Err(error) => {
278 let id = chunk_item.asset_ident().to_string().await;
279 let id = id.as_ref().map_or_else(|_| "unknown", |id| &**id);
280
281 let error = error.context(format!(
283 "An error occurred while generating the chunk item {id}"
284 ));
285 let error_message = format!("{}", PrettyPrintError(&error)).into();
286 let js_error_message = serde_json::to_string(&error_message)?;
287 CodeGenerationIssue {
288 severity: IssueSeverity::Error,
289 path: chunk_item.asset_ident().path().owned().await?,
290 title: StyledString::Text(rcstr!("Code generation for chunk item errored"))
291 .resolved_cell(),
292 message: StyledString::Text(error_message).resolved_cell(),
293 source: None,
294 }
295 .resolved_cell()
296 .emit();
297 let mut code = CodeBuilder::default();
298 code += "(() => {{\n\n";
299 writeln!(code, "throw new Error({error});", error = &js_error_message)?;
300 code += "\n}})";
301 code.build().cell()
302 }
303 })
304}
305
306#[turbo_tasks::value]
309pub struct EcmascriptModuleChunkItem {
310 module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
311 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
312 module_graph: ResolvedVc<ModuleGraph>,
313}
314
315pub fn ecmascript_chunk_item(
318 module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
319 module_graph: ResolvedVc<ModuleGraph>,
320 chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
321) -> Vc<Box<dyn ChunkItem>> {
322 Vc::upcast(
323 EcmascriptModuleChunkItem {
324 module,
325 chunking_context,
326 module_graph,
327 }
328 .cell(),
329 )
330}
331
332#[turbo_tasks::value_impl]
333impl ChunkItem for EcmascriptModuleChunkItem {
334 #[turbo_tasks::function]
335 fn asset_ident(&self) -> Vc<AssetIdent> {
336 self.module.ident()
337 }
338
339 #[turbo_tasks::function]
340 fn content_ident(&self) -> Vc<AssetIdent> {
341 self.module
342 .chunk_item_content_ident(*self.chunking_context, *self.module_graph)
343 }
344
345 #[turbo_tasks::function]
346 fn ty(&self) -> Vc<Box<dyn ChunkType>> {
347 Vc::upcast(Vc::<EcmascriptChunkType>::default())
348 }
349
350 #[turbo_tasks::function]
351 fn module(&self) -> Vc<Box<dyn Module>> {
352 Vc::upcast(*self.module)
353 }
354
355 #[turbo_tasks::function]
356 fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>> {
357 *self.chunking_context
358 }
359}
360
361#[turbo_tasks::value_impl]
362impl OutputAssetsReference for EcmascriptModuleChunkItem {
363 #[turbo_tasks::function]
364 fn references(&self) -> Vc<turbopack_core::output::OutputAssetsWithReferenced> {
365 self.module
366 .chunk_item_output_assets(*self.chunking_context, *self.module_graph)
367 }
368}
369
370#[turbo_tasks::value_impl]
371impl EcmascriptChunkItem for EcmascriptModuleChunkItem {
372 #[turbo_tasks::function]
373 fn content(&self) -> Vc<EcmascriptChunkItemContent> {
374 self.module
375 .chunk_item_content(*self.chunking_context, *self.module_graph, None, false)
376 }
377
378 #[turbo_tasks::function]
379 fn content_with_async_module_info(
380 &self,
381 async_module_info: Option<Vc<AsyncModuleInfo>>,
382 estimated: bool,
383 ) -> Vc<EcmascriptChunkItemContent> {
384 self.module.chunk_item_content(
385 *self.chunking_context,
386 *self.module_graph,
387 async_module_info,
388 estimated,
389 )
390 }
391}