turbopack_ecmascript/references/
async_module.rs

1use anyhow::Result;
2use bincode::{Decode, Encode};
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 crate::{
20    ScopeHoistingContext,
21    code_gen::{CodeGeneration, CodeGenerationHoistedStmt},
22    references::esm::base::ReferencedAsset,
23    utils::AstSyntaxContext,
24};
25
26/// Information needed for generating the async module wrapper for
27/// [EcmascriptChunkItem](crate::chunk::EcmascriptChunkItem)s.
28#[derive(PartialEq, Eq, Default, Debug, Clone, TraceRawVcs, NonLocalValue, Encode, Decode)]
29pub struct AsyncModuleOptions {
30    pub has_top_level_await: bool,
31}
32
33/// Option<[AsyncModuleOptions]>.
34#[turbo_tasks::value(transparent)]
35pub struct OptionAsyncModuleOptions(Option<AsyncModuleOptions>);
36
37#[turbo_tasks::value_impl]
38impl OptionAsyncModuleOptions {
39    #[turbo_tasks::function]
40    pub(crate) fn none() -> Vc<Self> {
41        Vc::cell(None)
42    }
43}
44
45/// Contains the information necessary to decide if an ecmascript module is
46/// async.
47///
48/// It will check if the current module or any of it's children contain a top
49/// level await statement or is referencing an external ESM module.
50#[turbo_tasks::value(shared)]
51pub struct AsyncModule {
52    pub has_top_level_await: bool,
53    pub import_externals: bool,
54}
55
56/// Option<[AsyncModule]>.
57#[turbo_tasks::value(transparent)]
58pub struct OptionAsyncModule(Option<ResolvedVc<AsyncModule>>);
59
60#[turbo_tasks::value_impl]
61impl OptionAsyncModule {
62    /// Create an empty [OptionAsyncModule].
63    #[turbo_tasks::function]
64    pub fn none() -> Vc<Self> {
65        Vc::cell(None)
66    }
67
68    #[turbo_tasks::function]
69    pub async fn module_options(
70        self: Vc<Self>,
71        async_module_info: Option<Vc<AsyncModuleInfo>>,
72    ) -> Result<Vc<OptionAsyncModuleOptions>> {
73        if let Some(async_module) = &*self.await? {
74            return Ok(async_module.module_options(async_module_info));
75        }
76
77        Ok(OptionAsyncModuleOptions::none())
78    }
79}
80
81/// The identifiers (and their corresponding syntax context) of all async modules referenced by the
82/// current module.
83#[turbo_tasks::value(transparent)]
84struct AsyncModuleIdents(
85    #[bincode(with = "turbo_bincode::indexset")] FxIndexSet<(String, AstSyntaxContext)>,
86);
87
88async fn get_inherit_async_referenced_asset(
89    r: ResolvedVc<Box<dyn ModuleReference>>,
90) -> Result<Option<ReadRef<ReferencedAsset>>> {
91    let Some(r) = ResolvedVc::try_downcast::<Box<dyn ChunkableModuleReference>>(r) 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) = get_inherit_async_referenced_asset(*r).await?
178                        else {
179                            return Ok(false);
180                        };
181                        Ok(matches!(
182                            &*referenced_asset,
183                            ReferencedAsset::External(_, ExternalType::EcmaScriptModule)
184                        ))
185                    })
186                    .try_join()
187                    .await?
188                    .iter()
189                    .any(|&b| b),
190        ))
191    }
192
193    /// Returns
194    #[turbo_tasks::function]
195    pub fn module_options(
196        &self,
197        async_module_info: Option<Vc<AsyncModuleInfo>>,
198    ) -> Vc<OptionAsyncModuleOptions> {
199        if async_module_info.is_none() {
200            return Vc::cell(None);
201        }
202
203        Vc::cell(Some(AsyncModuleOptions {
204            has_top_level_await: self.has_top_level_await,
205        }))
206    }
207}
208
209impl AsyncModule {
210    pub async fn code_generation(
211        self: Vc<Self>,
212        async_module_info: Option<Vc<AsyncModuleInfo>>,
213        references: Vc<ModuleReferences>,
214        chunking_context: Vc<Box<dyn ChunkingContext>>,
215    ) -> Result<CodeGeneration> {
216        if let Some(async_module_info) = async_module_info {
217            let async_idents = self
218                .get_async_idents(async_module_info, references, chunking_context)
219                .await?;
220
221            if !async_idents.is_empty() {
222                let idents = async_idents
223                    .iter()
224                    .map(|(ident, ctxt)| Ident::new(ident.clone().into(), DUMMY_SP, **ctxt))
225                    .collect::<Vec<_>>();
226
227                return Ok(CodeGeneration::hoisted_stmts([
228                    CodeGenerationHoistedStmt::new(rcstr!("__turbopack_async_dependencies__"),
229                        quote!(
230                            "var __turbopack_async_dependencies__ = __turbopack_handle_async_dependencies__($deps);"
231                                as Stmt,
232                            deps: Expr = Expr::Array(ArrayLit {
233                                span: DUMMY_SP,
234                                elems: idents
235                                    .iter()
236                                    .map(|ident| { Some(Expr::Ident(ident.clone()).into()) })
237                                    .collect(),
238                            })
239                        )
240                    ),
241                    CodeGenerationHoistedStmt::new(rcstr!("__turbopack_async_dependencies__ await"),
242                        quote!(
243                            "($deps = __turbopack_async_dependencies__.then ? (await \
244                            __turbopack_async_dependencies__)() : __turbopack_async_dependencies__);" as Stmt,
245                            deps: AssignTarget = ArrayPat {
246                                span: DUMMY_SP,
247                                elems: idents
248                                    .into_iter()
249                                    .map(|ident| { Some(ident.into()) })
250                                    .collect(),
251                                optional: false,
252                                type_ann: None,
253                            }.into(),
254                        )),
255                ].to_vec()));
256            }
257        }
258
259        Ok(CodeGeneration::empty())
260    }
261}