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
90impl EcmascriptChunkItemContent {
91 async fn module_factory(&self) -> Result<ResolvedVc<Code>> {
92 let mut code = CodeBuilder::default();
93 for additional_id in self.additional_ids.iter().try_join().await? {
94 writeln!(code, "{}, ", StringifyJs(&*additional_id))?;
95 }
96 if self.options.module_and_exports {
97 code += "((__turbopack_context__, module, exports) => {\n";
98 } else {
99 code += "((__turbopack_context__) => {\n";
100 }
101 if self.options.strict {
102 code += "\"use strict\";\n\n";
103 } else {
104 code += "\n";
105 }
106
107 if self.options.async_module.is_some() {
108 writeln!(
109 code,
110 "return {TURBOPACK_ASYNC_MODULE}(async (__turbopack_handle_async_dependencies__, \
111 __turbopack_async_result__) => {{ try {{\n"
112 )?;
113 }
114
115 let source_map = if let Some(rewrite_source_path) = &self.rewrite_source_path {
116 fileify_source_map(self.source_map.as_ref(), rewrite_source_path.clone()).await?
117 } else {
118 self.source_map.clone()
119 };
120
121 code.push_source(&self.inner_code, source_map);
122
123 if let Some(opts) = &self.options.async_module {
124 write!(
125 code,
126 "__turbopack_async_result__();\n}} catch(e) {{ __turbopack_async_result__(e); }} \
127 }}, {});",
128 opts.has_top_level_await
129 )?;
130 }
131
132 code += "})";
133
134 Ok(code.build().resolved_cell())
135 }
136}
137
138#[derive(
139 PartialEq, Eq, Default, Debug, Clone, Serialize, Deserialize, TraceRawVcs, NonLocalValue,
140)]
141pub struct EcmascriptChunkItemOptions {
142 pub strict: bool,
144 pub module_and_exports: bool,
147 pub externals: bool,
150 pub async_module: Option<AsyncModuleOptions>,
153 pub placeholder_for_future_extensions: (),
154}
155
156#[derive(
157 Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, TraceRawVcs, TaskInput, NonLocalValue,
158)]
159pub struct EcmascriptChunkItemWithAsyncInfo {
160 pub chunk_item: ResolvedVc<Box<dyn EcmascriptChunkItem>>,
161 pub async_info: Option<ResolvedVc<AsyncModuleInfo>>,
162}
163
164impl EcmascriptChunkItemWithAsyncInfo {
165 pub fn from_chunk_item(
166 chunk_item: &ChunkItemWithAsyncModuleInfo,
167 ) -> Result<EcmascriptChunkItemWithAsyncInfo> {
168 let ChunkItemWithAsyncModuleInfo {
169 chunk_item,
170 module: _,
171 async_info,
172 } = chunk_item;
173 let Some(chunk_item) =
174 ResolvedVc::try_downcast::<Box<dyn EcmascriptChunkItem>>(*chunk_item)
175 else {
176 bail!("Chunk item is not an ecmascript chunk item but reporting chunk type ecmascript");
177 };
178 Ok(EcmascriptChunkItemWithAsyncInfo {
179 chunk_item,
180 async_info: *async_info,
181 })
182 }
183}
184
185#[turbo_tasks::value_trait]
186pub trait EcmascriptChunkItem: ChunkItem {
187 #[turbo_tasks::function]
188 fn content(self: Vc<Self>) -> Vc<EcmascriptChunkItemContent>;
189 #[turbo_tasks::function]
190 fn content_with_async_module_info(
191 self: Vc<Self>,
192 _async_module_info: Option<Vc<AsyncModuleInfo>>,
193 ) -> Vc<EcmascriptChunkItemContent> {
194 self.content()
195 }
196
197 #[turbo_tasks::function]
200 fn need_async_module_info(self: Vc<Self>) -> Vc<bool> {
201 Vc::cell(false)
202 }
203}
204
205pub trait EcmascriptChunkItemExt {
206 fn code(self: Vc<Self>, async_module_info: Option<Vc<AsyncModuleInfo>>) -> Vc<Code>;
208}
209
210impl<T> EcmascriptChunkItemExt for T
211where
212 T: Upcast<Box<dyn EcmascriptChunkItem>>,
213{
214 fn code(self: Vc<Self>, async_module_info: Option<Vc<AsyncModuleInfo>>) -> Vc<Code> {
216 module_factory_with_code_generation_issue(Vc::upcast_non_strict(self), async_module_info)
217 }
218}
219
220#[turbo_tasks::function]
221async fn module_factory_with_code_generation_issue(
222 chunk_item: Vc<Box<dyn EcmascriptChunkItem>>,
223 async_module_info: Option<Vc<AsyncModuleInfo>>,
224) -> Result<Vc<Code>> {
225 let content = match chunk_item
226 .content_with_async_module_info(async_module_info)
227 .await
228 {
229 Ok(item) => item.module_factory().await,
230 Err(err) => Err(err),
231 };
232 Ok(match content {
233 Ok(factory) => *factory,
234 Err(error) => {
235 let id = chunk_item.asset_ident().to_string().await;
236 let id = id.as_ref().map_or_else(|_| "unknown", |id| &**id);
237 let error = error.context(format!(
238 "An error occurred while generating the chunk item {id}"
239 ));
240 let error_message = format!("{}", PrettyPrintError(&error)).into();
241 let js_error_message = serde_json::to_string(&error_message)?;
242 CodeGenerationIssue {
243 severity: IssueSeverity::Error,
244 path: chunk_item.asset_ident().path().owned().await?,
245 title: StyledString::Text(rcstr!("Code generation for chunk item errored"))
246 .resolved_cell(),
247 message: StyledString::Text(error_message).resolved_cell(),
248 }
249 .resolved_cell()
250 .emit();
251 let mut code = CodeBuilder::default();
252 code += "(() => {{\n\n";
253 writeln!(code, "throw new Error({error});", error = &js_error_message)?;
254 code += "\n}})";
255 code.build().cell()
256 }
257 })
258}