turbopack_ecmascript/chunk/
item.rs

1use 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                    // These things are not available in ESM
80                    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    /// Whether this chunk item should be in "use strict" mode.
143    pub strict: bool,
144    /// Whether this chunk item's module factory should include a `module` and
145    /// `exports` argument.
146    pub module_and_exports: bool,
147    /// Whether this chunk item's module factory should include a
148    /// `__turbopack_external_require__` argument.
149    pub externals: bool,
150    /// Whether this chunk item's module is async (either has a top level await
151    /// or is importing async modules).
152    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    /// Specifies which availability information the chunk item needs for code
198    /// generation
199    #[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    /// Generates the module factory for this chunk item.
207    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    /// Generates the module factory for this chunk item.
215    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}