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