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::{FxIndexSet, NonLocalValue, ResolvedVc, TryFlatJoinIterExt, TryJoinIterExt, Vc};
10use turbopack_core::{
11    chunk::{AsyncModuleInfo, ChunkingContext, ChunkingType},
12    reference::{ModuleReference, ModuleReferences},
13    resolve::ExternalType,
14};
15
16use crate::{
17    ScopeHoistingContext,
18    code_gen::{CodeGeneration, CodeGenerationHoistedStmt},
19    references::esm::base::ReferencedAsset,
20    utils::AstSyntaxContext,
21};
22
23/// Information needed for generating the async module wrapper for
24/// [EcmascriptChunkItem](crate::chunk::EcmascriptChunkItem)s.
25#[derive(PartialEq, Eq, Default, Debug, Clone, NonLocalValue, Encode, Decode)]
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 fn module_options(
67        &self,
68        async_module_info: Option<Vc<AsyncModuleInfo>>,
69    ) -> Vc<OptionAsyncModuleOptions> {
70        if let Some(async_module) = &self.0 {
71            return async_module.module_options(async_module_info);
72        }
73
74        OptionAsyncModuleOptions::none()
75    }
76}
77
78/// The identifiers (and their corresponding syntax context) of all async modules referenced by the
79/// current module.
80#[turbo_tasks::value(transparent)]
81struct AsyncModuleIdents(
82    #[bincode(with = "turbo_bincode::indexset")] FxIndexSet<(String, AstSyntaxContext)>,
83);
84
85async fn get_inherit_async_referenced_asset(
86    r: ResolvedVc<Box<dyn ModuleReference>>,
87) -> Result<Option<ReferencedAsset>> {
88    let trait_ref = r.into_trait_ref().await?;
89    let Some(ty) = &trait_ref.chunking_type() else {
90        return Ok(None);
91    };
92    if !matches!(
93        ty,
94        ChunkingType::Parallel {
95            inherit_async: true,
96            ..
97        }
98    ) {
99        return Ok(None);
100    };
101    let referenced_asset: ReferencedAsset =
102        ReferencedAsset::from_resolve_result(r.resolve_reference()).await?;
103    Ok(Some(referenced_asset))
104}
105
106#[turbo_tasks::value_impl]
107impl AsyncModule {
108    #[turbo_tasks::function]
109    async fn get_async_idents(
110        &self,
111        async_module_info: Vc<AsyncModuleInfo>,
112        references: Vc<ModuleReferences>,
113        chunking_context: Vc<Box<dyn ChunkingContext>>,
114    ) -> Result<Vc<AsyncModuleIdents>> {
115        let async_module_info = async_module_info.await?;
116
117        let reference_idents = references
118            .await?
119            .iter()
120            .map(async |r| {
121                let Some(referenced_asset) = get_inherit_async_referenced_asset(*r).await? else {
122                    return Ok(None);
123                };
124                Ok(match &referenced_asset {
125                    ReferencedAsset::External(_, ExternalType::EcmaScriptModule) => {
126                        if self.import_externals {
127                            referenced_asset
128                                .get_ident(chunking_context, None, ScopeHoistingContext::None)
129                                .await?
130                                .map(|i| i.into_module_namespace_ident().unwrap())
131                                .map(|(i, ctx)| (i, ctx.unwrap_or_default().into()))
132                        } else {
133                            None
134                        }
135                    }
136                    ReferencedAsset::Some(placeable) => {
137                        if async_module_info
138                            .referenced_async_modules
139                            .contains(&ResolvedVc::upcast(*placeable))
140                        {
141                            referenced_asset
142                                .get_ident(chunking_context, None, ScopeHoistingContext::None)
143                                .await?
144                                .map(|i| i.into_module_namespace_ident().unwrap())
145                                .map(|(i, ctx)| (i, ctx.unwrap_or_default().into()))
146                        } else {
147                            None
148                        }
149                    }
150                    ReferencedAsset::External(..) => None,
151                    ReferencedAsset::NonPlaceable(_)
152                    | ReferencedAsset::None
153                    | ReferencedAsset::Empty
154                    | 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(async |r| {
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}