turbopack_ecmascript/references/
async_module.rs

1use anyhow::Result;
2use serde::{Deserialize, Serialize};
3use swc_core::{
4    common::DUMMY_SP,
5    ecma::ast::{ArrayLit, ArrayPat, Expr, Ident},
6    quote,
7};
8use turbo_tasks::{
9    FxIndexSet, NonLocalValue, ReadRef, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt, Vc,
10    trace::TraceRawVcs,
11};
12use turbopack_core::{
13    chunk::{AsyncModuleInfo, ChunkableModuleReference, ChunkingContext, ChunkingType},
14    reference::{ModuleReference, ModuleReferences},
15    resolve::ExternalType,
16};
17
18use super::esm::base::ReferencedAsset;
19use crate::code_gen::{CodeGeneration, CodeGenerationHoistedStmt};
20
21/// Information needed for generating the async module wrapper for
22/// [EcmascriptChunkItem](crate::chunk::EcmascriptChunkItem)s.
23#[derive(
24    PartialEq, Eq, Default, Debug, Clone, Serialize, Deserialize, TraceRawVcs, NonLocalValue,
25)]
26pub struct AsyncModuleOptions {
27    pub has_top_level_await: bool,
28}
29
30/// Option<[AsyncModuleOptions]>.
31#[turbo_tasks::value(transparent)]
32pub struct OptionAsyncModuleOptions(Option<AsyncModuleOptions>);
33
34#[turbo_tasks::value_impl]
35impl OptionAsyncModuleOptions {
36    #[turbo_tasks::function]
37    pub(crate) fn none() -> Vc<Self> {
38        Vc::cell(None)
39    }
40}
41
42/// Contains the information necessary to decide if an ecmascript module is
43/// async.
44///
45/// It will check if the current module or any of it's children contain a top
46/// level await statement or is referencing an external ESM module.
47#[turbo_tasks::value(shared)]
48pub struct AsyncModule {
49    pub has_top_level_await: bool,
50    pub import_externals: bool,
51}
52
53/// Option<[AsyncModule]>.
54#[turbo_tasks::value(transparent)]
55pub struct OptionAsyncModule(Option<ResolvedVc<AsyncModule>>);
56
57#[turbo_tasks::value_impl]
58impl OptionAsyncModule {
59    /// Create an empty [OptionAsyncModule].
60    #[turbo_tasks::function]
61    pub fn none() -> Vc<Self> {
62        Vc::cell(None)
63    }
64
65    #[turbo_tasks::function]
66    pub async fn module_options(
67        self: Vc<Self>,
68        async_module_info: Option<Vc<AsyncModuleInfo>>,
69    ) -> Result<Vc<OptionAsyncModuleOptions>> {
70        if let Some(async_module) = &*self.await? {
71            return Ok(async_module.module_options(async_module_info));
72        }
73
74        Ok(OptionAsyncModuleOptions::none())
75    }
76}
77
78#[turbo_tasks::value(transparent)]
79struct AsyncModuleIdents(FxIndexSet<String>);
80
81async fn get_inherit_async_referenced_asset(
82    r: Vc<Box<dyn ModuleReference>>,
83) -> Result<Option<ReadRef<ReferencedAsset>>> {
84    let Some(r) = Vc::try_resolve_downcast::<Box<dyn ChunkableModuleReference>>(r).await? else {
85        return Ok(None);
86    };
87    let Some(ty) = &*r.chunking_type().await? else {
88        return Ok(None);
89    };
90    if !matches!(
91        ty,
92        ChunkingType::Parallel {
93            inherit_async: true,
94            ..
95        }
96    ) {
97        return Ok(None);
98    };
99    let referenced_asset: turbo_tasks::ReadRef<ReferencedAsset> =
100        ReferencedAsset::from_resolve_result(r.resolve_reference()).await?;
101    Ok(Some(referenced_asset))
102}
103
104#[turbo_tasks::value_impl]
105impl AsyncModule {
106    #[turbo_tasks::function]
107    async fn get_async_idents(
108        &self,
109        async_module_info: Vc<AsyncModuleInfo>,
110        references: Vc<ModuleReferences>,
111        chunking_context: Vc<Box<dyn ChunkingContext>>,
112    ) -> Result<Vc<AsyncModuleIdents>> {
113        let async_module_info = async_module_info.await?;
114
115        let reference_idents = references
116            .await?
117            .iter()
118            .map(|r| async {
119                let Some(referenced_asset) = get_inherit_async_referenced_asset(**r).await? else {
120                    return Ok(None);
121                };
122                Ok(match &*referenced_asset {
123                    ReferencedAsset::External(_, ExternalType::EcmaScriptModule) => {
124                        if self.import_externals {
125                            referenced_asset.get_ident(chunking_context).await?
126                        } else {
127                            None
128                        }
129                    }
130                    ReferencedAsset::Some(placeable) => {
131                        if async_module_info
132                            .referenced_async_modules
133                            .contains(&ResolvedVc::upcast(*placeable))
134                        {
135                            referenced_asset.get_ident(chunking_context).await?
136                        } else {
137                            None
138                        }
139                    }
140                    ReferencedAsset::External(..) => None,
141                    ReferencedAsset::None | ReferencedAsset::Unresolvable => None,
142                })
143            })
144            .try_flat_join()
145            .await?;
146
147        Ok(Vc::cell(FxIndexSet::from_iter(reference_idents)))
148    }
149
150    #[turbo_tasks::function]
151    pub(crate) async fn is_self_async(&self, references: Vc<ModuleReferences>) -> Result<Vc<bool>> {
152        if self.has_top_level_await {
153            return Ok(Vc::cell(true));
154        }
155
156        Ok(Vc::cell(
157            self.import_externals
158                && references
159                    .await?
160                    .iter()
161                    .map(|r| async {
162                        let Some(referenced_asset) =
163                            get_inherit_async_referenced_asset(**r).await?
164                        else {
165                            return Ok(false);
166                        };
167                        Ok(matches!(
168                            &*referenced_asset,
169                            ReferencedAsset::External(_, ExternalType::EcmaScriptModule)
170                        ))
171                    })
172                    .try_join()
173                    .await?
174                    .iter()
175                    .any(|&b| b),
176        ))
177    }
178
179    /// Returns
180    #[turbo_tasks::function]
181    pub fn module_options(
182        &self,
183        async_module_info: Option<Vc<AsyncModuleInfo>>,
184    ) -> Vc<OptionAsyncModuleOptions> {
185        if async_module_info.is_none() {
186            return Vc::cell(None);
187        }
188
189        Vc::cell(Some(AsyncModuleOptions {
190            has_top_level_await: self.has_top_level_await,
191        }))
192    }
193}
194
195impl AsyncModule {
196    pub async fn code_generation(
197        self: Vc<Self>,
198        async_module_info: Option<Vc<AsyncModuleInfo>>,
199        references: Vc<ModuleReferences>,
200        chunking_context: Vc<Box<dyn ChunkingContext>>,
201    ) -> Result<CodeGeneration> {
202        if let Some(async_module_info) = async_module_info {
203            let async_idents = self
204                .get_async_idents(async_module_info, references, chunking_context)
205                .await?;
206
207            if !async_idents.is_empty() {
208                let idents = async_idents
209                    .iter()
210                    .map(|ident: &String| {
211                        Ident::new(ident.clone().into(), DUMMY_SP, Default::default())
212                    })
213                    .collect::<Vec<_>>();
214
215                return Ok(CodeGeneration::hoisted_stmts([
216                    CodeGenerationHoistedStmt::new("__turbopack_async_dependencies__".into(),
217                        quote!(
218                            "var __turbopack_async_dependencies__ = __turbopack_handle_async_dependencies__($deps);"
219                                as Stmt,
220                            deps: Expr = Expr::Array(ArrayLit {
221                                span: DUMMY_SP,
222                                elems: idents
223                                    .iter()
224                                    .map(|ident| { Some(Expr::Ident(ident.clone()).into()) })
225                                    .collect(),
226                            })
227                        )
228                    ),
229                    CodeGenerationHoistedStmt::new("__turbopack_async_dependencies__ await".into(),
230                        quote!(
231                            "($deps = __turbopack_async_dependencies__.then ? (await \
232                            __turbopack_async_dependencies__)() : __turbopack_async_dependencies__);" as Stmt,
233                            deps: AssignTarget = ArrayPat {
234                                span: DUMMY_SP,
235                                elems: idents
236                                    .into_iter()
237                                    .map(|ident| { Some(ident.into()) })
238                                    .collect(),
239                                optional: false,
240                                type_ann: None,
241                            }.into(),
242                        )),
243                ].to_vec()));
244            }
245        }
246
247        Ok(CodeGeneration::empty())
248    }
249}