rspack_plugin_javascript/plugin/
infer_async_modules_plugin.rs1use 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 continue;
130 }
131 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}