swc_bundler/modules/sort/
chunk.rs1use std::{collections::VecDeque, iter::from_fn, mem::take, time::Instant};
2
3use indexmap::IndexSet;
4use petgraph::EdgeDirection::Outgoing;
5use rustc_hash::{FxBuildHasher, FxHashSet};
6use swc_common::{sync::Lrc, SourceMap, SyntaxContext};
7use swc_ecma_ast::*;
8use swc_ecma_utils::prepend_stmts;
9
10use super::stmt::sort_stmts;
11use crate::{dep_graph::ModuleGraph, modules::Modules, ModuleId};
12
13#[derive(Debug)]
15pub(super) struct Chunk {
16 pub stmts: Vec<ModuleItem>,
17}
18
19impl Modules {
20 #[allow(clippy::ptr_arg)]
22 pub(super) fn take_chunks(
23 &mut self,
24 entry_id: ModuleId,
25 graph: &ModuleGraph,
26 cycle: &Vec<Vec<ModuleId>>,
27 cm: &Lrc<SourceMap>,
28 ) -> Vec<Chunk> {
29 let injected_ctxt = self.injected_ctxt;
30 let mut chunks = Vec::new();
31
32 let mut modules = take(&mut self.modules);
33
34 for (id, module) in &mut modules {
35 if let Some(prepended) = self.prepended_stmts.remove(&*id) {
36 prepend_stmts(&mut module.body, prepended.into_iter());
37 }
38
39 if let Some(items) = self.appended_stmts.remove(&*id) {
40 module.body.extend(items);
41 }
42 }
43
44 chunks.extend(toposort_real_modules(
45 injected_ctxt,
46 modules,
47 entry_id,
48 graph,
49 cycle,
50 cm,
51 ));
52
53 chunks
54 }
55}
56
57fn toposort_real_modules<'a>(
59 injected_ctxt: SyntaxContext,
60 mut modules: Vec<(ModuleId, Module)>,
61 entry: ModuleId,
62 graph: &'a ModuleGraph,
63 cycles: &'a [Vec<ModuleId>],
64 cm: &Lrc<SourceMap>,
65) -> Vec<Chunk> {
66 let mut queue = modules.iter().map(|v| v.0).collect::<VecDeque<_>>();
67 queue.push_front(entry);
68
69 let mut chunks = Vec::new();
70
71 tracing::debug!(
72 "Topologically sorting modules based on the dependency graph: ({} items)",
73 modules.len()
74 );
75
76 #[cfg(not(target_arch = "wasm32"))]
77 let start = Instant::now();
78 let sorted_ids = toposort_real_module_ids(queue, graph, cycles).collect::<Vec<_>>();
79 #[cfg(not(target_arch = "wasm32"))]
80 let end = Instant::now();
81 #[cfg(not(target_arch = "wasm32"))]
82 tracing::debug!("Toposort of module ids took {:?}", end - start);
83 for ids in sorted_ids {
84 if ids.is_empty() {
85 continue;
86 }
87
88 let mut stmts = Vec::new();
89
90 for id in ids.iter().copied().rev() {
91 if let Some((_, module)) = modules.iter_mut().find(|(module_id, _)| *module_id == id) {
92 module
93 .body
94 .retain(|item| !matches!(item, ModuleItem::Stmt(Stmt::Empty(..))));
95 if module.body.is_empty() {
96 continue;
97 }
98
99 stmts.push(take(&mut module.body));
100 }
101 }
102
103 if stmts.is_empty() {
104 continue;
105 }
106
107 if ids.len() == 1 && graph.neighbors_directed(ids[0], Outgoing).count() == 0 {
109 chunks.push(Chunk {
110 stmts: stmts.into_iter().next().unwrap(),
111 });
112 continue;
113 }
114
115 let stmts = sort_stmts(injected_ctxt, stmts, cm);
116
117 chunks.push(Chunk { stmts })
128 }
129
130 chunks
131}
132
133fn cycles_for(
134 cycles: &[Vec<ModuleId>],
135 id: ModuleId,
136 checked: &mut Vec<ModuleId>,
137) -> IndexSet<ModuleId, FxBuildHasher> {
138 checked.push(id);
139 let mut v = cycles
140 .iter()
141 .filter(|v| v.contains(&id))
142 .flatten()
143 .copied()
144 .collect::<IndexSet<_, _>>();
145
146 let ids = v.clone();
147
148 for added in ids {
149 if checked.contains(&added) {
150 continue;
151 }
152 v.extend(cycles_for(cycles, added, checked));
153 }
154
155 v
156}
157
158fn toposort_real_module_ids<'a>(
159 mut queue: VecDeque<ModuleId>,
160 graph: &'a ModuleGraph,
161 cycles: &'a [Vec<ModuleId>],
162) -> impl 'a + Iterator<Item = Vec<ModuleId>> {
163 let mut done = FxHashSet::<ModuleId>::default();
164 let mut errorred = FxHashSet::<ModuleId>::default();
165
166 from_fn(move || {
167 while let Some(id) = queue.pop_front() {
168 if done.contains(&id) {
169 continue;
170 }
171
172 let deps = graph
175 .neighbors_directed(id, Outgoing)
176 .filter(|dep| !done.contains(dep))
177 .collect::<Vec<_>>();
178
179 if deps.is_empty() {
180 done.insert(id);
184 errorred.clear();
185 return Some(vec![id]);
186 }
187
188 let mut all_modules_in_circle = cycles_for(cycles, id, &mut Default::default());
191 all_modules_in_circle.reverse();
192
193 if all_modules_in_circle.is_empty() {
196 queue.push_front(id);
197
198 for dep in deps.iter().copied().rev() {
200 queue.push_front(dep);
201 }
202
203 continue;
204 }
205
206 let deps_of_circle = all_modules_in_circle
208 .iter()
209 .flat_map(|&id| {
210 graph
211 .neighbors_directed(id, Outgoing)
212 .filter(|dep| !done.contains(dep) && !all_modules_in_circle.contains(dep))
213 })
214 .collect::<Vec<_>>();
215
216 if !deps_of_circle.is_empty() {
219 if errorred.insert(id) {
220 queue.push_front(id);
221
222 for dep in deps_of_circle.iter().copied().rev() {
224 queue.push_front(dep);
225 }
226
227 continue;
228 }
229 tracing::info!("Using slow, fallback logic for topological sorting");
230 all_modules_in_circle.extend(deps_of_circle);
231 }
232
233 done.extend(all_modules_in_circle.iter().copied());
237 errorred.clear();
238 return Some(all_modules_in_circle.into_iter().collect());
239 }
240
241 None
242 })
243}