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