Skip to main content

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, PrettyPrintError, ResolvedVc, TaskInput, Upcast, ValueToString, Vc,
9    trace::TraceRawVcs,
10};
11use turbo_tasks_fs::{FileSystemPath, rope::Rope};
12use turbopack_core::{
13    chunk::{
14        AsyncModuleInfo, ChunkItem, ChunkItemWithAsyncModuleInfo, ChunkType, ChunkingContext,
15        ChunkingContextExt, ModuleId, SourceMapSourceType,
16    },
17    code_builder::{Code, CodeBuilder},
18    ident::AssetIdent,
19    issue::{IssueExt, IssueSeverity, StyledString, code_gen::CodeGenerationIssue},
20    module::Module,
21    module_graph::ModuleGraph,
22    output::OutputAssetsReference,
23    source_map::utils::{absolute_fileify_source_map, relative_fileify_source_map},
24};
25
26use crate::{
27    EcmascriptModuleContent,
28    chunk::{chunk_type::EcmascriptChunkType, placeable::EcmascriptChunkPlaceable},
29    references::async_module::{AsyncModuleOptions, OptionAsyncModuleOptions},
30    runtime_functions::TURBOPACK_ASYNC_MODULE,
31    utils::StringifyJs,
32};
33
34#[derive(
35    Debug,
36    Clone,
37    PartialEq,
38    Eq,
39    Hash,
40    TraceRawVcs,
41    TaskInput,
42    NonLocalValue,
43    Default,
44    Encode,
45    Decode,
46)]
47pub enum RewriteSourcePath {
48    AbsoluteFilePath(FileSystemPath),
49    RelativeFilePath(FileSystemPath, RcStr),
50    #[default]
51    None,
52}
53
54// Note we don't want to persist this as `module_factory_with_code_generation_issue` is already
55// persisted and we want to avoid duplicating it.
56#[turbo_tasks::value(shared, serialization = "none")]
57#[derive(Default, Clone)]
58pub struct EcmascriptChunkItemContent {
59    pub inner_code: Rope,
60    pub source_map: Option<Rope>,
61    pub additional_ids: SmallVec<[ModuleId; 1]>,
62    pub options: EcmascriptChunkItemOptions,
63    pub rewrite_source_path: RewriteSourcePath,
64    pub placeholder_for_future_extensions: (),
65}
66
67#[turbo_tasks::value_impl]
68impl EcmascriptChunkItemContent {
69    #[turbo_tasks::function]
70    pub async fn new(
71        content: Vc<EcmascriptModuleContent>,
72        chunking_context: Vc<Box<dyn ChunkingContext>>,
73        async_module_options: Vc<OptionAsyncModuleOptions>,
74    ) -> Result<Vc<Self>> {
75        let externals = *chunking_context
76            .environment()
77            .supports_commonjs_externals()
78            .await?;
79
80        let content = content.await?;
81        let async_module = async_module_options.owned().await?;
82        let strict = content.strict;
83
84        Ok(EcmascriptChunkItemContent {
85            rewrite_source_path: match *chunking_context.source_map_source_type().await? {
86                SourceMapSourceType::AbsoluteFileUri => {
87                    RewriteSourcePath::AbsoluteFilePath(chunking_context.root_path().owned().await?)
88                }
89                SourceMapSourceType::RelativeUri => RewriteSourcePath::RelativeFilePath(
90                    chunking_context.root_path().owned().await?,
91                    chunking_context
92                        .relative_path_from_chunk_root_to_project_root()
93                        .owned()
94                        .await?,
95                ),
96                SourceMapSourceType::TurbopackUri => RewriteSourcePath::None,
97            },
98            inner_code: content.inner_code.clone(),
99            source_map: content.source_map.clone(),
100            additional_ids: content.additional_ids.clone(),
101            options: if content.is_esm {
102                EcmascriptChunkItemOptions {
103                    strict: true,
104                    externals,
105                    async_module,
106                    ..Default::default()
107                }
108            } else {
109                if async_module.is_some() {
110                    bail!("CJS module can't be async.");
111                }
112
113                EcmascriptChunkItemOptions {
114                    strict,
115                    externals,
116                    // These things are not available in ESM
117                    module_and_exports: true,
118                    ..Default::default()
119                }
120            },
121            ..Default::default()
122        }
123        .cell())
124    }
125}
126
127impl EcmascriptChunkItemContent {
128    async fn module_factory(&self) -> Result<ResolvedVc<Code>> {
129        let mut code = CodeBuilder::default();
130        for additional_id in self.additional_ids.iter() {
131            writeln!(code, "{}, ", StringifyJs(&additional_id))?;
132        }
133        if self.options.module_and_exports {
134            code += "((__turbopack_context__, module, exports) => {\n";
135        } else {
136            code += "((__turbopack_context__) => {\n";
137        }
138        if self.options.strict {
139            code += "\"use strict\";\n\n";
140        } else {
141            code += "\n";
142        }
143
144        if self.options.async_module.is_some() {
145            writeln!(
146                code,
147                "return {TURBOPACK_ASYNC_MODULE}(async (__turbopack_handle_async_dependencies__, \
148                 __turbopack_async_result__) => {{ try {{\n"
149            )?;
150        }
151
152        let source_map = match &self.rewrite_source_path {
153            RewriteSourcePath::AbsoluteFilePath(path) => {
154                absolute_fileify_source_map(self.source_map.as_ref(), path.clone()).await?
155            }
156            RewriteSourcePath::RelativeFilePath(path, relative_path) => {
157                relative_fileify_source_map(
158                    self.source_map.as_ref(),
159                    path.clone(),
160                    relative_path.clone(),
161                )
162                .await?
163            }
164            RewriteSourcePath::None => self.source_map.clone(),
165        };
166
167        code.push_source(&self.inner_code, source_map);
168
169        if let Some(opts) = &self.options.async_module {
170            write!(
171                code,
172                "__turbopack_async_result__();\n}} catch(e) {{ __turbopack_async_result__(e); }} \
173                 }}, {});",
174                opts.has_top_level_await
175            )?;
176        }
177
178        code += "})";
179
180        Ok(code.build().resolved_cell())
181    }
182}
183
184#[derive(PartialEq, Eq, Default, Debug, Clone, TraceRawVcs, NonLocalValue, Encode, Decode)]
185pub struct EcmascriptChunkItemOptions {
186    /// Whether this chunk item should be in "use strict" mode.
187    pub strict: bool,
188    /// Whether this chunk item's module factory should include a `module` and
189    /// `exports` argument.
190    pub module_and_exports: bool,
191    /// Whether this chunk item's module factory should include a
192    /// `__turbopack_external_require__` argument.
193    pub externals: bool,
194    /// Whether this chunk item's module is async (either has a top level await
195    /// or is importing async modules).
196    pub async_module: Option<AsyncModuleOptions>,
197    pub placeholder_for_future_extensions: (),
198}
199
200#[derive(
201    Debug, Clone, PartialEq, Eq, Hash, TraceRawVcs, TaskInput, NonLocalValue, Encode, Decode,
202)]
203pub struct EcmascriptChunkItemWithAsyncInfo {
204    pub chunk_item: ResolvedVc<Box<dyn EcmascriptChunkItem>>,
205    pub async_info: Option<ResolvedVc<AsyncModuleInfo>>,
206}
207
208impl EcmascriptChunkItemWithAsyncInfo {
209    pub fn from_chunk_item(
210        chunk_item: &ChunkItemWithAsyncModuleInfo,
211    ) -> Result<EcmascriptChunkItemWithAsyncInfo> {
212        let ChunkItemWithAsyncModuleInfo {
213            chunk_item,
214            module: _,
215            async_info,
216        } = chunk_item;
217        let Some(chunk_item) =
218            ResolvedVc::try_downcast::<Box<dyn EcmascriptChunkItem>>(*chunk_item)
219        else {
220            bail!("Chunk item is not an ecmascript chunk item but reporting chunk type ecmascript");
221        };
222        Ok(EcmascriptChunkItemWithAsyncInfo {
223            chunk_item,
224            async_info: *async_info,
225        })
226    }
227}
228
229#[turbo_tasks::value_trait]
230pub trait EcmascriptChunkItem: ChunkItem + OutputAssetsReference {
231    #[turbo_tasks::function]
232    fn content(self: Vc<Self>) -> Vc<EcmascriptChunkItemContent>;
233
234    /// Fetches the content of the chunk item with async module info.
235    /// When `estimated` is true, it's ok to provide an estimated content, since it's only used for
236    /// compute the chunking. When `estimated` is true, this function should not invoke other
237    /// chunking operations that would cause cycles.
238    #[turbo_tasks::function]
239    fn content_with_async_module_info(
240        self: Vc<Self>,
241        _async_module_info: Option<Vc<AsyncModuleInfo>>,
242        _estimated: bool,
243    ) -> Vc<EcmascriptChunkItemContent> {
244        self.content()
245    }
246}
247
248pub trait EcmascriptChunkItemExt {
249    /// Generates the module factory for this chunk item.
250    fn code(self: Vc<Self>, async_module_info: Option<Vc<AsyncModuleInfo>>) -> Vc<Code>;
251}
252
253impl<T> EcmascriptChunkItemExt for T
254where
255    T: Upcast<Box<dyn EcmascriptChunkItem>>,
256{
257    /// Generates the module factory for this chunk item.
258    fn code(self: Vc<Self>, async_module_info: Option<Vc<AsyncModuleInfo>>) -> Vc<Code> {
259        module_factory_with_code_generation_issue(Vc::upcast_non_strict(self), async_module_info)
260    }
261}
262
263#[turbo_tasks::function]
264async fn module_factory_with_code_generation_issue(
265    chunk_item: Vc<Box<dyn EcmascriptChunkItem>>,
266    async_module_info: Option<Vc<AsyncModuleInfo>>,
267) -> Result<Vc<Code>> {
268    let content = match chunk_item
269        .content_with_async_module_info(async_module_info, false)
270        .await
271    {
272        Ok(item) => item.module_factory().await,
273        Err(err) => Err(err),
274    };
275    Ok(match content {
276        Ok(factory) => *factory,
277        Err(error) => {
278            let id = chunk_item.asset_ident().to_string().await;
279            let id = id.as_ref().map_or_else(|_| "unknown", |id| &**id);
280            let error = error.context(format!(
281                "An error occurred while generating the chunk item {id}"
282            ));
283            let error_message = format!("{}", PrettyPrintError(&error)).into();
284            let js_error_message = serde_json::to_string(&error_message)?;
285            CodeGenerationIssue {
286                severity: IssueSeverity::Error,
287                path: chunk_item.asset_ident().path().owned().await?,
288                title: StyledString::Text(rcstr!("Code generation for chunk item errored"))
289                    .resolved_cell(),
290                message: StyledString::Text(error_message).resolved_cell(),
291                source: None,
292            }
293            .resolved_cell()
294            .emit();
295            let mut code = CodeBuilder::default();
296            code += "(() => {{\n\n";
297            writeln!(code, "throw new Error({error});", error = &js_error_message)?;
298            code += "\n}})";
299            code.build().cell()
300        }
301    })
302}
303
304/// Generic chunk item that wraps any EcmascriptChunkPlaceable module.
305/// This replaces the need for individual per-module ChunkItem wrapper structs.
306#[turbo_tasks::value]
307pub struct EcmascriptModuleChunkItem {
308    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
309    chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
310    module_graph: ResolvedVc<ModuleGraph>,
311}
312
313/// Factory function to create an EcmascriptModuleChunkItem.
314/// Use this instead of implementing ChunkableModule::as_chunk_item() on each module.
315pub fn ecmascript_chunk_item(
316    module: ResolvedVc<Box<dyn EcmascriptChunkPlaceable>>,
317    module_graph: ResolvedVc<ModuleGraph>,
318    chunking_context: ResolvedVc<Box<dyn ChunkingContext>>,
319) -> Vc<Box<dyn ChunkItem>> {
320    Vc::upcast(
321        EcmascriptModuleChunkItem {
322            module,
323            chunking_context,
324            module_graph,
325        }
326        .cell(),
327    )
328}
329
330#[turbo_tasks::value_impl]
331impl ChunkItem for EcmascriptModuleChunkItem {
332    #[turbo_tasks::function]
333    fn asset_ident(&self) -> Vc<AssetIdent> {
334        self.module.ident()
335    }
336
337    #[turbo_tasks::function]
338    fn content_ident(&self) -> Vc<AssetIdent> {
339        self.module
340            .chunk_item_content_ident(*self.chunking_context, *self.module_graph)
341    }
342
343    #[turbo_tasks::function]
344    fn ty(&self) -> Vc<Box<dyn ChunkType>> {
345        Vc::upcast(Vc::<EcmascriptChunkType>::default())
346    }
347
348    #[turbo_tasks::function]
349    fn module(&self) -> Vc<Box<dyn Module>> {
350        Vc::upcast(*self.module)
351    }
352
353    #[turbo_tasks::function]
354    fn chunking_context(&self) -> Vc<Box<dyn ChunkingContext>> {
355        *self.chunking_context
356    }
357}
358
359#[turbo_tasks::value_impl]
360impl OutputAssetsReference for EcmascriptModuleChunkItem {
361    #[turbo_tasks::function]
362    fn references(&self) -> Vc<turbopack_core::output::OutputAssetsWithReferenced> {
363        self.module
364            .chunk_item_output_assets(*self.chunking_context, *self.module_graph)
365    }
366}
367
368#[turbo_tasks::value_impl]
369impl EcmascriptChunkItem for EcmascriptModuleChunkItem {
370    #[turbo_tasks::function]
371    fn content(&self) -> Vc<EcmascriptChunkItemContent> {
372        self.module
373            .chunk_item_content(*self.chunking_context, *self.module_graph, None, false)
374    }
375
376    #[turbo_tasks::function]
377    fn content_with_async_module_info(
378        &self,
379        async_module_info: Option<Vc<AsyncModuleInfo>>,
380        estimated: bool,
381    ) -> Vc<EcmascriptChunkItemContent> {
382        self.module.chunk_item_content(
383            *self.chunking_context,
384            *self.module_graph,
385            async_module_info,
386            estimated,
387        )
388    }
389}