Skip to main content

turbopack_core/module_graph/
async_module_info.rs

1use anyhow::{Context, Result};
2use rustc_hash::FxHashSet;
3use turbo_tasks::{OperationVc, ResolvedVc, TryFlatJoinIterExt, Vc};
4
5use crate::{
6    module::Module,
7    module_graph::{GraphTraversalAction, ModuleGraph, ModuleGraphLayer},
8};
9
10/// This lists all the modules that are async (self or transitively because they reference another
11/// module in this list).
12#[turbo_tasks::value(transparent, cell = "keyed")]
13pub struct AsyncModulesInfo(FxHashSet<ResolvedVc<Box<dyn Module>>>);
14
15impl AsyncModulesInfo {
16    pub async fn is_async(self: Vc<Self>, module: ResolvedVc<Box<dyn Module>>) -> Result<bool> {
17        self.contains_key(&module).await
18    }
19}
20
21#[turbo_tasks::function(operation)]
22pub async fn compute_async_module_info(
23    graphs: ResolvedVc<ModuleGraph>,
24) -> Result<Vc<AsyncModulesInfo>> {
25    // Layout segment optimization, we can individually compute the async modules for each graph.
26    let mut result = None;
27    for graph in graphs.iter_graphs().await? {
28        result = Some(compute_async_module_info_single(*graph, result));
29    }
30    Ok(result
31        .context("There must be at least one single graph in the module graph")?
32        .connect())
33}
34
35#[turbo_tasks::function(operation)]
36async fn compute_async_module_info_single(
37    graph: OperationVc<ModuleGraphLayer>,
38    parent_async_modules: Option<OperationVc<AsyncModulesInfo>>,
39) -> Result<Vc<AsyncModulesInfo>> {
40    let parent_async_modules = if let Some(parent_async_modules) = parent_async_modules {
41        Some(parent_async_modules.read_strongly_consistent().await?)
42    } else {
43        None
44    };
45    let graph = graph.read_strongly_consistent().await?;
46    let self_async_modules = graph
47        .enumerate_nodes()
48        .map(async |(_, node)| {
49            Ok(match node {
50                super::SingleModuleGraphNode::Module(node) => {
51                    node.is_self_async().await?.then_some(*node)
52                }
53                super::SingleModuleGraphNode::VisitedModule { idx: _, module } => {
54                    // If a module is async in the parent then we need to mark reverse dependencies
55                    // async in this graph as well.
56                    parent_async_modules
57                        .as_ref()
58                        .is_some_and(|set| set.contains(module))
59                        .then_some(*module)
60                }
61            })
62        })
63        .try_flat_join()
64        .await?;
65
66    // To determine which modules are async, we need to propagate the self-async flag to all
67    // importers, which is done using a reverse traversal over the graph
68    // Because we walk edges in the reverse direction we can trivially handle things like cycles
69    // without actually computing them.
70    let mut async_modules = FxHashSet::default();
71    async_modules.extend(self_async_modules.iter());
72
73    graph.traverse_edges_reverse_dfs(
74        self_async_modules,
75        &mut (),
76        // child is the previously visited module which must be async
77        // parent is a new module that depends on it
78        |child, parent, _state| {
79            Ok(if let Some((_, edge)) = child {
80                if edge.chunking_type.is_inherit_async() {
81                    async_modules.insert(parent);
82                    GraphTraversalAction::Continue
83                } else {
84                    // Wrong edge type to follow
85                    GraphTraversalAction::Exclude
86                }
87            } else {
88                // These are our entry points, just continue
89                GraphTraversalAction::Continue
90            })
91        },
92        |_, _, _| Ok(()),
93    )?;
94
95    // Accumulate the parent modules at the end. Not all parent async modules were in this graph
96    async_modules.extend(parent_async_modules.into_iter().flatten());
97
98    Ok(Vc::cell(async_modules))
99}