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, EcmascriptOptions,
22    references::async_module::{AsyncModuleOptions, OptionAsyncModuleOptions},
23    utils::{FormatIter, StringifyJs},
24};
25
26#[turbo_tasks::value(shared)]
27#[derive(Default, Clone)]
28pub struct EcmascriptChunkItemContent {
29    pub inner_code: Rope,
30    pub source_map: Option<Rope>,
31    pub additional_ids: SmallVec<[ResolvedVc<ModuleId>; 1]>,
32    pub options: EcmascriptChunkItemOptions,
33    pub rewrite_source_path: Option<FileSystemPath>,
34    pub placeholder_for_future_extensions: (),
35}
36
37#[turbo_tasks::value_impl]
38impl EcmascriptChunkItemContent {
39    #[turbo_tasks::function]
40    pub async fn new(
41        content: Vc<EcmascriptModuleContent>,
42        chunking_context: Vc<Box<dyn ChunkingContext>>,
43        options: Vc<EcmascriptOptions>,
44        async_module_options: Vc<OptionAsyncModuleOptions>,
45    ) -> Result<Vc<Self>> {
46        let refresh = options.await?.refresh;
47        let externals = *chunking_context
48            .environment()
49            .supports_commonjs_externals()
50            .await?;
51
52        let content = content.await?;
53        let async_module = async_module_options.owned().await?;
54        let strict = content.strict;
55
56        Ok(EcmascriptChunkItemContent {
57            rewrite_source_path: if *chunking_context.should_use_file_source_map_uris().await? {
58                Some(chunking_context.root_path().await?.clone_value())
59            } else {
60                None
61            },
62            inner_code: content.inner_code.clone(),
63            source_map: content.source_map.clone(),
64            additional_ids: content.additional_ids.clone(),
65            options: if content.is_esm {
66                EcmascriptChunkItemOptions {
67                    strict: true,
68                    refresh,
69                    externals,
70                    async_module,
71                    stub_require: true,
72                    ..Default::default()
73                }
74            } else {
75                if async_module.is_some() {
76                    bail!("CJS module can't be async.");
77                }
78
79                EcmascriptChunkItemOptions {
80                    strict,
81                    refresh,
82                    externals,
83                    // These things are not available in ESM
84                    module: true,
85                    exports: true,
86                    ..Default::default()
87                }
88            },
89            ..Default::default()
90        }
91        .cell())
92    }
93
94    #[turbo_tasks::function]
95    pub async fn module_factory(&self) -> Result<Vc<Code>> {
96        let mut args = Vec::new();
97        if self.options.async_module.is_some() {
98            args.push("a: __turbopack_async_module__");
99        }
100        if self.options.refresh {
101            args.push("k: __turbopack_refresh__");
102        }
103        if self.options.module || self.options.refresh {
104            args.push("m: module");
105        }
106        if self.options.exports {
107            args.push("e: exports");
108        }
109        if self.options.wasm {
110            args.push("w: __turbopack_wasm__");
111            args.push("u: __turbopack_wasm_module__");
112        }
113
114        let mut code = CodeBuilder::default();
115        let additional_ids = self.additional_ids.iter().try_join().await?;
116        if !additional_ids.is_empty() {
117            code += "["
118        }
119        code += "((__turbopack_context__) => {\n";
120        if self.options.strict {
121            code += "\"use strict\";\n\n";
122        } else {
123            code += "\n";
124        }
125        if !args.is_empty() {
126            let args = FormatIter(|| args.iter().copied().intersperse(", "));
127            writeln!(code, "var {{ {args} }} = __turbopack_context__;")?;
128        }
129
130        if self.options.async_module.is_some() {
131            code += "__turbopack_async_module__(async (__turbopack_handle_async_dependencies__, \
132                     __turbopack_async_result__) => { try {\n";
133        } else if !args.is_empty() {
134            code += "{\n";
135        }
136
137        let source_map = if let Some(rewrite_source_path) = &self.rewrite_source_path {
138            fileify_source_map(self.source_map.as_ref(), rewrite_source_path.clone()).await?
139        } else {
140            self.source_map.clone()
141        };
142
143        code.push_source(&self.inner_code, source_map);
144
145        if let Some(opts) = &self.options.async_module {
146            write!(
147                code,
148                "__turbopack_async_result__();\n}} catch(e) {{ __turbopack_async_result__(e); }} \
149                 }}, {});",
150                opts.has_top_level_await
151            )?;
152        } else if !args.is_empty() {
153            code += "}";
154        }
155
156        code += "})";
157        if !additional_ids.is_empty() {
158            writeln!(code, ", {}]", StringifyJs(&additional_ids))?;
159        }
160
161        Ok(code.build().cell())
162    }
163}
164
165#[derive(
166    PartialEq, Eq, Default, Debug, Clone, Serialize, Deserialize, TraceRawVcs, NonLocalValue,
167)]
168pub struct EcmascriptChunkItemOptions {
169    /// Whether this chunk item should be in "use strict" mode.
170    pub strict: bool,
171    /// Whether this chunk item's module factory should include a
172    /// `__turbopack_refresh__` argument.
173    pub refresh: bool,
174    /// Whether this chunk item's module factory should include a `module`
175    /// argument.
176    pub module: bool,
177    /// Whether this chunk item's module factory should include an `exports`
178    /// argument.
179    pub exports: bool,
180    /// Whether this chunk item's module factory should include an argument for a throwing require
181    /// stub (for ESM)
182    pub stub_require: bool,
183    /// Whether this chunk item's module factory should include a
184    /// `__turbopack_external_require__` argument.
185    pub externals: bool,
186    /// Whether this chunk item's module is async (either has a top level await
187    /// or is importing async modules).
188    pub async_module: Option<AsyncModuleOptions>,
189    /// Whether this chunk item's module factory should include
190    /// `__turbopack_wasm__` to load WebAssembly.
191    pub wasm: bool,
192    pub placeholder_for_future_extensions: (),
193}
194
195#[derive(
196    Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, TraceRawVcs, TaskInput, NonLocalValue,
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 {
226    #[turbo_tasks::function]
227    fn content(self: Vc<Self>) -> Vc<EcmascriptChunkItemContent>;
228    #[turbo_tasks::function]
229    fn content_with_async_module_info(
230        self: Vc<Self>,
231        _async_module_info: Option<Vc<AsyncModuleInfo>>,
232    ) -> Vc<EcmascriptChunkItemContent> {
233        self.content()
234    }
235
236    /// Specifies which availability information the chunk item needs for code
237    /// generation
238    #[turbo_tasks::function]
239    fn need_async_module_info(self: Vc<Self>) -> Vc<bool> {
240        Vc::cell(false)
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(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    Ok(
265        match chunk_item
266            .content_with_async_module_info(async_module_info)
267            .module_factory()
268            .resolve()
269            .await
270        {
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().await?.clone_value(),
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    )
297}