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, SingleModuleGraphNode},
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, root)]
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, root)]
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        .iter_reachable_nodes()?
48        .map(async |node| {
49            Ok(match node {
50                SingleModuleGraphNode::Module(node) => node.is_self_async().await?.then_some(*node),
51                SingleModuleGraphNode::VisitedModule { idx: _, module } => {
52                    // If a module is async in the parent then we need to mark reverse dependencies
53                    // async in this graph as well.
54                    parent_async_modules
55                        .as_ref()
56                        .is_some_and(|set| set.contains(module))
57                        .then_some(*module)
58                }
59            })
60        })
61        .try_flat_join()
62        .await?;
63
64    // To determine which modules are async, we need to propagate the self-async flag to all
65    // importers, which is done using a reverse traversal over the graph
66    // Because we walk edges in the reverse direction we can trivially handle things like cycles
67    // without actually computing them.
68    let mut async_modules = FxHashSet::default();
69    async_modules.extend(self_async_modules.iter());
70
71    graph.traverse_edges_reverse_dfs(
72        self_async_modules,
73        &mut (),
74        // child is the previously visited module which must be async
75        // parent is a new module that depends on it
76        |child, parent, _state| {
77            Ok(if let Some((_, edge)) = child {
78                if edge.chunking_type.is_inherit_async() {
79                    async_modules.insert(parent);
80                    GraphTraversalAction::Continue
81                } else {
82                    // Wrong edge type to follow
83                    GraphTraversalAction::Exclude
84                }
85            } else {
86                // These are our entry points, just continue
87                GraphTraversalAction::Continue
88            })
89        },
90        |_, _, _| Ok(()),
91    )?;
92
93    // Accumulate the parent modules at the end. Not all parent async modules were in this graph
94    async_modules.extend(parent_async_modules.into_iter().flatten());
95
96    Ok(Vc::cell(async_modules))
97}