turbopack_ecmascript/chunk/
item.rs1use std::io::Write;
2
3use anyhow::{Result, bail};
4use serde::{Deserialize, Serialize};
5use smallvec::SmallVec;
6use turbo_rcstr::rcstr;
7use turbo_tasks::{
8 NonLocalValue, ResolvedVc, TaskInput, TryJoinIterExt, Upcast, ValueToString, Vc,
9 trace::TraceRawVcs,
10};
11use turbo_tasks_fs::{FileSystemPath, rope::Rope};
12use turbopack_core::{
13 chunk::{AsyncModuleInfo, ChunkItem, ChunkItemWithAsyncModuleInfo, ChunkingContext, ModuleId},
14 code_builder::{Code, CodeBuilder},
15 error::PrettyPrintError,
16 issue::{IssueExt, IssueSeverity, StyledString, code_gen::CodeGenerationIssue},
17 source_map::utils::fileify_source_map,
18};
19
20use crate::{
21 EcmascriptModuleContent,
22 references::async_module::{AsyncModuleOptions, OptionAsyncModuleOptions},
23 runtime_functions::TURBOPACK_ASYNC_MODULE,
24 utils::StringifyJs,
25};
26
27#[turbo_tasks::value(shared)]
28#[derive(Default, Clone)]
29pub struct EcmascriptChunkItemContent {
30 pub inner_code: Rope,
31 pub source_map: Option<Rope>,
32 pub additional_ids: SmallVec<[ResolvedVc<ModuleId>; 1]>,
33 pub options: EcmascriptChunkItemOptions,
34 pub rewrite_source_path: Option<FileSystemPath>,
35 pub placeholder_for_future_extensions: (),
36}
37
38#[turbo_tasks::value_impl]
39impl EcmascriptChunkItemContent {
40 #[turbo_tasks::function]
41 pub async fn new(
42 content: Vc<EcmascriptModuleContent>,
43 chunking_context: Vc<Box<dyn ChunkingContext>>,
44 async_module_options: Vc<OptionAsyncModuleOptions>,
45 ) -> Result<Vc<Self>> {
46 let externals = *chunking_context
47 .environment()
48 .supports_commonjs_externals()
49 .await?;
50
51 let content = content.await?;
52 let async_module = async_module_options.owned().await?;
53 let strict = content.strict;
54
55 Ok(EcmascriptChunkItemContent {
56 rewrite_source_path: if *chunking_context.should_use_file_source_map_uris().await? {
57 Some(chunking_context.root_path().owned().await?)
58 } else {
59 None
60 },
61 inner_code: content.inner_code.clone(),
62 source_map: content.source_map.clone(),
63 additional_ids: content.additional_ids.clone(),
64 options: if content.is_esm {
65 EcmascriptChunkItemOptions {
66 strict: true,
67 externals,
68 async_module,
69 ..Default::default()
70 }
71 } else {
72 if async_module.is_some() {
73 bail!("CJS module can't be async.");
74 }
75
76 EcmascriptChunkItemOptions {
77 strict,
78 externals,
79 module_and_exports: true,
81 ..Default::default()
82 }
83 },
84 ..Default::default()
85 }
86 .cell())
87 }
88
89 #[turbo_tasks::function]
90 pub async fn module_factory(&self) -> Result<Vc<Code>> {
91 let mut code = CodeBuilder::default();
92 for additional_id in self.additional_ids.iter().try_join().await? {
93 writeln!(code, "{}, ", StringifyJs(&*additional_id))?;
94 }
95 if self.options.module_and_exports {
96 code += "((__turbopack_context__, module, exports) => {\n";
97 } else {
98 code += "((__turbopack_context__) => {\n";
99 }
100 if self.options.strict {
101 code += "\"use strict\";\n\n";
102 } else {
103 code += "\n";
104 }
105
106 if self.options.async_module.is_some() {
107 writeln!(
108 code,
109 "return {TURBOPACK_ASYNC_MODULE}(async (__turbopack_handle_async_dependencies__, \
110 __turbopack_async_result__) => {{ try {{\n"
111 )?;
112 }
113
114 let source_map = if let Some(rewrite_source_path) = &self.rewrite_source_path {
115 fileify_source_map(self.source_map.as_ref(), rewrite_source_path.clone()).await?
116 } else {
117 self.source_map.clone()
118 };
119
120 code.push_source(&self.inner_code, source_map);
121
122 if let Some(opts) = &self.options.async_module {
123 write!(
124 code,
125 "__turbopack_async_result__();\n}} catch(e) {{ __turbopack_async_result__(e); }} \
126 }}, {});",
127 opts.has_top_level_await
128 )?;
129 }
130
131 code += "})";
132
133 Ok(code.build().cell())
134 }
135}
136
137#[derive(
138 PartialEq, Eq, Default, Debug, Clone, Serialize, Deserialize, TraceRawVcs, NonLocalValue,
139)]
140pub struct EcmascriptChunkItemOptions {
141 pub strict: bool,
143 pub module_and_exports: bool,
146 pub externals: bool,
149 pub async_module: Option<AsyncModuleOptions>,
152 pub placeholder_for_future_extensions: (),
153}
154
155#[derive(
156 Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, TraceRawVcs, TaskInput, NonLocalValue,
157)]
158pub struct EcmascriptChunkItemWithAsyncInfo {
159 pub chunk_item: ResolvedVc<Box<dyn EcmascriptChunkItem>>,
160 pub async_info: Option<ResolvedVc<AsyncModuleInfo>>,
161}
162
163impl EcmascriptChunkItemWithAsyncInfo {
164 pub fn from_chunk_item(
165 chunk_item: &ChunkItemWithAsyncModuleInfo,
166 ) -> Result<EcmascriptChunkItemWithAsyncInfo> {
167 let ChunkItemWithAsyncModuleInfo {
168 chunk_item,
169 module: _,
170 async_info,
171 } = chunk_item;
172 let Some(chunk_item) =
173 ResolvedVc::try_downcast::<Box<dyn EcmascriptChunkItem>>(*chunk_item)
174 else {
175 bail!("Chunk item is not an ecmascript chunk item but reporting chunk type ecmascript");
176 };
177 Ok(EcmascriptChunkItemWithAsyncInfo {
178 chunk_item,
179 async_info: *async_info,
180 })
181 }
182}
183
184#[turbo_tasks::value_trait]
185pub trait EcmascriptChunkItem: ChunkItem {
186 #[turbo_tasks::function]
187 fn content(self: Vc<Self>) -> Vc<EcmascriptChunkItemContent>;
188 #[turbo_tasks::function]
189 fn content_with_async_module_info(
190 self: Vc<Self>,
191 _async_module_info: Option<Vc<AsyncModuleInfo>>,
192 ) -> Vc<EcmascriptChunkItemContent> {
193 self.content()
194 }
195
196 #[turbo_tasks::function]
199 fn need_async_module_info(self: Vc<Self>) -> Vc<bool> {
200 Vc::cell(false)
201 }
202}
203
204pub trait EcmascriptChunkItemExt {
205 fn code(self: Vc<Self>, async_module_info: Option<Vc<AsyncModuleInfo>>) -> Vc<Code>;
207}
208
209impl<T> EcmascriptChunkItemExt for T
210where
211 T: Upcast<Box<dyn EcmascriptChunkItem>>,
212{
213 fn code(self: Vc<Self>, async_module_info: Option<Vc<AsyncModuleInfo>>) -> Vc<Code> {
215 module_factory_with_code_generation_issue(Vc::upcast(self), async_module_info)
216 }
217}
218
219#[turbo_tasks::function]
220async fn module_factory_with_code_generation_issue(
221 chunk_item: Vc<Box<dyn EcmascriptChunkItem>>,
222 async_module_info: Option<Vc<AsyncModuleInfo>>,
223) -> Result<Vc<Code>> {
224 Ok(
225 match chunk_item
226 .content_with_async_module_info(async_module_info)
227 .module_factory()
228 .resolve()
229 .await
230 {
231 Ok(factory) => factory,
232 Err(error) => {
233 let id = chunk_item.asset_ident().to_string().await;
234 let id = id.as_ref().map_or_else(|_| "unknown", |id| &**id);
235 let error = error.context(format!(
236 "An error occurred while generating the chunk item {id}"
237 ));
238 let error_message = format!("{}", PrettyPrintError(&error)).into();
239 let js_error_message = serde_json::to_string(&error_message)?;
240 CodeGenerationIssue {
241 severity: IssueSeverity::Error,
242 path: chunk_item.asset_ident().path().owned().await?,
243 title: StyledString::Text(rcstr!("Code generation for chunk item errored"))
244 .resolved_cell(),
245 message: StyledString::Text(error_message).resolved_cell(),
246 }
247 .resolved_cell()
248 .emit();
249 let mut code = CodeBuilder::default();
250 code += "(() => {{\n\n";
251 writeln!(code, "throw new Error({error});", error = &js_error_message)?;
252 code += "\n}})";
253 code.build().cell()
254 }
255 },
256 )
257}