use std::collections::HashMap;
use anyhow::{Context, Error};
use swc_atoms::JsWord;
use swc_common::{
collections::AHashMap, sync::Lrc, FileName, Globals, Mark, SourceMap, SyntaxContext, GLOBALS,
};
use swc_ecma_ast::Module;
use self::scope::Scope;
use crate::{Hook, Load, ModuleId, Resolve};
mod chunk;
mod export;
mod finalize;
mod helpers;
mod import;
mod keywords;
mod load;
mod optimize;
mod scope;
#[cfg(test)]
pub(crate) mod tests;
#[derive(Debug, Default)]
pub struct Config {
pub require: bool,
pub disable_inliner: bool,
pub disable_hygiene: bool,
pub disable_fixer: bool,
pub disable_dce: bool,
pub external_modules: Vec<JsWord>,
pub module: ModuleType,
}
#[derive(Debug, PartialEq, Eq, Hash, Default)]
pub enum ModuleType {
#[default]
Es,
Iife,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum BundleKind {
Named { name: String },
Dynamic,
Lib { name: String },
}
#[derive(Debug)]
pub struct Bundle {
pub kind: BundleKind,
pub id: ModuleId,
pub module: Module,
}
pub struct Bundler<'a, L, R>
where
L: Load,
R: Resolve,
{
config: Config,
unresolved_mark: Mark,
globals: &'a Globals,
cm: Lrc<SourceMap>,
loader: L,
resolver: R,
_helper_ctxt: SyntaxContext,
synthesized_ctxt: SyntaxContext,
pub(crate) injected_ctxt: SyntaxContext,
scope: Scope,
hook: Box<dyn 'a + Hook>,
}
impl<'a, L, R> Bundler<'a, L, R>
where
L: Load,
R: Resolve,
{
pub fn new(
globals: &'a Globals,
cm: Lrc<SourceMap>,
loader: L,
resolver: R,
config: Config,
hook: Box<dyn 'a + Hook>,
) -> Self {
GLOBALS.set(globals, || {
let helper_ctxt = SyntaxContext::empty().apply_mark(Mark::fresh(Mark::root()));
tracing::debug!("Helper ctxt: {:?}", helper_ctxt);
let synthesized_ctxt = SyntaxContext::empty().apply_mark(Mark::fresh(Mark::root()));
tracing::debug!("Synthesized ctxt: {:?}", synthesized_ctxt);
let injected_ctxt = SyntaxContext::empty().apply_mark(Mark::fresh(Mark::root()));
tracing::debug!("Injected ctxt: {:?}", injected_ctxt);
Bundler {
config,
globals,
cm,
loader,
resolver,
_helper_ctxt: helper_ctxt,
synthesized_ctxt,
injected_ctxt,
scope: Default::default(),
hook,
unresolved_mark: Mark::new(),
}
})
}
pub(crate) fn is_external(&self, src: &JsWord) -> bool {
self.config.external_modules.iter().any(|v| v == src)
}
pub fn bundle(&mut self, entries: HashMap<String, FileName>) -> Result<Vec<Bundle>, Error> {
let results = entries
.into_iter()
.map(|(name, path)| -> Result<_, Error> {
let path = match path {
FileName::Real(path) => {
if cfg!(target_os = "windows") {
let path = path
.canonicalize()
.context("failed to canonicalize entry")?;
FileName::Real(path)
} else {
FileName::Real(path)
}
}
_ => path,
};
let res = self
.load_transformed(&path)
.context("load_transformed failed")?;
Ok((name, res))
})
.collect::<Vec<_>>();
let local = {
let mut output = AHashMap::default();
for res in results {
let (name, m) = res?;
let m = m.unwrap();
output.insert(name, m);
}
output
};
let bundles = self.chunk(local)?;
let bundles = self.finalize(bundles, self.unresolved_mark)?;
#[cfg(feature = "concurrent")]
{
let scope = std::mem::take(&mut self.scope);
rayon::spawn(move || drop(scope))
}
Ok(bundles)
}
#[inline]
fn run<F, Ret>(&self, op: F) -> Ret
where
F: FnOnce() -> Ret,
{
GLOBALS.set(self.globals, op)
}
}