turbopack_ecmascript/chunk/
item.rs1use 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, ResolvedVc, TaskInput, Upcast, ValueToString, Vc, trace::TraceRawVcs,
9};
10use turbo_tasks_fs::{FileSystemPath, rope::Rope};
11use turbopack_core::{
12 chunk::{
13 AsyncModuleInfo, ChunkItem, ChunkItemWithAsyncModuleInfo, ChunkingContext,
14 ChunkingContextExt, ModuleId, SourceMapSourceType,
15 },
16 code_builder::{Code, CodeBuilder},
17 error::PrettyPrintError,
18 issue::{IssueExt, IssueSeverity, StyledString, code_gen::CodeGenerationIssue},
19 output::OutputAssetsReference,
20 source_map::utils::{absolute_fileify_source_map, relative_fileify_source_map},
21};
22
23use crate::{
24 EcmascriptModuleContent,
25 references::async_module::{AsyncModuleOptions, OptionAsyncModuleOptions},
26 runtime_functions::TURBOPACK_ASYNC_MODULE,
27 utils::StringifyJs,
28};
29
30#[derive(
31 Debug,
32 Clone,
33 PartialEq,
34 Eq,
35 Hash,
36 TraceRawVcs,
37 TaskInput,
38 NonLocalValue,
39 Default,
40 Encode,
41 Decode,
42)]
43pub enum RewriteSourcePath {
44 AbsoluteFilePath(FileSystemPath),
45 RelativeFilePath(FileSystemPath, RcStr),
46 #[default]
47 None,
48}
49
50#[turbo_tasks::value(shared, serialization = "none")]
53#[derive(Default, Clone)]
54pub struct EcmascriptChunkItemContent {
55 pub inner_code: Rope,
56 pub source_map: Option<Rope>,
57 pub additional_ids: SmallVec<[ModuleId; 1]>,
58 pub options: EcmascriptChunkItemOptions,
59 pub rewrite_source_path: RewriteSourcePath,
60 pub placeholder_for_future_extensions: (),
61}
62
63#[turbo_tasks::value_impl]
64impl EcmascriptChunkItemContent {
65 #[turbo_tasks::function]
66 pub async fn new(
67 content: Vc<EcmascriptModuleContent>,
68 chunking_context: Vc<Box<dyn ChunkingContext>>,
69 async_module_options: Vc<OptionAsyncModuleOptions>,
70 ) -> Result<Vc<Self>> {
71 let externals = *chunking_context
72 .environment()
73 .supports_commonjs_externals()
74 .await?;
75
76 let content = content.await?;
77 let async_module = async_module_options.owned().await?;
78 let strict = content.strict;
79
80 Ok(EcmascriptChunkItemContent {
81 rewrite_source_path: match *chunking_context.source_map_source_type().await? {
82 SourceMapSourceType::AbsoluteFileUri => {
83 RewriteSourcePath::AbsoluteFilePath(chunking_context.root_path().owned().await?)
84 }
85 SourceMapSourceType::RelativeUri => RewriteSourcePath::RelativeFilePath(
86 chunking_context.root_path().owned().await?,
87 chunking_context
88 .relative_path_from_chunk_root_to_project_root()
89 .owned()
90 .await?,
91 ),
92 SourceMapSourceType::TurbopackUri => RewriteSourcePath::None,
93 },
94 inner_code: content.inner_code.clone(),
95 source_map: content.source_map.clone(),
96 additional_ids: content.additional_ids.clone(),
97 options: if content.is_esm {
98 EcmascriptChunkItemOptions {
99 strict: true,
100 externals,
101 async_module,
102 ..Default::default()
103 }
104 } else {
105 if async_module.is_some() {
106 bail!("CJS module can't be async.");
107 }
108
109 EcmascriptChunkItemOptions {
110 strict,
111 externals,
112 module_and_exports: true,
114 ..Default::default()
115 }
116 },
117 ..Default::default()
118 }
119 .cell())
120 }
121}
122
123impl EcmascriptChunkItemContent {
124 async fn module_factory(&self) -> Result<ResolvedVc<Code>> {
125 let mut code = CodeBuilder::default();
126 for additional_id in self.additional_ids.iter() {
127 writeln!(code, "{}, ", StringifyJs(&additional_id))?;
128 }
129 if self.options.module_and_exports {
130 code += "((__turbopack_context__, module, exports) => {\n";
131 } else {
132 code += "((__turbopack_context__) => {\n";
133 }
134 if self.options.strict {
135 code += "\"use strict\";\n\n";
136 } else {
137 code += "\n";
138 }
139
140 if self.options.async_module.is_some() {
141 writeln!(
142 code,
143 "return {TURBOPACK_ASYNC_MODULE}(async (__turbopack_handle_async_dependencies__, \
144 __turbopack_async_result__) => {{ try {{\n"
145 )?;
146 }
147
148 let source_map = match &self.rewrite_source_path {
149 RewriteSourcePath::AbsoluteFilePath(path) => {
150 absolute_fileify_source_map(self.source_map.as_ref(), path.clone()).await?
151 }
152 RewriteSourcePath::RelativeFilePath(path, relative_path) => {
153 relative_fileify_source_map(
154 self.source_map.as_ref(),
155 path.clone(),
156 relative_path.clone(),
157 )
158 .await?
159 }
160 RewriteSourcePath::None => self.source_map.clone(),
161 };
162
163 code.push_source(&self.inner_code, source_map);
164
165 if let Some(opts) = &self.options.async_module {
166 write!(
167 code,
168 "__turbopack_async_result__();\n}} catch(e) {{ __turbopack_async_result__(e); }} \
169 }}, {});",
170 opts.has_top_level_await
171 )?;
172 }
173
174 code += "})";
175
176 Ok(code.build().resolved_cell())
177 }
178}
179
180#[derive(PartialEq, Eq, Default, Debug, Clone, TraceRawVcs, NonLocalValue, Encode, Decode)]
181pub struct EcmascriptChunkItemOptions {
182 pub strict: bool,
184 pub module_and_exports: bool,
187 pub externals: bool,
190 pub async_module: Option<AsyncModuleOptions>,
193 pub placeholder_for_future_extensions: (),
194}
195
196#[derive(
197 Debug, Clone, PartialEq, Eq, Hash, TraceRawVcs, TaskInput, NonLocalValue, Encode, Decode,
198)]
199pub struct EcmascriptChunkItemWithAsyncInfo {
200 pub chunk_item: ResolvedVc<Box<dyn EcmascriptChunkItem>>,
201 pub async_info: Option<ResolvedVc<AsyncModuleInfo>>,
202}
203
204impl EcmascriptChunkItemWithAsyncInfo {
205 pub fn from_chunk_item(
206 chunk_item: &ChunkItemWithAsyncModuleInfo,
207 ) -> Result<EcmascriptChunkItemWithAsyncInfo> {
208 let ChunkItemWithAsyncModuleInfo {
209 chunk_item,
210 module: _,
211 async_info,
212 } = chunk_item;
213 let Some(chunk_item) =
214 ResolvedVc::try_downcast::<Box<dyn EcmascriptChunkItem>>(*chunk_item)
215 else {
216 bail!("Chunk item is not an ecmascript chunk item but reporting chunk type ecmascript");
217 };
218 Ok(EcmascriptChunkItemWithAsyncInfo {
219 chunk_item,
220 async_info: *async_info,
221 })
222 }
223}
224
225#[turbo_tasks::value_trait]
226pub trait EcmascriptChunkItem: ChunkItem + OutputAssetsReference {
227 #[turbo_tasks::function]
228 fn content(self: Vc<Self>) -> Vc<EcmascriptChunkItemContent>;
229
230 #[turbo_tasks::function]
235 fn content_with_async_module_info(
236 self: Vc<Self>,
237 _async_module_info: Option<Vc<AsyncModuleInfo>>,
238 _estimated: bool,
239 ) -> Vc<EcmascriptChunkItemContent> {
240 self.content()
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_non_strict(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 let content = match chunk_item
265 .content_with_async_module_info(async_module_info, false)
266 .await
267 {
268 Ok(item) => item.module_factory().await,
269 Err(err) => Err(err),
270 };
271 Ok(match content {
272 Ok(factory) => *factory,
273 Err(error) => {
274 let id = chunk_item.asset_ident().to_string().await;
275 let id = id.as_ref().map_or_else(|_| "unknown", |id| &**id);
276 let error = error.context(format!(
277 "An error occurred while generating the chunk item {id}"
278 ));
279 let error_message = format!("{}", PrettyPrintError(&error)).into();
280 let js_error_message = serde_json::to_string(&error_message)?;
281 CodeGenerationIssue {
282 severity: IssueSeverity::Error,
283 path: chunk_item.asset_ident().path().owned().await?,
284 title: StyledString::Text(rcstr!("Code generation for chunk item errored"))
285 .resolved_cell(),
286 message: StyledString::Text(error_message).resolved_cell(),
287 }
288 .resolved_cell()
289 .emit();
290 let mut code = CodeBuilder::default();
291 code += "(() => {{\n\n";
292 writeln!(code, "throw new Error({error});", error = &js_error_message)?;
293 code += "\n}})";
294 code.build().cell()
295 }
296 })
297}