turbopack_ecmascript/chunk/
item.rs

1use 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// Note we don't want to persist this as `module_factory_with_code_generation_issue` is already
51// persisted and we want to avoid duplicating it.
52#[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                    // These things are not available in ESM
113                    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    /// Whether this chunk item should be in "use strict" mode.
183    pub strict: bool,
184    /// Whether this chunk item's module factory should include a `module` and
185    /// `exports` argument.
186    pub module_and_exports: bool,
187    /// Whether this chunk item's module factory should include a
188    /// `__turbopack_external_require__` argument.
189    pub externals: bool,
190    /// Whether this chunk item's module is async (either has a top level await
191    /// or is importing async modules).
192    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    /// Fetches the content of the chunk item with async module info.
231    /// When `estimated` is true, it's ok to provide an estimated content, since it's only used for
232    /// compute the chunking. When `estimated` is true, this function should not invoke other
233    /// chunking operations that would cause cycles.
234    #[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    /// Generates the module factory for this chunk item.
246    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    /// Generates the module factory for this chunk item.
254    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}