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    #[turbo_tasks::function]
90    pub async fn module_factory(&self) -> Result<Vc<Code>> {
91        let mut code = CodeBuilder::default();
92        for additional_id in self.additional_ids.iter().try_join().await? {
93            writeln!(code, "{}, ", StringifyJs(&*additional_id))?;
94        }
95        if self.options.module_and_exports {
96            code += "((__turbopack_context__, module, exports) => {\n";
97        } else {
98            code += "((__turbopack_context__) => {\n";
99        }
100        if self.options.strict {
101            code += "\"use strict\";\n\n";
102        } else {
103            code += "\n";
104        }
105
106        if self.options.async_module.is_some() {
107            writeln!(
108                code,
109                "return {TURBOPACK_ASYNC_MODULE}(async (__turbopack_handle_async_dependencies__, \
110                 __turbopack_async_result__) => {{ try {{\n"
111            )?;
112        }
113
114        let source_map = if let Some(rewrite_source_path) = &self.rewrite_source_path {
115            fileify_source_map(self.source_map.as_ref(), rewrite_source_path.clone()).await?
116        } else {
117            self.source_map.clone()
118        };
119
120        code.push_source(&self.inner_code, source_map);
121
122        if let Some(opts) = &self.options.async_module {
123            write!(
124                code,
125                "__turbopack_async_result__();\n}} catch(e) {{ __turbopack_async_result__(e); }} \
126                 }}, {});",
127                opts.has_top_level_await
128            )?;
129        }
130
131        code += "})";
132
133        Ok(code.build().cell())
134    }
135}
136
137#[derive(
138    PartialEq, Eq, Default, Debug, Clone, Serialize, Deserialize, TraceRawVcs, NonLocalValue,
139)]
140pub struct EcmascriptChunkItemOptions {
141    /// Whether this chunk item should be in "use strict" mode.
142    pub strict: bool,
143    /// Whether this chunk item's module factory should include a `module` and
144    /// `exports` argument.
145    pub module_and_exports: bool,
146    /// Whether this chunk item's module factory should include a
147    /// `__turbopack_external_require__` argument.
148    pub externals: bool,
149    /// Whether this chunk item's module is async (either has a top level await
150    /// or is importing async modules).
151    pub async_module: Option<AsyncModuleOptions>,
152    pub placeholder_for_future_extensions: (),
153}
154
155#[derive(
156    Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, TraceRawVcs, TaskInput, NonLocalValue,
157)]
158pub struct EcmascriptChunkItemWithAsyncInfo {
159    pub chunk_item: ResolvedVc<Box<dyn EcmascriptChunkItem>>,
160    pub async_info: Option<ResolvedVc<AsyncModuleInfo>>,
161}
162
163impl EcmascriptChunkItemWithAsyncInfo {
164    pub fn from_chunk_item(
165        chunk_item: &ChunkItemWithAsyncModuleInfo,
166    ) -> Result<EcmascriptChunkItemWithAsyncInfo> {
167        let ChunkItemWithAsyncModuleInfo {
168            chunk_item,
169            module: _,
170            async_info,
171        } = chunk_item;
172        let Some(chunk_item) =
173            ResolvedVc::try_downcast::<Box<dyn EcmascriptChunkItem>>(*chunk_item)
174        else {
175            bail!("Chunk item is not an ecmascript chunk item but reporting chunk type ecmascript");
176        };
177        Ok(EcmascriptChunkItemWithAsyncInfo {
178            chunk_item,
179            async_info: *async_info,
180        })
181    }
182}
183
184#[turbo_tasks::value_trait]
185pub trait EcmascriptChunkItem: ChunkItem {
186    #[turbo_tasks::function]
187    fn content(self: Vc<Self>) -> Vc<EcmascriptChunkItemContent>;
188    #[turbo_tasks::function]
189    fn content_with_async_module_info(
190        self: Vc<Self>,
191        _async_module_info: Option<Vc<AsyncModuleInfo>>,
192    ) -> Vc<EcmascriptChunkItemContent> {
193        self.content()
194    }
195
196    /// Specifies which availability information the chunk item needs for code
197    /// generation
198    #[turbo_tasks::function]
199    fn need_async_module_info(self: Vc<Self>) -> Vc<bool> {
200        Vc::cell(false)
201    }
202}
203
204pub trait EcmascriptChunkItemExt {
205    /// Generates the module factory for this chunk item.
206    fn code(self: Vc<Self>, async_module_info: Option<Vc<AsyncModuleInfo>>) -> Vc<Code>;
207}
208
209impl<T> EcmascriptChunkItemExt for T
210where
211    T: Upcast<Box<dyn EcmascriptChunkItem>>,
212{
213    /// Generates the module factory for this chunk item.
214    fn code(self: Vc<Self>, async_module_info: Option<Vc<AsyncModuleInfo>>) -> Vc<Code> {
215        module_factory_with_code_generation_issue(Vc::upcast(self), async_module_info)
216    }
217}
218
219#[turbo_tasks::function]
220async fn module_factory_with_code_generation_issue(
221    chunk_item: Vc<Box<dyn EcmascriptChunkItem>>,
222    async_module_info: Option<Vc<AsyncModuleInfo>>,
223) -> Result<Vc<Code>> {
224    Ok(
225        match chunk_item
226            .content_with_async_module_info(async_module_info)
227            .module_factory()
228            .resolve()
229            .await
230        {
231            Ok(factory) => factory,
232            Err(error) => {
233                let id = chunk_item.asset_ident().to_string().await;
234                let id = id.as_ref().map_or_else(|_| "unknown", |id| &**id);
235                let error = error.context(format!(
236                    "An error occurred while generating the chunk item {id}"
237                ));
238                let error_message = format!("{}", PrettyPrintError(&error)).into();
239                let js_error_message = serde_json::to_string(&error_message)?;
240                CodeGenerationIssue {
241                    severity: IssueSeverity::Error,
242                    path: chunk_item.asset_ident().path().owned().await?,
243                    title: StyledString::Text(rcstr!("Code generation for chunk item errored"))
244                        .resolved_cell(),
245                    message: StyledString::Text(error_message).resolved_cell(),
246                }
247                .resolved_cell()
248                .emit();
249                let mut code = CodeBuilder::default();
250                code += "(() => {{\n\n";
251                writeln!(code, "throw new Error({error});", error = &js_error_message)?;
252                code += "\n}})";
253                code.build().cell()
254            }
255        },
256    )
257}