Skip to main content

rspack_plugin_javascript/plugin/
infer_async_modules_plugin.rs

1use rayon::prelude::*;
2use rspack_collections::{IdentifierLinkedSet, IdentifierMap, IdentifierSet};
3use rspack_core::{
4  AsyncModulesArtifact, Compilation, CompilationFinishModules, DependencyType, ExportsInfoArtifact,
5  Logger, ModuleGraph, Plugin, SideEffectsStateArtifact,
6  incremental::{IncrementalPasses, Mutation, Mutations},
7};
8use rspack_error::Result;
9use rspack_hook::{plugin, plugin_hook};
10
11#[plugin]
12#[derive(Debug, Default)]
13pub struct InferAsyncModulesPlugin;
14
15#[plugin_hook(CompilationFinishModules for InferAsyncModulesPlugin)]
16async fn finish_modules(
17  &self,
18  compilation: &Compilation,
19  async_modules_artifact: &mut AsyncModulesArtifact,
20  _exports_info_artifact: &mut ExportsInfoArtifact,
21  _side_effects_state_artifact: &mut SideEffectsStateArtifact,
22) -> Result<()> {
23  if let Some(mutations) = compilation
24    .incremental
25    .mutations_read(IncrementalPasses::FINISH_MODULES)
26  {
27    mutations
28      .iter()
29      .filter_map(|mutation| {
30        if let Mutation::ModuleRemove { module } = mutation {
31          Some(module)
32        } else {
33          None
34        }
35      })
36      .for_each(|module| {
37        async_modules_artifact.remove(module);
38      });
39  }
40
41  let module_graph = compilation.get_module_graph();
42  let mut sync_modules = IdentifierLinkedSet::default();
43  let mut async_modules = IdentifierLinkedSet::default();
44  for (module_identifier, module) in module_graph.modules() {
45    let build_meta = module.build_meta();
46    if build_meta.has_top_level_await() {
47      async_modules.insert(*module_identifier);
48    } else {
49      sync_modules.insert(*module_identifier);
50    }
51  }
52
53  let mut mutations = compilation
54    .incremental
55    .mutations_writable()
56    .then(Mutations::default);
57
58  set_sync_modules(
59    module_graph,
60    async_modules_artifact,
61    sync_modules,
62    &mut mutations,
63  );
64  set_async_modules(
65    module_graph,
66    async_modules_artifact,
67    async_modules,
68    &mut mutations,
69  );
70
71  if compilation
72    .incremental
73    .mutations_readable(IncrementalPasses::FINISH_MODULES)
74    && let Some(mutations) = &mutations
75  {
76    let logger = compilation.get_logger("rspack.incremental.finishModules");
77    logger.log(format!(
78      "{} modules are updated by set_async",
79      mutations.len()
80    ));
81  }
82
83  if let Some(mut compilation_mutations) = compilation.incremental.mutations_write()
84    && let Some(mutations) = mutations
85  {
86    compilation_mutations.extend(mutations);
87  }
88
89  Ok(())
90}
91
92fn set_sync_modules(
93  module_graph: &ModuleGraph,
94  async_modules_artifact: &mut AsyncModulesArtifact,
95  modules: IdentifierLinkedSet,
96  mutations: &mut Option<Mutations>,
97) {
98  let mut outgoing_connections = modules
99    .iter()
100    .par_bridge()
101    .map(|mid| {
102      (
103        *mid,
104        module_graph
105          .get_outgoing_connections(mid)
106          .filter_map(|con| module_graph.module_identifier_by_dependency_id(&con.dependency_id))
107          .filter(|&out| out != mid)
108          .copied()
109          .collect::<Vec<_>>(),
110      )
111    })
112    .collect::<IdentifierMap<_>>();
113
114  let mut queue = modules;
115  while let Some(module) = queue.pop_front() {
116    let module_outgoing_connections = outgoing_connections.entry(module).or_insert_with(|| {
117      module_graph
118        .get_outgoing_connections(&module)
119        .filter_map(|con| module_graph.module_identifier_by_dependency_id(&con.dependency_id))
120        .filter(|&out| &module != out)
121        .copied()
122        .collect::<Vec<_>>()
123    });
124    if module_outgoing_connections
125      .iter()
126      .any(|out| ModuleGraph::is_async(async_modules_artifact, out))
127    {
128      // We can't safely reset is_async to false if there are any outgoing module is async
129      continue;
130    }
131    // The module is_async = false will also decide its parent module is_async, so if the module is_async = false
132    // is not changed, this means its parent module will be not affected, so we stop the infer at here.
133    if ModuleGraph::set_async(async_modules_artifact, module, false) {
134      if let Some(mutations) = mutations {
135        mutations.add(Mutation::ModuleSetAsync { module });
136      }
137      module_graph
138        .get_incoming_connections(&module)
139        .filter(|con| {
140          let dep = module_graph.dependency_by_id(&con.dependency_id);
141          matches!(
142            dep.dependency_type(),
143            DependencyType::EsmImport | DependencyType::EsmExportImport | DependencyType::Provided
144          )
145        })
146        .for_each(|con| {
147          if let Some(id) = con.original_module_identifier {
148            queue.insert(id);
149          }
150        });
151    }
152  }
153}
154
155fn set_async_modules(
156  module_graph: &ModuleGraph,
157  async_modules_artifact: &mut AsyncModulesArtifact,
158  modules: IdentifierLinkedSet,
159  mutations: &mut Option<Mutations>,
160) {
161  let mut queue = modules;
162  let mut visited: IdentifierSet = queue.iter().copied().collect();
163
164  while let Some(module) = queue.pop_front() {
165    if ModuleGraph::set_async(async_modules_artifact, module, true)
166      && let Some(mutations) = mutations
167    {
168      mutations.add(Mutation::ModuleSetAsync { module });
169    }
170    module_graph
171      .get_incoming_connections(&module)
172      .filter(|con| {
173        let dep = module_graph.dependency_by_id(&con.dependency_id);
174        matches!(
175          dep.dependency_type(),
176          DependencyType::EsmImport | DependencyType::EsmExportImport | DependencyType::Provided
177        )
178      })
179      .for_each(|con| {
180        if let Some(id) = con.original_module_identifier
181          && visited.insert(id)
182        {
183          queue.insert(id);
184        }
185      });
186  }
187}
188
189impl Plugin for InferAsyncModulesPlugin {
190  fn name(&self) -> &'static str {
191    "InferAsyncModulesPlugin"
192  }
193
194  fn apply(&self, ctx: &mut rspack_core::ApplyContext<'_>) -> Result<()> {
195    ctx
196      .compilation_hooks
197      .finish_modules
198      .tap(finish_modules::new(self));
199    Ok(())
200  }
201}