swc_bundler/modules/sort/
chunk.rs

1use 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/// The unit of sorting.
14#[derive(Debug)]
15pub(super) struct Chunk {
16    pub stmts: Vec<ModuleItem>,
17}
18
19impl Modules {
20    /// Modules with circular import relations will be in same chunk.
21    #[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
57/// Sort items topologically, while merging cycles as a
58fn 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        // Skip sorting statements if there is no import.
108        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        // print_hygiene(
118        //     &format!("after sort: {:?}", ids),
119        //     cm,
120        //     &Module {
121        //         span: DUMMY_SP,
122        //         body: stmts.clone(),
123        //         shebang: None,
124        //     },
125        // );
126
127        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            // dbg!(id);
173
174            let deps = graph
175                .neighbors_directed(id, Outgoing)
176                .filter(|dep| !done.contains(dep))
177                .collect::<Vec<_>>();
178
179            if deps.is_empty() {
180                // TODO: Add dependants
181
182                // Emit
183                done.insert(id);
184                errorred.clear();
185                return Some(vec![id]);
186            }
187
188            // dbg!(&deps);
189
190            let mut all_modules_in_circle = cycles_for(cycles, id, &mut Default::default());
191            all_modules_in_circle.reverse();
192
193            // dbg!(&all_modules_in_circle);
194
195            if all_modules_in_circle.is_empty() {
196                queue.push_front(id);
197
198                // This module does not have any circular imports.
199                for dep in deps.iter().copied().rev() {
200                    queue.push_front(dep);
201                }
202
203                continue;
204            }
205
206            // We need to handle dependencies of all circular modules.
207            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            // dbg!(&deps_of_circle);
217
218            if !deps_of_circle.is_empty() {
219                if errorred.insert(id) {
220                    queue.push_front(id);
221
222                    // Handle dependencies first.
223                    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            // TODO: Add dependants
234
235            // Emit
236            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}