turbopack_ecmascript/tree_shake/
merge.rs

1use anyhow::Error;
2use rustc_hash::FxHashSet;
3use swc_core::ecma::{
4    ast::{Module, ModuleDecl, ModuleItem},
5    atoms::Atom,
6};
7
8use super::{PartId, graph::find_turbopack_part_id_in_asserts};
9
10/// A loader used to merge module items after splitting.
11pub trait Load {
12    /// Loads a module while returning [None] if the module is already loaded.
13    fn load(&mut self, uri: &str, part_id: u32) -> Result<Option<Module>, Error>;
14}
15
16/// A module merger.
17///
18/// This ensures that a module is loaded only once.
19pub struct Merger<L>
20where
21    L: Load,
22{
23    loader: L,
24
25    done: FxHashSet<(Atom, u32)>,
26}
27
28impl<L> Merger<L>
29where
30    L: Load,
31{
32    /// Creates a module merger.
33    pub fn new(loader: L) -> Self {
34        Merger {
35            loader,
36            done: Default::default(),
37        }
38    }
39
40    /// Merges module content by appending the content of imported modules. This
41    /// is recursive, so a single call is enoguh.
42    pub fn merge_recursively(&mut self, entry: Module) -> Result<Module, Error> {
43        let mut content = vec![];
44        let mut extra_body = vec![];
45
46        for stmt in entry.body {
47            match stmt {
48                ModuleItem::ModuleDecl(ModuleDecl::Import(import)) => {
49                    // Try to prepend the content of module
50
51                    let part_id = import
52                        .with
53                        .as_deref()
54                        .and_then(find_turbopack_part_id_in_asserts);
55
56                    if let Some(PartId::Internal(part_id, _)) = part_id {
57                        if self.done.insert((import.src.value.clone(), part_id)) {
58                            if let Some(dep) = self.loader.load(&import.src.value, part_id)? {
59                                let mut dep = self.merge_recursively(dep)?;
60
61                                extra_body.append(&mut dep.body);
62                            } else {
63                                content.push(ModuleItem::ModuleDecl(ModuleDecl::Import(import)));
64                            }
65                        } else {
66                            // Remove import
67                        }
68                    } else {
69                        // Preserve normal imports
70                        content.push(ModuleItem::ModuleDecl(ModuleDecl::Import(import)));
71                    }
72                }
73                _ => extra_body.push(stmt),
74            }
75        }
76
77        content.append(&mut extra_body);
78
79        Ok(Module {
80            span: entry.span,
81            body: content,
82            shebang: entry.shebang,
83        })
84    }
85}