turbopack_core/module_graph/
async_module_info.rs

1use anyhow::Result;
2use rustc_hash::FxHashSet;
3use turbo_tasks::{ResolvedVc, TryJoinIterExt, Vc};
4
5use crate::{
6    module::{Module, Modules},
7    module_graph::{GraphTraversalAction, ModuleGraph, SingleModuleGraph},
8};
9
10#[turbo_tasks::value(transparent)]
11pub struct ModulesSet(FxHashSet<ResolvedVc<Box<dyn Module>>>);
12
13/// This lists all the modules that are async (self or transitively because they reference another
14/// module in this list).
15#[turbo_tasks::value(transparent)]
16pub struct AsyncModulesInfo(FxHashSet<ResolvedVc<Box<dyn Module>>>);
17
18#[turbo_tasks::value_impl]
19impl AsyncModulesInfo {
20    #[turbo_tasks::function]
21    pub fn is_async(&self, module: ResolvedVc<Box<dyn Module>>) -> Vc<bool> {
22        Vc::cell(self.0.contains(&module))
23    }
24
25    #[turbo_tasks::function]
26    pub async fn is_async_multiple(&self, modules: ResolvedVc<Modules>) -> Result<Vc<ModulesSet>> {
27        Ok(Vc::cell(
28            modules
29                .await?
30                .iter()
31                .copied()
32                .filter(|m| self.0.contains(m))
33                .collect(),
34        ))
35    }
36}
37
38#[turbo_tasks::function(operation)]
39pub async fn compute_async_module_info(
40    graph: ResolvedVc<ModuleGraph>,
41) -> Result<Vc<AsyncModulesInfo>> {
42    // Layout segment optimization, we can individually compute the async modules for each graph.
43    let mut result: Vc<AsyncModulesInfo> = Vc::cell(Default::default());
44    for g in &graph.await?.graphs {
45        result = compute_async_module_info_single(**g, result);
46    }
47    Ok(result)
48}
49
50#[turbo_tasks::function]
51async fn compute_async_module_info_single(
52    graph: Vc<SingleModuleGraph>,
53    parent_async_modules: Vc<AsyncModulesInfo>,
54) -> Result<Vc<AsyncModulesInfo>> {
55    let parent_async_modules = parent_async_modules.await?;
56    let graph = graph.await?;
57
58    let self_async_modules = graph
59        .iter_nodes()
60        .map(async |node| Ok((node.module, *node.module.is_self_async().await?)))
61        .try_join()
62        .await?
63        .into_iter()
64        .flat_map(|(k, v)| v.then_some(k))
65        .chain(parent_async_modules.iter().copied())
66        .collect::<FxHashSet<_>>();
67
68    // To determine which modules are async, we need to propagate the self-async flag to all
69    // importers, which is done using a postorder traversal of the graph.
70    //
71    // This however doesn't cover cycles of async modules, which are handled by determining all
72    // strongly-connected components, and then marking all the whole SCC as async if one of the
73    // modules in the SCC is async.
74
75    let mut async_modules = self_async_modules;
76    graph.traverse_edges_from_entries_topological(
77        graph.entry_modules(),
78        &mut (),
79        |_, _, _| Ok(GraphTraversalAction::Continue),
80        |parent_info, module, _| {
81            let Some((parent_module, chunking_type)) = parent_info else {
82                // An entry module
83                return;
84            };
85            let module = module.module();
86            let parent_module = parent_module.module;
87
88            if chunking_type.is_inherit_async() && async_modules.contains(&module) {
89                async_modules.insert(parent_module);
90            }
91        },
92    )?;
93
94    graph.traverse_cycles(
95        |ty| ty.is_inherit_async(),
96        |cycle| {
97            if cycle
98                .iter()
99                .any(|node| async_modules.contains(&node.module))
100            {
101                for &node in cycle {
102                    async_modules.insert(node.module);
103                }
104            }
105        },
106    );
107
108    Ok(Vc::cell(async_modules))
109}