Skip to main content

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, 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 fn module_options(
70        &self,
71        async_module_info: Option<Vc<AsyncModuleInfo>>,
72    ) -> Vc<OptionAsyncModuleOptions> {
73        if let Some(async_module) = &self.0 {
74            return async_module.module_options(async_module_info);
75        }
76
77        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 trait_ref = r.into_trait_ref().await?;
92    let Some(ty) = &trait_ref.chunking_type() else {
93        return Ok(None);
94    };
95    if !matches!(
96        ty,
97        ChunkingType::Parallel {
98            inherit_async: true,
99            ..
100        }
101    ) {
102        return Ok(None);
103    };
104    let referenced_asset: turbo_tasks::ReadRef<ReferencedAsset> =
105        ReferencedAsset::from_resolve_result(r.resolve_reference()).await?;
106    Ok(Some(referenced_asset))
107}
108
109#[turbo_tasks::value_impl]
110impl AsyncModule {
111    #[turbo_tasks::function]
112    async fn get_async_idents(
113        &self,
114        async_module_info: Vc<AsyncModuleInfo>,
115        references: Vc<ModuleReferences>,
116        chunking_context: Vc<Box<dyn ChunkingContext>>,
117    ) -> Result<Vc<AsyncModuleIdents>> {
118        let async_module_info = async_module_info.await?;
119
120        let reference_idents = references
121            .await?
122            .iter()
123            .map(|r| async {
124                let Some(referenced_asset) = get_inherit_async_referenced_asset(*r).await? else {
125                    return Ok(None);
126                };
127                Ok(match &*referenced_asset {
128                    ReferencedAsset::External(_, ExternalType::EcmaScriptModule) => {
129                        if self.import_externals {
130                            referenced_asset
131                                .get_ident(chunking_context, None, ScopeHoistingContext::None)
132                                .await?
133                                .map(|i| i.into_module_namespace_ident().unwrap())
134                                .map(|(i, ctx)| (i, ctx.unwrap_or_default().into()))
135                        } else {
136                            None
137                        }
138                    }
139                    ReferencedAsset::Some(placeable) => {
140                        if async_module_info
141                            .referenced_async_modules
142                            .contains(&ResolvedVc::upcast(*placeable))
143                        {
144                            referenced_asset
145                                .get_ident(chunking_context, None, ScopeHoistingContext::None)
146                                .await?
147                                .map(|i| i.into_module_namespace_ident().unwrap())
148                                .map(|(i, ctx)| (i, ctx.unwrap_or_default().into()))
149                        } else {
150                            None
151                        }
152                    }
153                    ReferencedAsset::External(..) => None,
154                    ReferencedAsset::None | ReferencedAsset::Unresolvable => None,
155                })
156            })
157            .try_flat_join()
158            .await?;
159
160        Ok(Vc::cell(FxIndexSet::from_iter(reference_idents)))
161    }
162
163    #[turbo_tasks::function]
164    pub(crate) async fn is_self_async(&self, references: Vc<ModuleReferences>) -> Result<Vc<bool>> {
165        if self.has_top_level_await {
166            return Ok(Vc::cell(true));
167        }
168
169        Ok(Vc::cell(
170            self.import_externals
171                && references
172                    .await?
173                    .iter()
174                    .map(|r| async {
175                        let Some(referenced_asset) = get_inherit_async_referenced_asset(*r).await?
176                        else {
177                            return Ok(false);
178                        };
179                        Ok(matches!(
180                            &*referenced_asset,
181                            ReferencedAsset::External(_, ExternalType::EcmaScriptModule)
182                        ))
183                    })
184                    .try_join()
185                    .await?
186                    .iter()
187                    .any(|&b| b),
188        ))
189    }
190
191    /// Returns
192    #[turbo_tasks::function]
193    pub fn module_options(
194        &self,
195        async_module_info: Option<Vc<AsyncModuleInfo>>,
196    ) -> Vc<OptionAsyncModuleOptions> {
197        if async_module_info.is_none() {
198            return Vc::cell(None);
199        }
200
201        Vc::cell(Some(AsyncModuleOptions {
202            has_top_level_await: self.has_top_level_await,
203        }))
204    }
205}
206
207impl AsyncModule {
208    pub async fn code_generation(
209        self: Vc<Self>,
210        async_module_info: Option<Vc<AsyncModuleInfo>>,
211        references: Vc<ModuleReferences>,
212        chunking_context: Vc<Box<dyn ChunkingContext>>,
213    ) -> Result<CodeGeneration> {
214        if let Some(async_module_info) = async_module_info {
215            let async_idents = self
216                .get_async_idents(async_module_info, references, chunking_context)
217                .await?;
218
219            if !async_idents.is_empty() {
220                let idents = async_idents
221                    .iter()
222                    .map(|(ident, ctxt)| Ident::new(ident.clone().into(), DUMMY_SP, **ctxt))
223                    .collect::<Vec<_>>();
224
225                return Ok(CodeGeneration::hoisted_stmts([
226                    CodeGenerationHoistedStmt::new(rcstr!("__turbopack_async_dependencies__"),
227                        quote!(
228                            "var __turbopack_async_dependencies__ = __turbopack_handle_async_dependencies__($deps);"
229                                as Stmt,
230                            deps: Expr = Expr::Array(ArrayLit {
231                                span: DUMMY_SP,
232                                elems: idents
233                                    .iter()
234                                    .map(|ident| { Some(Expr::Ident(ident.clone()).into()) })
235                                    .collect(),
236                            })
237                        )
238                    ),
239                    CodeGenerationHoistedStmt::new(rcstr!("__turbopack_async_dependencies__ await"),
240                        quote!(
241                            "($deps = __turbopack_async_dependencies__.then ? (await \
242                            __turbopack_async_dependencies__)() : __turbopack_async_dependencies__);" as Stmt,
243                            deps: AssignTarget = ArrayPat {
244                                span: DUMMY_SP,
245                                elems: idents
246                                    .into_iter()
247                                    .map(|ident| { Some(ident.into()) })
248                                    .collect(),
249                                optional: false,
250                                type_ann: None,
251                            }.into(),
252                        )),
253                ].to_vec()));
254            }
255        }
256
257        Ok(CodeGeneration::empty())
258    }
259}