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, EcmascriptOptions,
22 references::async_module::{AsyncModuleOptions, OptionAsyncModuleOptions},
23 utils::{FormatIter, StringifyJs},
24};
25
26#[turbo_tasks::value(shared)]
27#[derive(Default, Clone)]
28pub struct EcmascriptChunkItemContent {
29 pub inner_code: Rope,
30 pub source_map: Option<Rope>,
31 pub additional_ids: SmallVec<[ResolvedVc<ModuleId>; 1]>,
32 pub options: EcmascriptChunkItemOptions,
33 pub rewrite_source_path: Option<FileSystemPath>,
34 pub placeholder_for_future_extensions: (),
35}
36
37#[turbo_tasks::value_impl]
38impl EcmascriptChunkItemContent {
39 #[turbo_tasks::function]
40 pub async fn new(
41 content: Vc<EcmascriptModuleContent>,
42 chunking_context: Vc<Box<dyn ChunkingContext>>,
43 options: Vc<EcmascriptOptions>,
44 async_module_options: Vc<OptionAsyncModuleOptions>,
45 ) -> Result<Vc<Self>> {
46 let refresh = options.await?.refresh;
47 let externals = *chunking_context
48 .environment()
49 .supports_commonjs_externals()
50 .await?;
51
52 let content = content.await?;
53 let async_module = async_module_options.owned().await?;
54 let strict = content.strict;
55
56 Ok(EcmascriptChunkItemContent {
57 rewrite_source_path: if *chunking_context.should_use_file_source_map_uris().await? {
58 Some(chunking_context.root_path().await?.clone_value())
59 } else {
60 None
61 },
62 inner_code: content.inner_code.clone(),
63 source_map: content.source_map.clone(),
64 additional_ids: content.additional_ids.clone(),
65 options: if content.is_esm {
66 EcmascriptChunkItemOptions {
67 strict: true,
68 refresh,
69 externals,
70 async_module,
71 stub_require: true,
72 ..Default::default()
73 }
74 } else {
75 if async_module.is_some() {
76 bail!("CJS module can't be async.");
77 }
78
79 EcmascriptChunkItemOptions {
80 strict,
81 refresh,
82 externals,
83 module: true,
85 exports: true,
86 ..Default::default()
87 }
88 },
89 ..Default::default()
90 }
91 .cell())
92 }
93
94 #[turbo_tasks::function]
95 pub async fn module_factory(&self) -> Result<Vc<Code>> {
96 let mut args = Vec::new();
97 if self.options.async_module.is_some() {
98 args.push("a: __turbopack_async_module__");
99 }
100 if self.options.refresh {
101 args.push("k: __turbopack_refresh__");
102 }
103 if self.options.module || self.options.refresh {
104 args.push("m: module");
105 }
106 if self.options.exports {
107 args.push("e: exports");
108 }
109 if self.options.wasm {
110 args.push("w: __turbopack_wasm__");
111 args.push("u: __turbopack_wasm_module__");
112 }
113
114 let mut code = CodeBuilder::default();
115 let additional_ids = self.additional_ids.iter().try_join().await?;
116 if !additional_ids.is_empty() {
117 code += "["
118 }
119 code += "((__turbopack_context__) => {\n";
120 if self.options.strict {
121 code += "\"use strict\";\n\n";
122 } else {
123 code += "\n";
124 }
125 if !args.is_empty() {
126 let args = FormatIter(|| args.iter().copied().intersperse(", "));
127 writeln!(code, "var {{ {args} }} = __turbopack_context__;")?;
128 }
129
130 if self.options.async_module.is_some() {
131 code += "__turbopack_async_module__(async (__turbopack_handle_async_dependencies__, \
132 __turbopack_async_result__) => { try {\n";
133 } else if !args.is_empty() {
134 code += "{\n";
135 }
136
137 let source_map = if let Some(rewrite_source_path) = &self.rewrite_source_path {
138 fileify_source_map(self.source_map.as_ref(), rewrite_source_path.clone()).await?
139 } else {
140 self.source_map.clone()
141 };
142
143 code.push_source(&self.inner_code, source_map);
144
145 if let Some(opts) = &self.options.async_module {
146 write!(
147 code,
148 "__turbopack_async_result__();\n}} catch(e) {{ __turbopack_async_result__(e); }} \
149 }}, {});",
150 opts.has_top_level_await
151 )?;
152 } else if !args.is_empty() {
153 code += "}";
154 }
155
156 code += "})";
157 if !additional_ids.is_empty() {
158 writeln!(code, ", {}]", StringifyJs(&additional_ids))?;
159 }
160
161 Ok(code.build().cell())
162 }
163}
164
165#[derive(
166 PartialEq, Eq, Default, Debug, Clone, Serialize, Deserialize, TraceRawVcs, NonLocalValue,
167)]
168pub struct EcmascriptChunkItemOptions {
169 pub strict: bool,
171 pub refresh: bool,
174 pub module: bool,
177 pub exports: bool,
180 pub stub_require: bool,
183 pub externals: bool,
186 pub async_module: Option<AsyncModuleOptions>,
189 pub wasm: bool,
192 pub placeholder_for_future_extensions: (),
193}
194
195#[derive(
196 Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, TraceRawVcs, TaskInput, NonLocalValue,
197)]
198pub struct EcmascriptChunkItemWithAsyncInfo {
199 pub chunk_item: ResolvedVc<Box<dyn EcmascriptChunkItem>>,
200 pub async_info: Option<ResolvedVc<AsyncModuleInfo>>,
201}
202
203impl EcmascriptChunkItemWithAsyncInfo {
204 pub fn from_chunk_item(
205 chunk_item: &ChunkItemWithAsyncModuleInfo,
206 ) -> Result<EcmascriptChunkItemWithAsyncInfo> {
207 let ChunkItemWithAsyncModuleInfo {
208 chunk_item,
209 module: _,
210 async_info,
211 } = chunk_item;
212 let Some(chunk_item) =
213 ResolvedVc::try_downcast::<Box<dyn EcmascriptChunkItem>>(*chunk_item)
214 else {
215 bail!("Chunk item is not an ecmascript chunk item but reporting chunk type ecmascript");
216 };
217 Ok(EcmascriptChunkItemWithAsyncInfo {
218 chunk_item,
219 async_info: *async_info,
220 })
221 }
222}
223
224#[turbo_tasks::value_trait]
225pub trait EcmascriptChunkItem: ChunkItem {
226 #[turbo_tasks::function]
227 fn content(self: Vc<Self>) -> Vc<EcmascriptChunkItemContent>;
228 #[turbo_tasks::function]
229 fn content_with_async_module_info(
230 self: Vc<Self>,
231 _async_module_info: Option<Vc<AsyncModuleInfo>>,
232 ) -> Vc<EcmascriptChunkItemContent> {
233 self.content()
234 }
235
236 #[turbo_tasks::function]
239 fn need_async_module_info(self: Vc<Self>) -> Vc<bool> {
240 Vc::cell(false)
241 }
242}
243
244pub trait EcmascriptChunkItemExt {
245 fn code(self: Vc<Self>, async_module_info: Option<Vc<AsyncModuleInfo>>) -> Vc<Code>;
247}
248
249impl<T> EcmascriptChunkItemExt for T
250where
251 T: Upcast<Box<dyn EcmascriptChunkItem>>,
252{
253 fn code(self: Vc<Self>, async_module_info: Option<Vc<AsyncModuleInfo>>) -> Vc<Code> {
255 module_factory_with_code_generation_issue(Vc::upcast(self), async_module_info)
256 }
257}
258
259#[turbo_tasks::function]
260async fn module_factory_with_code_generation_issue(
261 chunk_item: Vc<Box<dyn EcmascriptChunkItem>>,
262 async_module_info: Option<Vc<AsyncModuleInfo>>,
263) -> Result<Vc<Code>> {
264 Ok(
265 match chunk_item
266 .content_with_async_module_info(async_module_info)
267 .module_factory()
268 .resolve()
269 .await
270 {
271 Ok(factory) => factory,
272 Err(error) => {
273 let id = chunk_item.asset_ident().to_string().await;
274 let id = id.as_ref().map_or_else(|_| "unknown", |id| &**id);
275 let error = error.context(format!(
276 "An error occurred while generating the chunk item {id}"
277 ));
278 let error_message = format!("{}", PrettyPrintError(&error)).into();
279 let js_error_message = serde_json::to_string(&error_message)?;
280 CodeGenerationIssue {
281 severity: IssueSeverity::Error,
282 path: chunk_item.asset_ident().path().await?.clone_value(),
283 title: StyledString::Text(rcstr!("Code generation for chunk item errored"))
284 .resolved_cell(),
285 message: StyledString::Text(error_message).resolved_cell(),
286 }
287 .resolved_cell()
288 .emit();
289 let mut code = CodeBuilder::default();
290 code += "(() => {{\n\n";
291 writeln!(code, "throw new Error({error});", error = &js_error_message)?;
292 code += "\n}})";
293 code.build().cell()
294 }
295 },
296 )
297}