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