turbopack_ecmascript/chunk/
item.rs

1use std::io::Write;
2
3use anyhow::{Result, bail};
4use serde::{Deserialize, Serialize};
5use turbo_tasks::{
6    NonLocalValue, ResolvedVc, TaskInput, Upcast, ValueToString, Vc, trace::TraceRawVcs,
7};
8use turbo_tasks_fs::{FileSystemPath, rope::Rope};
9use turbopack_core::{
10    chunk::{AsyncModuleInfo, ChunkItem, ChunkItemWithAsyncModuleInfo, ChunkingContext},
11    code_builder::{Code, CodeBuilder},
12    error::PrettyPrintError,
13    issue::{IssueExt, IssueSeverity, StyledString, code_gen::CodeGenerationIssue},
14    source_map::utils::fileify_source_map,
15};
16
17use crate::{
18    EcmascriptModuleContent, EcmascriptOptions,
19    references::async_module::{AsyncModuleOptions, OptionAsyncModuleOptions},
20    utils::FormatIter,
21};
22
23#[turbo_tasks::value(shared)]
24#[derive(Default, Clone)]
25pub struct EcmascriptChunkItemContent {
26    pub inner_code: Rope,
27    pub source_map: Option<Rope>,
28    pub options: EcmascriptChunkItemOptions,
29    pub rewrite_source_path: Option<ResolvedVc<FileSystemPath>>,
30    pub placeholder_for_future_extensions: (),
31}
32
33#[turbo_tasks::value_impl]
34impl EcmascriptChunkItemContent {
35    #[turbo_tasks::function]
36    pub async fn new(
37        content: Vc<EcmascriptModuleContent>,
38        chunking_context: Vc<Box<dyn ChunkingContext>>,
39        options: Vc<EcmascriptOptions>,
40        async_module_options: Vc<OptionAsyncModuleOptions>,
41    ) -> Result<Vc<Self>> {
42        let refresh = options.await?.refresh;
43        let externals = *chunking_context
44            .environment()
45            .supports_commonjs_externals()
46            .await?;
47
48        let content = content.await?;
49        let async_module = async_module_options.owned().await?;
50
51        Ok(EcmascriptChunkItemContent {
52            rewrite_source_path: if *chunking_context.should_use_file_source_map_uris().await? {
53                Some(chunking_context.root_path().to_resolved().await?)
54            } else {
55                None
56            },
57            inner_code: content.inner_code.clone(),
58            source_map: content.source_map.clone(),
59            options: if content.is_esm {
60                EcmascriptChunkItemOptions {
61                    strict: true,
62                    refresh,
63                    externals,
64                    async_module,
65                    stub_require: true,
66                    ..Default::default()
67                }
68            } else {
69                if async_module.is_some() {
70                    bail!("CJS module can't be async.");
71                }
72
73                EcmascriptChunkItemOptions {
74                    refresh,
75                    externals,
76                    // These things are not available in ESM
77                    module: true,
78                    exports: true,
79                    this: true,
80                    ..Default::default()
81                }
82            },
83            ..Default::default()
84        }
85        .cell())
86    }
87
88    #[turbo_tasks::function]
89    pub async fn module_factory(&self) -> Result<Vc<Code>> {
90        let mut args = Vec::new();
91        if self.options.async_module.is_some() {
92            args.push("a: __turbopack_async_module__");
93        }
94        if self.options.refresh {
95            args.push("k: __turbopack_refresh__");
96        }
97        if self.options.module || self.options.refresh {
98            args.push("m: module");
99        }
100        if self.options.exports {
101            args.push("e: exports");
102        }
103        if self.options.wasm {
104            args.push("w: __turbopack_wasm__");
105            args.push("u: __turbopack_wasm_module__");
106        }
107        let mut code = CodeBuilder::default();
108        if self.options.this {
109            code += "(function(__turbopack_context__) {\n";
110        } else {
111            code += "((__turbopack_context__) => {\n";
112        }
113        if self.options.strict {
114            code += "\"use strict\";\n\n";
115        } else {
116            code += "\n";
117        }
118        if !args.is_empty() {
119            let args = FormatIter(|| args.iter().copied().intersperse(", "));
120            writeln!(code, "var {{ {args} }} = __turbopack_context__;")?;
121        }
122
123        if self.options.async_module.is_some() {
124            code += "__turbopack_async_module__(async (__turbopack_handle_async_dependencies__, \
125                     __turbopack_async_result__) => { try {\n";
126        } else if !args.is_empty() {
127            code += "{\n";
128        }
129
130        let source_map = if let Some(rewrite_source_path) = self.rewrite_source_path {
131            fileify_source_map(self.source_map.as_ref(), *rewrite_source_path).await?
132        } else {
133            self.source_map.clone()
134        };
135
136        code.push_source(&self.inner_code, source_map);
137
138        if let Some(opts) = &self.options.async_module {
139            write!(
140                code,
141                "__turbopack_async_result__();\n}} catch(e) {{ __turbopack_async_result__(e); }} \
142                 }}, {});",
143                opts.has_top_level_await
144            )?;
145        } else if !args.is_empty() {
146            code += "}";
147        }
148
149        code += "})";
150        Ok(code.build().cell())
151    }
152}
153
154#[derive(
155    PartialEq, Eq, Default, Debug, Clone, Serialize, Deserialize, TraceRawVcs, NonLocalValue,
156)]
157pub struct EcmascriptChunkItemOptions {
158    /// Whether this chunk item should be in "use strict" mode.
159    pub strict: bool,
160    /// Whether this chunk item's module factory should include a
161    /// `__turbopack_refresh__` argument.
162    pub refresh: bool,
163    /// Whether this chunk item's module factory should include a `module`
164    /// argument.
165    pub module: bool,
166    /// Whether this chunk item's module factory should include an `exports`
167    /// argument.
168    pub exports: bool,
169    /// Whether this chunk item's module factory should include an argument for a throwing require
170    /// stub (for ESM)
171    pub stub_require: bool,
172    /// Whether this chunk item's module factory should include a
173    /// `__turbopack_external_require__` argument.
174    pub externals: bool,
175    /// Whether this chunk item's module is async (either has a top level await
176    /// or is importing async modules).
177    pub async_module: Option<AsyncModuleOptions>,
178    pub this: bool,
179    /// Whether this chunk item's module factory should include
180    /// `__turbopack_wasm__` to load WebAssembly.
181    pub wasm: bool,
182    pub placeholder_for_future_extensions: (),
183}
184
185#[derive(
186    Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, TraceRawVcs, TaskInput, NonLocalValue,
187)]
188pub struct EcmascriptChunkItemWithAsyncInfo {
189    pub chunk_item: ResolvedVc<Box<dyn EcmascriptChunkItem>>,
190    pub async_info: Option<ResolvedVc<AsyncModuleInfo>>,
191}
192
193impl EcmascriptChunkItemWithAsyncInfo {
194    pub fn from_chunk_item(
195        chunk_item: &ChunkItemWithAsyncModuleInfo,
196    ) -> Result<EcmascriptChunkItemWithAsyncInfo> {
197        let ChunkItemWithAsyncModuleInfo {
198            chunk_item,
199            module: _,
200            async_info,
201        } = chunk_item;
202        let Some(chunk_item) =
203            ResolvedVc::try_downcast::<Box<dyn EcmascriptChunkItem>>(*chunk_item)
204        else {
205            bail!("Chunk item is not an ecmascript chunk item but reporting chunk type ecmascript");
206        };
207        Ok(EcmascriptChunkItemWithAsyncInfo {
208            chunk_item,
209            async_info: *async_info,
210        })
211    }
212}
213
214#[turbo_tasks::value_trait]
215pub trait EcmascriptChunkItem: ChunkItem {
216    fn content(self: Vc<Self>) -> Vc<EcmascriptChunkItemContent>;
217    fn content_with_async_module_info(
218        self: Vc<Self>,
219        _async_module_info: Option<Vc<AsyncModuleInfo>>,
220    ) -> Vc<EcmascriptChunkItemContent> {
221        self.content()
222    }
223
224    /// Specifies which availablility information the chunk item needs for code
225    /// generation
226    fn need_async_module_info(self: Vc<Self>) -> Vc<bool> {
227        Vc::cell(false)
228    }
229}
230
231pub trait EcmascriptChunkItemExt {
232    /// Generates the module factory for this chunk item.
233    fn code(self: Vc<Self>, async_module_info: Option<Vc<AsyncModuleInfo>>) -> Vc<Code>;
234}
235
236impl<T> EcmascriptChunkItemExt for T
237where
238    T: Upcast<Box<dyn EcmascriptChunkItem>>,
239{
240    /// Generates the module factory for this chunk item.
241    fn code(self: Vc<Self>, async_module_info: Option<Vc<AsyncModuleInfo>>) -> Vc<Code> {
242        module_factory_with_code_generation_issue(Vc::upcast(self), async_module_info)
243    }
244}
245
246#[turbo_tasks::function]
247async fn module_factory_with_code_generation_issue(
248    chunk_item: Vc<Box<dyn EcmascriptChunkItem>>,
249    async_module_info: Option<Vc<AsyncModuleInfo>>,
250) -> Result<Vc<Code>> {
251    Ok(
252        match chunk_item
253            .content_with_async_module_info(async_module_info)
254            .module_factory()
255            .resolve()
256            .await
257        {
258            Ok(factory) => factory,
259            Err(error) => {
260                let id = chunk_item
261                    .chunking_context()
262                    .chunk_item_id(Vc::upcast(chunk_item))
263                    .to_string()
264                    .await;
265                let id = id.as_ref().map_or_else(|_| "unknown", |id| &**id);
266                let error = error.context(format!(
267                    "An error occurred while generating the chunk item {id}"
268                ));
269                let error_message = format!("{}", PrettyPrintError(&error)).into();
270                let js_error_message = serde_json::to_string(&error_message)?;
271                CodeGenerationIssue {
272                    severity: IssueSeverity::Error.resolved_cell(),
273                    path: chunk_item.asset_ident().path().to_resolved().await?,
274                    title: StyledString::Text("Code generation for chunk item errored".into())
275                        .resolved_cell(),
276                    message: StyledString::Text(error_message).resolved_cell(),
277                }
278                .resolved_cell()
279                .emit();
280                let mut code = CodeBuilder::default();
281                code += "(() => {{\n\n";
282                writeln!(code, "throw new Error({error});", error = &js_error_message)?;
283                code += "\n}})";
284                code.build().cell()
285            }
286        },
287    )
288}