swc_bundler/bundler/chunk/
mod.rs

1use std::time::Instant;
2
3use anyhow::{Context, Error};
4#[cfg(feature = "rayon")]
5use rayon::iter::ParallelIterator;
6use rustc_hash::FxHashMap;
7
8use super::{load::TransformedModule, Bundler};
9use crate::{
10    bundler::chunk::merge::Ctx, load::Load, resolve::Resolve, util::IntoParallelIterator, Bundle,
11};
12
13mod cjs;
14mod computed_key;
15mod merge;
16mod plan;
17
18impl<L, R> Bundler<'_, L, R>
19where
20    L: Load,
21    R: Resolve,
22{
23    /// `entries` - Entry modules (provided by user) by it's basename.
24    ///
25    /// # How it works
26    ///
27    /// For first, we load all dependencies and determine all entries.
28    pub(super) fn chunk(
29        &self,
30        entries: FxHashMap<String, TransformedModule>,
31    ) -> Result<Vec<Bundle>, Error> {
32        #[cfg(not(target_arch = "wasm32"))]
33        let start = Instant::now();
34        let (plan, graph, cycles) = self.determine_entries(entries).context("failed to plan")?;
35        #[cfg(not(target_arch = "wasm32"))]
36        let dur = Instant::now() - start;
37        #[cfg(not(target_arch = "wasm32"))]
38        tracing::debug!("Dependency analysis took {:?}", dur);
39
40        if cfg!(debug_assertions) {
41            for (i, id1) in plan.all.iter().enumerate() {
42                for (j, id2) in plan.all.iter().enumerate() {
43                    if i == j {
44                        continue;
45                    }
46
47                    debug_assert_ne!(
48                        id1, id2,
49                        "Dependency analysis created duplicate entries: {id1:?}"
50                    )
51                }
52            }
53        }
54
55        let ctx = Ctx {
56            graph,
57            cycles,
58            transitive_remap: Default::default(),
59            export_stars_in_wrapped: Default::default(),
60        };
61
62        #[cfg(not(target_arch = "wasm32"))]
63        let start = Instant::now();
64        let mut all = (&*plan.all)
65            .into_par_iter()
66            .map(|id| -> Result<_, Error> {
67                self.run(|| {
68                    // TODO: is_entry should be false if it's dep of other entry.
69                    let is_entry = plan.entries.contains_key(id);
70                    let module = self.get_for_merging(&ctx, *id, is_entry)?;
71
72                    Ok((*id, module))
73                })
74            })
75            .collect::<Result<FxHashMap<_, _>, _>>()?;
76
77        #[cfg(not(target_arch = "wasm32"))]
78        let dur = Instant::now() - start;
79        #[cfg(not(target_arch = "wasm32"))]
80        tracing::debug!("Module preparation took {:?}", dur);
81
82        let entries = all
83            .iter()
84            .filter_map(|(id, module)| {
85                if plan.entries.contains_key(id) {
86                    return Some((*id, module.clone()));
87                }
88                None
89            })
90            .collect::<Vec<_>>();
91
92        let merged: Vec<_> = if entries.len() == 1 {
93            entries
94                .into_iter()
95                .map(|(id, mut entry)| {
96                    self.merge_into_entry(&ctx, id, &mut entry, &mut all);
97
98                    tracing::debug!("Merged `{}` and it's dep into an entry", id);
99
100                    (id, entry)
101                })
102                .map(|(id, module)| {
103                    let kind = plan
104                        .entries
105                        .get(&id)
106                        .unwrap_or_else(|| {
107                            unreachable!("Plan does not contain bundle kind for {:?}", id)
108                        })
109                        .clone();
110                    Bundle {
111                        kind,
112                        id,
113                        module: module.into(),
114                    }
115                })
116                .collect()
117        } else {
118            entries
119                .into_iter()
120                .map(|(id, mut entry)| {
121                    let mut a = all.clone();
122                    self.merge_into_entry(&ctx, id, &mut entry, &mut a);
123
124                    tracing::debug!("Merged `{}` and it's dep into an entry", id);
125
126                    (id, entry)
127                })
128                .map(|(id, module)| {
129                    let kind = plan
130                        .entries
131                        .get(&id)
132                        .unwrap_or_else(|| {
133                            unreachable!("Plan does not contain bundle kind for {:?}", id)
134                        })
135                        .clone();
136                    Bundle {
137                        kind,
138                        id,
139                        module: module.into(),
140                    }
141                })
142                .collect()
143        };
144
145        Ok(merged)
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use swc_common::FileName;
152
153    use super::*;
154    use crate::bundler::tests::suite;
155
156    #[test]
157    fn cjs_chunk() {
158        suite()
159            .file(
160                "main.js",
161                "
162                require('./a');
163                require('./b');
164                ",
165            )
166            .file("a.js", "require('./common')")
167            .file("b.js", "require('./common')")
168            .file("common.js", r#"console.log('foo')"#)
169            .run(|t| {
170                let module = t
171                    .bundler
172                    .load_transformed(&FileName::Real("main.js".into()))?
173                    .unwrap();
174                let mut entries = FxHashMap::default();
175                entries.insert("main.js".to_string(), module);
176
177                let chunked = t.bundler.chunk(entries)?;
178                assert_eq!(chunked.len(), 1);
179
180                Ok(())
181            });
182    }
183}