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