swc_bundler/bundler/
load.rs

1#![allow(dead_code)]
2
3use anyhow::{Context, Error};
4use is_macro::Is;
5#[cfg(feature = "rayon")]
6use rayon::iter::ParallelIterator;
7use swc_atoms::atom;
8use swc_common::{
9    sync::{Lock, Lrc},
10    FileName, SourceFile, SyntaxContext,
11};
12use swc_ecma_ast::{
13    CallExpr, Callee, Expr, Ident, ImportDecl, ImportSpecifier, MemberExpr, MemberProp, Module,
14    ModuleDecl, ModuleExportName, Str, SuperProp, SuperPropExpr,
15};
16use swc_ecma_transforms_base::resolver;
17use swc_ecma_visit::{
18    noop_visit_mut_type, noop_visit_type, Visit, VisitMut, VisitMutWith, VisitWith,
19};
20
21use super::{export::Exports, helpers::Helpers, Bundler};
22use crate::{
23    bundler::{export::RawExports, import::RawImports},
24    id::{Id, ModuleId},
25    load::ModuleData,
26    util,
27    util::IntoParallelIterator,
28    Load, Resolve,
29};
30/// Module after applying transformations.
31#[derive(Debug, Clone)]
32pub(crate) struct TransformedModule {
33    pub id: ModuleId,
34    pub fm: Lrc<SourceFile>,
35    pub module: Lrc<Module>,
36    pub imports: Lrc<Imports>,
37    pub exports: Lrc<Exports>,
38
39    /// If false, the module will be wrapped with a small helper function.
40    pub is_es6: bool,
41
42    /// Used helpers
43    pub helpers: Lrc<Helpers>,
44
45    pub swc_helpers: Lrc<Lock<swc_ecma_transforms_base::helpers::HelperData>>,
46
47    local_ctxt: SyntaxContext,
48    export_ctxt: SyntaxContext,
49}
50
51impl TransformedModule {
52    /// [SyntaxContext] for exported items.
53    pub fn export_ctxt(&self) -> SyntaxContext {
54        self.export_ctxt
55    }
56
57    /// Top level contexts.
58    pub fn local_ctxt(&self) -> SyntaxContext {
59        self.local_ctxt
60    }
61}
62
63impl<L, R> Bundler<'_, L, R>
64where
65    L: Load,
66    R: Resolve,
67{
68    /// Phase 1 (discovery)
69    ///
70    /// We apply transforms at this phase to make cache efficient.
71    /// As we cache in this phase, changing dependency does not affect cache.
72    pub(super) fn load_transformed(
73        &self,
74        file_name: &FileName,
75    ) -> Result<Option<TransformedModule>, Error> {
76        self.run(|| {
77            tracing::trace!("load_transformed: ({})", file_name);
78
79            // In case of common module
80            if let Some(cached) = self.scope.get_module_by_path(file_name) {
81                tracing::debug!("Cached: {}", file_name);
82                return Ok(Some(cached));
83            }
84
85            let (_, data) = self.load(file_name).context("Bundler.load() failed")?;
86            let (v, mut files) = self
87                .analyze(file_name, data)
88                .context("failed to analyze module")?;
89            files.dedup_by_key(|v| v.1.clone());
90
91            tracing::debug!(
92                "({:?}, {:?}, {:?}) Storing module: {}",
93                v.id,
94                v.local_ctxt(),
95                v.export_ctxt(),
96                file_name
97            );
98            self.scope.store_module(v.clone());
99
100            // Load dependencies and store them in the `Scope`
101            let results = files
102                .into_par_iter()
103                .map(|(_src, path)| {
104                    tracing::trace!("loading dependency: {}", path);
105                    self.load_transformed(&path)
106                })
107                .collect::<Vec<_>>();
108
109            // Do tasks in parallel, and then wait for result
110            for result in results {
111                result?;
112            }
113
114            Ok(Some(v))
115        })
116    }
117
118    fn load(&self, file_name: &FileName) -> Result<(ModuleId, ModuleData), Error> {
119        self.run(|| {
120            let (module_id, _, _) = self.scope.module_id_gen.gen(file_name);
121
122            let data = self
123                .loader
124                .load(file_name)
125                .with_context(|| format!("Bundler.loader.load({file_name}) failed"))?;
126            self.scope.mark_as_loaded(module_id);
127            Ok((module_id, data))
128        })
129    }
130
131    /// This methods returns [Source]s which should be loaded.
132    fn analyze(
133        &self,
134        file_name: &FileName,
135        mut data: ModuleData,
136    ) -> Result<(TransformedModule, Vec<(Source, Lrc<FileName>)>), Error> {
137        self.run(|| {
138            tracing::trace!("transform_module({})", data.fm.name);
139            let (id, local_mark, export_mark) = self.scope.module_id_gen.gen(file_name);
140
141            data.module.visit_mut_with(&mut ClearMark);
142
143            data.module
144                .visit_mut_with(&mut resolver(self.unresolved_mark, local_mark, false));
145
146            // {
147            //     let code = self
148            //         .swc
149            //         .print(
150            //             &module.clone().fold_with(&mut HygieneVisualizer),
151            //             SourceMapsConfig::Bool(false),
152            //             None,
153            //             false,
154            //         )
155            //         .unwrap()
156            //         .code;
157            //
158            //     println!("Resolved:\n{}\n\n", code);
159            // }
160
161            let imports = self.extract_import_info(file_name, &mut data.module, local_mark);
162
163            // {
164            //     let code = self
165            //         .swc
166            //         .print(
167            //             &module.clone().fold_with(&mut HygieneVisualizer),
168            //             SourceMapsConfig::Bool(false),
169            //             None,
170            //             false,
171            //         )
172            //         .unwrap()
173            //         .code;
174            //
175            //     println!("After imports:\n{}\n", code,);
176            // }
177
178            let exports = self.extract_export_info(
179                file_name,
180                &mut data.module,
181                SyntaxContext::empty().apply_mark(export_mark),
182            );
183
184            let is_es6 = if !self.config.require {
185                true
186            } else {
187                let mut v = Es6ModuleDetector {
188                    forced_es6: false,
189                    found_other: false,
190                };
191                data.module.visit_with(&mut v);
192                v.forced_es6 || !v.found_other
193            };
194
195            let (imports, exports) = util::join(
196                || self.resolve_imports(file_name, imports),
197                || self.resolve_exports(file_name, exports),
198            );
199            let (imports, mut import_files) = imports?;
200            let (exports, reexport_files) = exports?;
201            import_files.extend(reexport_files);
202
203            Ok((
204                TransformedModule {
205                    id,
206                    fm: data.fm,
207                    module: Lrc::new(data.module),
208                    imports: Lrc::new(imports),
209                    exports: Lrc::new(exports),
210                    is_es6,
211                    helpers: Default::default(),
212                    swc_helpers: Lrc::new(Lock::new(data.helpers.data())),
213                    local_ctxt: SyntaxContext::empty().apply_mark(local_mark),
214                    export_ctxt: SyntaxContext::empty().apply_mark(export_mark),
215                },
216                import_files,
217            ))
218        })
219    }
220
221    /// Resolve re-exports.
222    fn resolve_exports(
223        &self,
224        base: &FileName,
225        raw: RawExports,
226    ) -> Result<(Exports, Vec<(Source, Lrc<FileName>)>), Error> {
227        self.run(|| {
228            tracing::trace!("resolve_exports({})", base);
229            let mut files = Vec::new();
230
231            let mut exports = Exports::default();
232
233            let items = raw
234                .items
235                .into_par_iter()
236                .map(|(src, ss)| -> Result<_, Error> {
237                    self.run(|| {
238                        let info = match src {
239                            Some(src) => {
240                                let name = self.resolve(base, &src.value.to_string_lossy())?;
241                                let (id, local_mark, export_mark) =
242                                    self.scope.module_id_gen.gen(&name);
243                                Some((id, local_mark, export_mark, name, src))
244                            }
245                            None => None,
246                        };
247
248                        Ok((info, ss))
249                    })
250                })
251                .collect::<Vec<_>>();
252
253            for res in items {
254                let (info, specifiers) = res?;
255
256                match info {
257                    None => exports.items.extend(specifiers),
258                    Some((id, local_mark, export_mark, name, src)) => {
259                        //
260                        let src = Source {
261                            is_loaded_synchronously: true,
262                            is_unconditional: false,
263                            module_id: id,
264                            local_ctxt: SyntaxContext::empty().apply_mark(local_mark),
265                            export_ctxt: SyntaxContext::empty().apply_mark(export_mark),
266                            src,
267                        };
268                        exports.reexports.push((src.clone(), specifiers));
269                        files.push((src, name));
270                    }
271                }
272            }
273
274            Ok((exports, files))
275        })
276    }
277
278    /// Resolve dependencies
279    fn resolve_imports(
280        &self,
281        base: &FileName,
282        info: RawImports,
283    ) -> Result<(Imports, Vec<(Source, Lrc<FileName>)>), Error> {
284        self.run(|| {
285            tracing::trace!("resolve_imports({})", base);
286            let mut files = Vec::new();
287
288            let mut merged = Imports::default();
289            let RawImports {
290                imports,
291                lazy_imports,
292                dynamic_imports,
293                forced_ns,
294            } = info;
295
296            let loaded = imports
297                .into_par_iter()
298                .map(|v| (v, false, true))
299                .chain(lazy_imports.into_par_iter().map(|v| (v, false, false)))
300                .chain(dynamic_imports.into_par_iter().map(|src| {
301                    (
302                        ImportDecl {
303                            span: src.span,
304                            specifiers: Vec::new(),
305                            src: Box::new(src),
306                            type_only: false,
307                            with: None,
308                            phase: Default::default(),
309                        },
310                        true,
311                        false,
312                    )
313                }))
314                .map(|(decl, dynamic, unconditional)| -> Result<_, Error> {
315                    self.run(|| {
316                        //
317                        let file_name = self.resolve(base, &decl.src.value.to_string_lossy())?;
318                        let (id, local_mark, export_mark) =
319                            self.scope.module_id_gen.gen(&file_name);
320
321                        Ok((
322                            id,
323                            local_mark,
324                            export_mark,
325                            file_name,
326                            decl,
327                            dynamic,
328                            unconditional,
329                        ))
330                    })
331                })
332                .collect::<Vec<_>>();
333
334            for res in loaded {
335                // TODO: Report error and proceed instead of returning an error
336                let (id, local_mark, export_mark, file_name, decl, is_dynamic, is_unconditional) =
337                    res?;
338
339                let src = Source {
340                    is_loaded_synchronously: !is_dynamic,
341                    is_unconditional,
342                    module_id: id,
343                    local_ctxt: SyntaxContext::empty().apply_mark(local_mark),
344                    export_ctxt: SyntaxContext::empty().apply_mark(export_mark),
345                    src: *decl.src,
346                };
347                files.push((src.clone(), file_name));
348
349                // TODO: Handle rename
350                let mut specifiers = Vec::new();
351                for s in decl.specifiers {
352                    match s {
353                        ImportSpecifier::Named(s) => {
354                            let imported = match s.imported {
355                                Some(ModuleExportName::Ident(ident)) => Some(ident),
356                                Some(ModuleExportName::Str(..)) => {
357                                    unimplemented!("module string names unimplemented")
358                                }
359                                _ => None,
360                            };
361                            specifiers.push(Specifier::Specific {
362                                local: s.local.into(),
363                                alias: imported.map(From::from),
364                            })
365                        }
366                        ImportSpecifier::Default(s) => specifiers.push(Specifier::Specific {
367                            local: s.local.into(),
368                            alias: Some(Id::new(atom!("default"), SyntaxContext::empty())),
369                        }),
370                        ImportSpecifier::Namespace(s) => {
371                            specifiers.push(Specifier::Namespace {
372                                local: s.local.into(),
373                                all: forced_ns.contains(&src.src.value),
374                            });
375                        }
376                        #[cfg(swc_ast_unknown)]
377                        _ => panic!("unable to access unknown nodes"),
378                    }
379                }
380
381                merged.specifiers.push((src, specifiers));
382            }
383
384            Ok((merged, files))
385        })
386    }
387}
388
389#[derive(Debug, Default)]
390pub(crate) struct Imports {
391    /// If imported ids are empty, it is a side-effect import.
392    pub specifiers: Vec<(Source, Vec<Specifier>)>,
393}
394
395/// Clone is relatively cheap
396#[derive(Debug, Clone, Is)]
397pub(crate) enum Specifier {
398    Specific {
399        local: Id,
400        alias: Option<Id>,
401    },
402    Namespace {
403        local: Id,
404        /// True for `import * as foo from 'foo'; foo[computedKey()]`
405        all: bool,
406    },
407}
408
409#[derive(Debug, Clone, PartialEq, Eq, Hash)]
410pub(crate) struct Source {
411    pub is_loaded_synchronously: bool,
412    pub is_unconditional: bool,
413
414    pub module_id: ModuleId,
415    pub local_ctxt: SyntaxContext,
416    pub export_ctxt: SyntaxContext,
417
418    // Clone is relatively cheap, thanks to hstr.
419    pub src: Str,
420}
421
422struct Es6ModuleDetector {
423    /// If import statement or export is detected, it's an es6 module regardless
424    /// of other codes.
425    forced_es6: bool,
426    /// True if other module system is detected.
427    found_other: bool,
428}
429
430impl Visit for Es6ModuleDetector {
431    noop_visit_type!();
432
433    fn visit_call_expr(&mut self, e: &CallExpr) {
434        e.visit_children_with(self);
435
436        match &e.callee {
437            Callee::Expr(e) => {
438                if let Expr::Ident(Ident { sym: _require, .. }) = &**e {
439                    self.found_other = true;
440                }
441            }
442            Callee::Super(_) | Callee::Import(_) => {}
443            #[cfg(swc_ast_unknown)]
444            _ => panic!("unable to access unknown nodes"),
445        }
446    }
447
448    fn visit_member_expr(&mut self, e: &MemberExpr) {
449        e.obj.visit_with(self);
450
451        if let MemberProp::Computed(c) = &e.prop {
452            c.visit_with(self);
453        }
454
455        if let Expr::Ident(i) = &*e.obj {
456            // TODO: Check syntax context (Check if marker is the global mark)
457            if i.sym == *"module" {
458                self.found_other = true;
459            }
460
461            if i.sym == *"exports" {
462                self.found_other = true;
463            }
464        }
465    }
466
467    fn visit_super_prop_expr(&mut self, e: &SuperPropExpr) {
468        if let SuperProp::Computed(c) = &e.prop {
469            c.visit_with(self);
470        }
471    }
472
473    fn visit_module_decl(&mut self, decl: &ModuleDecl) {
474        match decl {
475            ModuleDecl::Import(_)
476            | ModuleDecl::ExportDecl(_)
477            | ModuleDecl::ExportNamed(_)
478            | ModuleDecl::ExportDefaultDecl(_)
479            | ModuleDecl::ExportDefaultExpr(_)
480            | ModuleDecl::ExportAll(_) => {
481                self.forced_es6 = true;
482            }
483
484            ModuleDecl::TsImportEquals(_) => {}
485            ModuleDecl::TsExportAssignment(_) => {}
486            ModuleDecl::TsNamespaceExport(_) => {}
487            #[cfg(swc_ast_unknown)]
488            _ => panic!("unable to access unknown nodes"),
489        }
490    }
491}
492
493#[derive(Clone, Copy)]
494struct ClearMark;
495impl VisitMut for ClearMark {
496    noop_visit_mut_type!(fail);
497
498    fn visit_mut_ident(&mut self, ident: &mut Ident) {
499        ident.ctxt = SyntaxContext::empty();
500    }
501}