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