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, TryJoinIterExt, Upcast, ValueToString, Vc,
9 trace::TraceRawVcs,
10};
11use turbo_tasks_fs::{FileSystemPath, rope::Rope};
12use turbopack_core::{
13 chunk::{
14 AsyncModuleInfo, ChunkItem, ChunkItemWithAsyncModuleInfo, ChunkingContext,
15 ChunkingContextExt, ModuleId, SourceMapSourceType,
16 },
17 code_builder::{Code, CodeBuilder},
18 error::PrettyPrintError,
19 issue::{IssueExt, IssueSeverity, StyledString, code_gen::CodeGenerationIssue},
20 output::OutputAssetsReference,
21 source_map::utils::{absolute_fileify_source_map, relative_fileify_source_map},
22};
23
24use crate::{
25 EcmascriptModuleContent,
26 references::async_module::{AsyncModuleOptions, OptionAsyncModuleOptions},
27 runtime_functions::TURBOPACK_ASYNC_MODULE,
28 utils::StringifyJs,
29};
30
31#[derive(
32 Debug,
33 Clone,
34 PartialEq,
35 Eq,
36 Hash,
37 TraceRawVcs,
38 TaskInput,
39 NonLocalValue,
40 Default,
41 Encode,
42 Decode,
43)]
44pub enum RewriteSourcePath {
45 AbsoluteFilePath(FileSystemPath),
46 RelativeFilePath(FileSystemPath, RcStr),
47 #[default]
48 None,
49}
50
51#[turbo_tasks::value(shared)]
52#[derive(Default, Clone)]
53pub struct EcmascriptChunkItemContent {
54 pub inner_code: Rope,
55 pub source_map: Option<Rope>,
56 pub additional_ids: SmallVec<[ResolvedVc<ModuleId>; 1]>,
57 pub options: EcmascriptChunkItemOptions,
58 pub rewrite_source_path: RewriteSourcePath,
59 pub placeholder_for_future_extensions: (),
60}
61
62#[turbo_tasks::value_impl]
63impl EcmascriptChunkItemContent {
64 #[turbo_tasks::function]
65 pub async fn new(
66 content: Vc<EcmascriptModuleContent>,
67 chunking_context: Vc<Box<dyn ChunkingContext>>,
68 async_module_options: Vc<OptionAsyncModuleOptions>,
69 ) -> Result<Vc<Self>> {
70 let externals = *chunking_context
71 .environment()
72 .supports_commonjs_externals()
73 .await?;
74
75 let content = content.await?;
76 let async_module = async_module_options.owned().await?;
77 let strict = content.strict;
78
79 Ok(EcmascriptChunkItemContent {
80 rewrite_source_path: match *chunking_context.source_map_source_type().await? {
81 SourceMapSourceType::AbsoluteFileUri => {
82 RewriteSourcePath::AbsoluteFilePath(chunking_context.root_path().owned().await?)
83 }
84 SourceMapSourceType::RelativeUri => RewriteSourcePath::RelativeFilePath(
85 chunking_context.root_path().owned().await?,
86 chunking_context
87 .relative_path_from_chunk_root_to_project_root()
88 .owned()
89 .await?,
90 ),
91 SourceMapSourceType::TurbopackUri => RewriteSourcePath::None,
92 },
93 inner_code: content.inner_code.clone(),
94 source_map: content.source_map.clone(),
95 additional_ids: content.additional_ids.clone(),
96 options: if content.is_esm {
97 EcmascriptChunkItemOptions {
98 strict: true,
99 externals,
100 async_module,
101 ..Default::default()
102 }
103 } else {
104 if async_module.is_some() {
105 bail!("CJS module can't be async.");
106 }
107
108 EcmascriptChunkItemOptions {
109 strict,
110 externals,
111 module_and_exports: true,
113 ..Default::default()
114 }
115 },
116 ..Default::default()
117 }
118 .cell())
119 }
120}
121
122impl EcmascriptChunkItemContent {
123 async fn module_factory(&self) -> Result<ResolvedVc<Code>> {
124 let mut code = CodeBuilder::default();
125 for additional_id in self.additional_ids.iter().try_join().await? {
126 writeln!(code, "{}, ", StringifyJs(&*additional_id))?;
127 }
128 if self.options.module_and_exports {
129 code += "((__turbopack_context__, module, exports) => {\n";
130 } else {
131 code += "((__turbopack_context__) => {\n";
132 }
133 if self.options.strict {
134 code += "\"use strict\";\n\n";
135 } else {
136 code += "\n";
137 }
138
139 if self.options.async_module.is_some() {
140 writeln!(
141 code,
142 "return {TURBOPACK_ASYNC_MODULE}(async (__turbopack_handle_async_dependencies__, \
143 __turbopack_async_result__) => {{ try {{\n"
144 )?;
145 }
146
147 let source_map = match &self.rewrite_source_path {
148 RewriteSourcePath::AbsoluteFilePath(path) => {
149 absolute_fileify_source_map(self.source_map.as_ref(), path.clone()).await?
150 }
151 RewriteSourcePath::RelativeFilePath(path, relative_path) => {
152 relative_fileify_source_map(
153 self.source_map.as_ref(),
154 path.clone(),
155 relative_path.clone(),
156 )
157 .await?
158 }
159 RewriteSourcePath::None => self.source_map.clone(),
160 };
161
162 code.push_source(&self.inner_code, source_map);
163
164 if let Some(opts) = &self.options.async_module {
165 write!(
166 code,
167 "__turbopack_async_result__();\n}} catch(e) {{ __turbopack_async_result__(e); }} \
168 }}, {});",
169 opts.has_top_level_await
170 )?;
171 }
172
173 code += "})";
174
175 Ok(code.build().resolved_cell())
176 }
177}
178
179#[derive(PartialEq, Eq, Default, Debug, Clone, TraceRawVcs, NonLocalValue, Encode, Decode)]
180pub struct EcmascriptChunkItemOptions {
181 pub strict: bool,
183 pub module_and_exports: bool,
186 pub externals: bool,
189 pub async_module: Option<AsyncModuleOptions>,
192 pub placeholder_for_future_extensions: (),
193}
194
195#[derive(
196 Debug, Clone, PartialEq, Eq, Hash, TraceRawVcs, TaskInput, NonLocalValue, Encode, Decode,
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 + OutputAssetsReference {
226 #[turbo_tasks::function]
227 fn content(self: Vc<Self>) -> Vc<EcmascriptChunkItemContent>;
228
229 #[turbo_tasks::function]
234 fn content_with_async_module_info(
235 self: Vc<Self>,
236 _async_module_info: Option<Vc<AsyncModuleInfo>>,
237 _estimated: bool,
238 ) -> Vc<EcmascriptChunkItemContent> {
239 self.content()
240 }
241}
242
243pub trait EcmascriptChunkItemExt {
244 fn code(self: Vc<Self>, async_module_info: Option<Vc<AsyncModuleInfo>>) -> Vc<Code>;
246}
247
248impl<T> EcmascriptChunkItemExt for T
249where
250 T: Upcast<Box<dyn EcmascriptChunkItem>>,
251{
252 fn code(self: Vc<Self>, async_module_info: Option<Vc<AsyncModuleInfo>>) -> Vc<Code> {
254 module_factory_with_code_generation_issue(Vc::upcast_non_strict(self), async_module_info)
255 }
256}
257
258#[turbo_tasks::function]
259async fn module_factory_with_code_generation_issue(
260 chunk_item: Vc<Box<dyn EcmascriptChunkItem>>,
261 async_module_info: Option<Vc<AsyncModuleInfo>>,
262) -> Result<Vc<Code>> {
263 let content = match chunk_item
264 .content_with_async_module_info(async_module_info, false)
265 .await
266 {
267 Ok(item) => item.module_factory().await,
268 Err(err) => Err(err),
269 };
270 Ok(match content {
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().owned().await?,
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}