diff --git a/bundler/Cargo.toml b/bundler/Cargo.toml index cf7c2c15557..fb131e95e05 100644 --- a/bundler/Cargo.toml +++ b/bundler/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" license = "Apache-2.0/MIT" name = "swc_bundler" repository = "https://github.com/swc-project/swc.git" -version = "0.11.0" +version = "0.11.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] @@ -36,6 +36,7 @@ swc_ecma_visit = {version = "0.19.0", path = "../ecmascript/visit"} [dev-dependencies] reqwest = {version = "0.10.8", features = ["blocking"]} +tempfile = "3.1.0" testing = {version = "0.10.0", path = "../testing"} url = "2.1.1" walkdir = "2" diff --git a/bundler/src/bundler/computed_key.rs b/bundler/src/bundler/chunk/computed_key.rs similarity index 86% rename from bundler/src/bundler/computed_key.rs rename to bundler/src/bundler/chunk/computed_key.rs index b8263309e54..5a2c6de8bf3 100644 --- a/bundler/src/bundler/computed_key.rs +++ b/bundler/src/bundler/chunk/computed_key.rs @@ -1,5 +1,5 @@ -use super::load::TransformedModule; -use crate::{Bundler, Load, Resolve}; +use super::plan::Plan; +use crate::{bundler::load::TransformedModule, util::CHashSet, Bundler, Load, ModuleId, Resolve}; use anyhow::Error; use std::mem::take; use swc_atoms::js_word; @@ -31,20 +31,27 @@ where /// ``` pub(super) fn wrap_esm_as_a_var( &self, - _info: &TransformedModule, + plan: &Plan, module: Module, + info: &TransformedModule, + merged: &CHashSet, id: Ident, ) -> Result { let span = module.span; + let mut module_items = vec![]; + let stmts = { let mut module = module.fold_with(&mut ExportToReturn::default()); take(&mut module.body) .into_iter() - .map(|v| match v { - ModuleItem::ModuleDecl(_) => unreachable!(), - ModuleItem::Stmt(s) => s, + .filter_map(|v| match v { + ModuleItem::Stmt(s) => Some(s), + _ => { + module_items.push(v); + None + } }) .collect() }; @@ -85,11 +92,26 @@ where }], }; - Ok(Module { + module_items.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(var_decl)))); + + let module = Module { span: DUMMY_SP, shebang: None, - body: vec![ModuleItem::Stmt(Stmt::Decl(Decl::Var(var_decl)))], - }) + body: module_items, + }; + + let module_plan; + let module_plan = match plan.normal.get(&info.id) { + Some(v) => v, + None => { + module_plan = Default::default(); + &module_plan + } + }; + let module = self.merge_imports(&plan, module_plan, module, info, merged, false)?; + // print_hygiene("Imports", &self.cm, &module); + + Ok(module) } } @@ -112,7 +134,7 @@ impl Fold for ExportToReturn { }; let stmt = match decl { - ModuleDecl::Import(_) => None, + ModuleDecl::Import(_) => return ModuleItem::ModuleDecl(decl), ModuleDecl::ExportDecl(export) => { match &export.decl { Decl::Class(ClassDecl { ident, .. }) | Decl::Fn(FnDecl { ident, .. }) => { diff --git a/bundler/src/bundler/chunk/export.rs b/bundler/src/bundler/chunk/export.rs index 5f5fbb27826..72cc229f32a 100644 --- a/bundler/src/bundler/chunk/export.rs +++ b/bundler/src/bundler/chunk/export.rs @@ -62,6 +62,7 @@ where // Transitive dependencies let mut additional_modules = vec![]; let mut reexports = vec![]; + let mut decls_for_reexport = vec![]; // Remove transitive dependencies which is merged by parent moudle. for v in info.exports.reexports.clone() { @@ -76,6 +77,41 @@ where } } + for (src, specifiers) in info.exports.reexports.iter() { + let imported = self.scope.get_module(src.module_id).unwrap(); + + // export * from './foo'; + if specifiers.is_empty() { + decls_for_reexport.extend( + imported + .exports + .items + .iter() + .chain(imported.exports.reexports.iter().map(|v| &*v.1).flatten()) + .map(|specifier| match specifier { + Specifier::Specific { local, alias } => VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident( + local.clone().replace_mark(info.mark()).into_ident(), + ), + init: Some(Box::new(Expr::Ident( + alias.clone().unwrap_or_else(|| local.clone()).into_ident(), + ))), + definite: false, + }, + Specifier::Namespace { local, .. } => VarDeclarator { + span: DUMMY_SP, + name: Pat::Ident( + local.clone().replace_mark(info.mark()).into_ident(), + ), + init: Some(Box::new(Expr::Ident(local.clone().into_ident()))), + definite: false, + }, + }), + ); + }; + } + let deps = reexports .into_par_iter() .map(|(src, specifiers)| -> Result<_, Error> { @@ -110,7 +146,20 @@ where }); if let Some(id) = id_of_export_namespace_from { - dep = self.wrap_esm_as_a_var(info, dep, id)?; + dep = self.wrap_esm_as_a_var(plan, dep, &imported, merged, id)?; + + let module_plan; + let module_plan = match plan.normal.get(&info.id) { + Some(v) => v, + None => { + module_plan = Default::default(); + &module_plan + } + }; + + dep = self + .merge_imports(plan, &module_plan, dep, &info, merged, false) + .context("failed to merge imports")?; } else { dep = self.remark_exports(dep, src.ctxt, None, false); } @@ -256,6 +305,16 @@ where assert_eq!(injector.imported, vec![]); } + if !decls_for_reexport.is_empty() { + entry + .body + .push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(VarDecl { + span: DUMMY_SP, + kind: VarDeclKind::Const, + declare: false, + decls: decls_for_reexport, + })))); + } Ok(()) } } diff --git a/bundler/src/bundler/chunk/merge.rs b/bundler/src/bundler/chunk/merge.rs index f43aa19b6dc..5de42a1fd7e 100644 --- a/bundler/src/bundler/chunk/merge.rs +++ b/bundler/src/bundler/chunk/merge.rs @@ -1,6 +1,6 @@ -use super::plan::Plan; +use super::plan::{NormalPlan, Plan}; use crate::{ - bundler::load::{Imports, Specifier}, + bundler::load::{Imports, Specifier, TransformedModule}, id::ModuleId, load::Load, resolve::Resolve, @@ -84,278 +84,295 @@ where self.merge_reexports(plan, module_plan, &mut entry, &info, merged) .context("failed to merge reepxorts")?; + // We handle this kind of modules specially. + if self.scope.should_be_wrapped_with_a_fn(info.id) { + return Ok(entry); + } + // print_hygiene("after: merge_reexports", &self.cm, &entry); - let to_merge: Vec<_> = info - .imports - .specifiers - .iter() - .filter(|(src, _)| { - log::trace!("Checking: {} <= {}", info.fm.name, src.src.value); - - // Import and export from same file. We use export to merge it. - if info - .exports - .reexports - .iter() - .any(|(es, _)| es.module_id == src.module_id) - { - return false; - } - - // Skip if a dependency is going to be merged by other dependency - module_plan.chunks.contains(&src.module_id) - }) - .collect(); - - let (deps, transitive_deps) = util::join( - || { - to_merge - .into_par_iter() - .map(|(src, specifiers)| -> Result, Error> { - self.run(|| { - if !merged.insert(src.module_id) { - log::debug!("Skipping: {} <= {}", info.fm.name, src.src.value); - return Ok(None); - } - - log::debug!("Merging: {} <= {}", info.fm.name, src.src.value); - - let dep_info = self.scope.get_module(src.module_id).unwrap(); - info.helpers.extend(&dep_info.helpers); - // In the case of - // - // a <- b - // b <- c - // - // we change it to - // - // a <- b + chunk(c) - // - let mut dep = self - .merge_modules(plan, src.module_id, false, false, merged) - .with_context(|| { - format!( - "failed to merge: ({}):{} <= ({}):{}", - info.id, info.fm.name, src.module_id, src.src.value - ) - })?; - - if dep_info.is_es6 { - // print_hygiene("dep:before:tree-shaking", &self.cm, &dep); - - let is_acccessed_with_computed_key = - specifiers.iter().any(|s| match s { - Specifier::Namespace { all: true, .. } => true, - _ => false, - }); - - // If an import with a computed key exists, we can't shake tree - if is_acccessed_with_computed_key { - let id = specifiers - .iter() - .find_map(|s| match s { - Specifier::Namespace { local, all: true } => { - Some(local) - } - _ => None, - }) - .unwrap(); - - dep = self.wrap_esm_as_a_var( - &dep_info, - dep, - id.clone().replace_mark(dep_info.mark()).into_ident(), - )?; - } else { - if let Some(imports) = info - .imports - .specifiers - .iter() - .find(|(s, _)| s.module_id == dep_info.id) - .map(|v| &v.1) - { - // print_hygiene( - // "dep: before remarking exports", - // &self.cm, - // &dep, - // ); - - dep = self.remark_exports( - dep, - dep_info.ctxt(), - Some(&imports), - true, - ); - } - - // print_hygiene( - // "dep: after remarking exports", - // &self.cm, - // &dep, - // ); - } - // print_hygiene("dep:after:tree-shaking", &self.cm, &dep); - - // if let Some(imports) = info - // .imports - // .specifiers - // .iter() - // .find(|(s, _)| s.module_id == dep_info.id) - // .map(|v| &v.1) - // { - // dep = dep.fold_with(&mut ExportRenamer { - // mark: dep_info.mark(), - // _exports: &dep_info.exports, - // imports: &imports, - // extras: vec![], - // }); - // } - // print_hygiene("dep:after:export-renamer", &self.cm, &dep); - - dep = dep.fold_with(&mut Unexporter); - } - // print_hygiene("dep:before-injection", &self.cm, &dep); - - Ok(Some((dep, dep_info))) - }) - }) - .collect::>() - }, - || { - let deps = module_plan - .transitive_chunks - .clone() - .into_par_iter() - .map(|id| -> Result<_, Error> { - if !merged.insert(id) { - return Ok(None); - } - - let dep_info = self.scope.get_module(id).unwrap(); - let mut dep = self.merge_modules(plan, id, false, true, merged)?; - - // print_hygiene("transitive dep", &self.cm, &dep); - - dep = self.remark_exports(dep, dep_info.ctxt(), None, true); - dep = dep.fold_with(&mut Unexporter); - - // As transitive deps can have no direct relation with entry, - // remark_exports is not enough. - Ok(Some((dep, dep_info))) - }) - .collect::>(); - - deps - }, - ); - - let mut targets = module_plan.chunks.clone(); - - for (dep, is_direct) in deps - .into_iter() - .map(|v| (v, true)) - .chain(transitive_deps.into_iter().map(|v| (v, false))) - { - let dep = dep?; - let dep = match dep { - Some(v) => v, - None => continue, - }; - - let (mut dep, dep_info) = dep; - - if let Some(idx) = targets.iter().position(|v| *v == dep_info.id) { - targets.remove(idx); - if let Some(v) = plan.normal.get(&dep_info.id) { - targets.retain(|&id| !v.chunks.contains(&id)); - } - if let Some(v) = plan.circular.get(&dep_info.id) { - targets.retain(|&id| !v.chunks.contains(&id)); - } - } - - // print_hygiene("dep: before injection", &self.cm, &dep); - - if dep_info.is_es6 { - // print_hygiene("entry: before injection", &self.cm, &entry); - - // Replace import statement / require with module body - let mut injector = Es6ModuleInjector { - imported: take(&mut dep.body), - ctxt: dep_info.ctxt(), - is_direct, - }; - entry.body.visit_mut_with(&mut injector); - - if injector.imported.is_empty() { - // print_hygiene("entry:after:injection", &self.cm, &entry); - - log::debug!("Merged {} as an es module", info.fm.name); - // print_hygiene( - // &format!("ES6: {:?} <- {:?}", info.ctxt(), dep_info.ctxt()), - // &self.cm, - // &entry, - // ); - continue; - } - - if !is_direct { - prepend_stmts(&mut entry.body, injector.imported.into_iter()); - - // print_hygiene("ES6", &self.cm, &entry); - continue; - } - - // print_hygiene("entry: failed to inject", &self.cm, &entry); - - dep.body = take(&mut injector.imported); - } - - if self.config.require { - self.merge_cjs( - plan, - is_entry, - &mut entry, - &info, - Cow::Owned(dep), - &dep_info, - &mut targets, - )?; - } - } - - // if is_entry && self.config.require && !targets.is_empty() { - // log::info!("Injectng remaining: {:?}", targets); - - // // Handle transitive dependencies - // for target in targets.drain(..) { - // log::trace!( - // "Remaining: {}", - // self.scope.get_module(target).unwrap().fm.name - // ); - - // let dep_info = self.scope.get_module(target).unwrap(); - // self.merge_cjs( - // plan, - // &mut entry, - // &info, - // Cow::Borrowed(&dep_info.module), - // &dep_info, - // &mut targets, - // )?; - // } - // } - - if is_entry { - // print_hygiene("done", &self.cm, &entry); - self.finalize_merging_of_entry(plan, &mut entry); - } + entry = self + .merge_imports(plan, module_plan, entry, &info, merged, is_entry) + .context("failed to merge imports")?; Ok(entry) }) } + pub(super) fn merge_imports( + &self, + plan: &Plan, + module_plan: &NormalPlan, + mut entry: Module, + info: &TransformedModule, + merged: &CHashSet, + is_entry: bool, + ) -> Result { + let to_merge: Vec<_> = info + .imports + .specifiers + .iter() + .filter(|(src, _)| { + log::trace!("Checking: {} <= {}", info.fm.name, src.src.value); + + // Import and export from same file. We use export to merge it. + if info + .exports + .reexports + .iter() + .any(|(es, _)| es.module_id == src.module_id) + { + return true; + } + + // Skip if a dependency is going to be merged by other dependency + module_plan.chunks.contains(&src.module_id) + }) + .collect(); + + let (deps, transitive_deps) = util::join( + || { + to_merge + .into_par_iter() + .map(|(src, specifiers)| -> Result, Error> { + self.run(|| { + if !merged.insert(src.module_id) { + log::debug!("Skipping: {} <= {}", info.fm.name, src.src.value); + return Ok(None); + } + + log::debug!("Merging: {} <= {}", info.fm.name, src.src.value); + + let dep_info = self.scope.get_module(src.module_id).unwrap(); + info.helpers.extend(&dep_info.helpers); + // In the case of + // + // a <- b + // b <- c + // + // we change it to + // + // a <- b + chunk(c) + // + let mut dep = self + .merge_modules(plan, src.module_id, false, false, merged) + .with_context(|| { + format!( + "failed to merge: ({}):{} <= ({}):{}", + info.id, info.fm.name, src.module_id, src.src.value + ) + })?; + + if dep_info.is_es6 { + // print_hygiene("dep:before:tree-shaking", &self.cm, &dep); + + let is_acccessed_with_computed_key = + self.scope.should_be_wrapped_with_a_fn(dep_info.id); + + // If an import with a computed key exists, we can't shake tree + if is_acccessed_with_computed_key { + let id = specifiers + .iter() + .find_map(|s| match s { + Specifier::Namespace { local, all: true } => { + Some(local) + } + _ => None, + }) + .unwrap(); + + dep = self.wrap_esm_as_a_var( + plan, + dep, + &dep_info, + merged, + id.clone().replace_mark(dep_info.mark()).into_ident(), + )?; + } else { + if let Some(imports) = info + .imports + .specifiers + .iter() + .find(|(s, _)| s.module_id == dep_info.id) + .map(|v| &v.1) + { + // print_hygiene( + // "dep: before remarking exports", + // &self.cm, + // &dep, + // ); + + dep = self.remark_exports( + dep, + dep_info.ctxt(), + Some(&imports), + true, + ); + } + } + // print_hygiene("dep:after:tree-shaking", &self.cm, &dep); + + // if let Some(imports) = info + // .imports + // .specifiers + // .iter() + // .find(|(s, _)| s.module_id == dep_info.id) + // .map(|v| &v.1) + // { + // dep = dep.fold_with(&mut ExportRenamer { + // mark: dep_info.mark(), + // _exports: &dep_info.exports, + // imports: &imports, + // extras: vec![], + // }); + // } + // print_hygiene("dep:after:export-renamer", &self.cm, &dep); + + dep = dep.fold_with(&mut Unexporter); + } + // print_hygiene("dep:before-injection", &self.cm, &dep); + + Ok(Some((dep, dep_info))) + }) + }) + .collect::>() + }, + || { + let deps = module_plan + .transitive_chunks + .clone() + .into_par_iter() + .map(|id| -> Result<_, Error> { + if !merged.insert(id) { + return Ok(None); + } + + let dep_info = self.scope.get_module(id).unwrap(); + let mut dep = self.merge_modules(plan, id, false, true, merged)?; + + // print_hygiene("transitive dep", &self.cm, &dep); + + dep = self.remark_exports(dep, dep_info.ctxt(), None, true); + dep = dep.fold_with(&mut Unexporter); + + // As transitive deps can have no direct relation with entry, + // remark_exports is not enough. + Ok(Some((dep, dep_info))) + }) + .collect::>(); + + deps + }, + ); + + let mut targets = module_plan.chunks.clone(); + + for (dep, is_direct) in deps + .into_iter() + .map(|v| (v, true)) + .chain(transitive_deps.into_iter().map(|v| (v, false))) + { + let dep = dep?; + let dep = match dep { + Some(v) => v, + None => continue, + }; + + let (mut dep, dep_info) = dep; + + if let Some(idx) = targets.iter().position(|v| *v == dep_info.id) { + targets.remove(idx); + if let Some(v) = plan.normal.get(&dep_info.id) { + targets.retain(|&id| !v.chunks.contains(&id)); + } + if let Some(v) = plan.circular.get(&dep_info.id) { + targets.retain(|&id| !v.chunks.contains(&id)); + } + } + + // print_hygiene("dep: before injection", &self.cm, &dep); + + if dep_info.is_es6 { + // print_hygiene("entry: before injection", &self.cm, &entry); + + // Replace import statement / require with module body + let mut injector = Es6ModuleInjector { + imported: take(&mut dep.body), + ctxt: dep_info.ctxt(), + is_direct, + }; + entry.body.visit_mut_with(&mut injector); + + if injector.imported.is_empty() { + // print_hygiene("entry:after:injection", &self.cm, &entry); + + log::debug!( + "Merged {} into {} as an es module", + dep_info.fm.name, + info.fm.name, + ); + // print_hygiene( + // &format!("ES6: {:?} <- {:?}", info.ctxt(), dep_info.ctxt()), + // &self.cm, + // &entry, + // ); + continue; + } + + if !is_direct { + prepend_stmts(&mut entry.body, injector.imported.into_iter()); + + // print_hygiene("ES6", &self.cm, &entry); + continue; + } + + // print_hygiene("entry: failed to inject", &self.cm, &entry); + + dep.body = take(&mut injector.imported); + } + + if self.config.require { + self.merge_cjs( + plan, + is_entry, + &mut entry, + &info, + Cow::Owned(dep), + &dep_info, + &mut targets, + )?; + } + } + + // if is_entry && self.config.require && !targets.is_empty() { + // log::info!("Injectng remaining: {:?}", targets); + + // // Handle transitive dependencies + // for target in targets.drain(..) { + // log::trace!( + // "Remaining: {}", + // self.scope.get_module(target).unwrap().fm.name + // ); + + // let dep_info = self.scope.get_module(target).unwrap(); + // self.merge_cjs( + // plan, + // &mut entry, + // &info, + // Cow::Borrowed(&dep_info.module), + // &dep_info, + // &mut targets, + // )?; + // } + // } + + if is_entry { + self.finalize_merging_of_entry(plan, &mut entry); + } + + Ok(entry) + } + fn finalize_merging_of_entry(&self, plan: &Plan, entry: &mut Module) { // print_hygiene("done", &self.cm, &entry); @@ -412,6 +429,15 @@ where }); entry.visit_mut_with(&mut DefaultRenamer); + + // print_hygiene( + // "done-clean", + // &self.cm, + // &entry + // .clone() + // .fold_with(&mut hygiene()) + // .fold_with(&mut fixer(None)), + // ); } } diff --git a/bundler/src/bundler/chunk/mod.rs b/bundler/src/bundler/chunk/mod.rs index f8568114219..97d5fee208c 100644 --- a/bundler/src/bundler/chunk/mod.rs +++ b/bundler/src/bundler/chunk/mod.rs @@ -9,6 +9,7 @@ use swc_ecma_visit::FoldWith; mod circular; mod cjs; +mod computed_key; mod export; mod merge; mod plan; diff --git a/bundler/src/bundler/chunk/plan/mod.rs b/bundler/src/bundler/chunk/plan/mod.rs index 8fe43f39c10..f6f216a8364 100644 --- a/bundler/src/bundler/chunk/plan/mod.rs +++ b/bundler/src/bundler/chunk/plan/mod.rs @@ -26,7 +26,9 @@ struct PlanBuilder { /// This contains all dependencies, including transitive ones. For example, /// if `a` dependes on `b` and `b` depdends on `c`, all of /// `(a, b)`, `(a, c)`,`(b, c)` will be inserted. - all_deps: HashSet<(ModuleId, ModuleId)>, + /// + /// `bool` is `true` if it's connected with exports. + all_deps: HashMap<(ModuleId, ModuleId), bool>, /// Graph to compute direct dependencies (direct means it will be merged /// directly) @@ -168,7 +170,7 @@ where None => {} } - self.add_to_graph(&mut builder, module.id, &mut vec![]); + self.add_to_graph(&mut builder, module.id, &mut vec![], true); } let mut metadata = HashMap::::default(); @@ -222,11 +224,14 @@ where let root_entry = *root_entry; let mut bfs = Bfs::new(&builder.direct_deps, root_entry); + let mut done = HashSet::new(); + while let Some(entry) = bfs.next(&builder.direct_deps) { - let deps: Vec<_> = builder + let mut deps: Vec<_> = builder .direct_deps .neighbors_directed(entry, Outgoing) .collect(); + deps.sort(); for &dep in &deps { if let Some(circular_members) = builder.circular.get(entry) { @@ -237,21 +242,33 @@ where entry ); if entry != root_entry && dep != root_entry { + done.insert(dep); plans.normal.entry(entry).or_default().chunks.push(dep); } continue; } } + if done.contains(&dep) { + continue; + } + let is_es6 = self.scope.get_module(entry).unwrap().is_es6; - let dependants = builder + let mut dependants = builder .direct_deps .neighbors_directed(dep, Incoming) .collect::>(); + dependants.sort(); if metadata.get(&dep).map(|md| md.bundle_cnt).unwrap_or(0) == 1 { log::debug!("{:?} depends on {:?}", entry, dep); + let is_reexport = builder + .all_deps + .get(&(entry, dep)) + .copied() + .unwrap_or(false); + // Common js support. if !is_es6 { // Dependancy of @@ -280,7 +297,6 @@ where // a <- b // a <- c let module = least_common_ancestor(&builder, &dependants); - let normal_plan = plans.normal.entry(module).or_default(); for &dep in &deps { @@ -301,6 +317,17 @@ where continue; } + if is_reexport { + let normal_plan = plans.normal.entry(entry).or_default(); + if !normal_plan.chunks.contains(&dep) { + done.insert(dep); + + log::trace!("Normal: {:?} => {:?}", entry, dep); + normal_plan.chunks.push(dep); + } + continue; + } + if 2 <= dependants.len() { // Should be merged as a transitive dependency. let higher_module = if plans.entries.contains(&dependants[0]) { @@ -314,8 +341,11 @@ where }; if dependants.len() == 2 && dependants.contains(&higher_module) { - let mut entry = - *dependants.iter().find(|&&v| v != higher_module).unwrap(); + let mut entry = if is_reexport { + higher_module + } else { + *dependants.iter().find(|&&v| v != higher_module).unwrap() + }; // We choose higher node if import is circular if builder.is_circular(entry) { @@ -325,6 +355,7 @@ where let normal_plan = plans.normal.entry(entry).or_default(); if !normal_plan.chunks.contains(&dep) { log::trace!("Normal: {:?} => {:?}", entry, dep); + done.insert(dep); normal_plan.chunks.push(dep); } } else { @@ -335,12 +366,14 @@ where .transitive_chunks; if !t.contains(&dep) { log::trace!("Transitive: {:?} => {:?}", entry, dep); + done.insert(dep); t.push(dep) } } } else { // Direct dependency. log::trace!("Normal: {:?} => {:?}", entry, dep); + done.insert(dep); plans.normal.entry(entry).or_default().chunks.push(dep); } @@ -436,6 +469,7 @@ where builder: &mut PlanBuilder, module_id: ModuleId, path: &mut Vec, + is_in_reexports: bool, ) { builder.direct_deps.add_node(module_id); @@ -444,34 +478,44 @@ where .get_module(module_id) .expect("failed to get module"); - for src in m + for (src, is_export) in m .imports .specifiers .iter() - .map(|v| &v.0) - .chain(m.exports.reexports.iter().map(|v| &v.0)) + .map(|v| (&v.0, false)) + .chain(m.exports.reexports.iter().map(|v| (&v.0, true))) { if !builder.direct_deps.contains_edge(module_id, src.module_id) { - log::debug!("Dependency: {:?} => {:?}", module_id, src.module_id); + log::debug!( + "Dependency: {:?} => {:?}; in export = {:?}; export = {:?}", + module_id, + src.module_id, + is_in_reexports, + is_export + ); } builder.direct_deps.add_edge(module_id, src.module_id, 0); for &id in &*path { - builder.all_deps.insert((id, src.module_id)); + builder + .all_deps + .insert((id, src.module_id), is_in_reexports); } - builder.all_deps.insert((module_id, src.module_id)); + builder + .all_deps + .insert((module_id, src.module_id), is_export); - if !builder.all_deps.contains(&(src.module_id, module_id)) { + if !builder.all_deps.contains_key(&(src.module_id, module_id)) { path.push(module_id); - self.add_to_graph(builder, src.module_id, path); + self.add_to_graph(builder, src.module_id, path, is_export); assert_eq!(path.pop(), Some(module_id)); } } // Prevent dejavu for (src, _) in &m.imports.specifiers { - if builder.all_deps.contains(&(src.module_id, module_id)) { + if builder.all_deps.contains_key(&(src.module_id, module_id)) { log::debug!("Circular dep: {:?} => {:?}", module_id, src.module_id); builder.mark_as_circular(module_id, src.module_id); diff --git a/bundler/src/bundler/chunk/plan/tests.rs b/bundler/src/bundler/chunk/plan/tests.rs index fb549c122e3..4e5ee67b26c 100644 --- a/bundler/src/bundler/chunk/plan/tests.rs +++ b/bundler/src/bundler/chunk/plan/tests.rs @@ -109,8 +109,8 @@ fn concurrency_001() { assert_eq!(p.circular.len(), 0); - assert_normal(t, &p, "main", &["a"]); - assert_normal(t, &p, "a", &["b"]); + assert_normal(t, &p, "main", &["a", "b"]); + assert_normal(t, &p, "a", &[]); assert_normal(t, &p, "b", &[]); Ok(()) @@ -153,8 +153,8 @@ fn concurrency_002() { assert_eq!(p.circular.len(), 0); - assert_normal(t, &p, "main", &["a"]); - assert_normal(t, &p, "a", &["b"]); + assert_normal(t, &p, "main", &["a", "b"]); + assert_normal(t, &p, "a", &[]); assert_normal(t, &p, "b", &[]); Ok(()) @@ -702,7 +702,6 @@ fn circular_002() { } #[test] -#[ignore = "Not deterministic yet"] fn deno_002() { suite() .file( @@ -799,7 +798,13 @@ fn deno_002() { dbg!(&p); - assert_normal(t, &p, "main", &["http-server"]); + assert_normal_transitive( + t, + &p, + "main", + &["http-server"], + &["encoding-utf8", "io-bufio", "_util-assert"], + ); assert_normal_transitive(t, &p, "http-server", &["async-mod"], &[]); assert_circular(t, &p, "http-server", &["http-_io"]); @@ -810,25 +815,22 @@ fn deno_002() { assert_normal(t, &p, "_util-assert", &[]); - assert_normal_transitive( - t, - &p, - "http-_io", - &["textproto-mod", "http-http_status"], - &["encoding-utf8", "io-bufio", "_util-assert"], - ); + assert_normal(t, &p, "http-_io", &["textproto-mod", "http-http_status"]); assert_circular(t, &p, "http-_io", &["http-server"]); assert_normal(t, &p, "textproto-mod", &[]); assert_normal(t, &p, "_util-assert", &[]); - assert_normal(t, &p, "async-mod", &["async-mux_async_iterator"]); + assert_normal( + t, + &p, + "async-mod", + &["async-mux_async_iterator", "async-deferred"], + ); assert_normal(t, &p, "bytes-mod", &[]); - assert_normal(t, &p, "async-mux_async_iterator", &["async-deferred"]); - Ok(()) }); } @@ -908,7 +910,6 @@ fn circular_root_entry_2() { } #[test] -#[ignore = "Not deterministic yet"] fn deno_003() { suite() .file( @@ -945,9 +946,14 @@ fn deno_003() { assert_normal(t, &p, "main", &["async-mod"]); - assert_normal(t, &p, "async-mod", &["async-mux_async_iterator"]); + assert_normal( + t, + &p, + "async-mod", + &["async-deferred", "async-mux_async_iterator"], + ); - assert_normal(t, &p, "async-mux_async_iterator", &["async-deferred"]); + assert_normal(t, &p, "async-mux_async_iterator", &[]); Ok(()) }); diff --git a/bundler/src/bundler/chunk/remark.rs b/bundler/src/bundler/chunk/remark.rs index 33546bbcace..d2417995125 100644 --- a/bundler/src/bundler/chunk/remark.rs +++ b/bundler/src/bundler/chunk/remark.rs @@ -121,20 +121,17 @@ impl ExportRenamer<'_> { impl Fold for ExportRenamer<'_> { noop_fold_type!(); + /// no-op, as imports or exports are only related to top level nodes. fn fold_class(&mut self, node: Class) -> Class { node } + /// no-op, as imports or exports are only related to top level nodes. fn fold_function(&mut self, node: Function) -> Function { node } fn fold_module_item(&mut self, item: ModuleItem) -> ModuleItem { - let mut actual = ActualMarker { - dep_ctxt: self.dep_ctxt, - imports: self.imports, - }; - let span = item.span(); let item: ModuleItem = item.fold_children_with(self); @@ -442,7 +439,7 @@ impl Fold for ExportRenamer<'_> { } } - ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(decl)) => { + ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(mut decl)) => { // return match decl.decl { Decl::TsInterface(_) @@ -451,7 +448,11 @@ impl Fold for ExportRenamer<'_> { | Decl::TsModule(_) => ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(decl)), Decl::Class(mut c) => { - c.ident = c.ident.fold_with(&mut actual); + c.ident.visit_mut_with(&mut ActualMarker { + dep_ctxt: self.dep_ctxt, + imports: self.imports, + remarks: &mut self.remark_map, + }); if self.unexport { Stmt::Decl(Decl::Class(c)).into() @@ -463,7 +464,11 @@ impl Fold for ExportRenamer<'_> { } } Decl::Fn(mut f) => { - f.ident = f.ident.fold_with(&mut actual); + f.ident.visit_mut_with(&mut ActualMarker { + dep_ctxt: self.dep_ctxt, + imports: self.imports, + remarks: &mut self.remark_map, + }); if self.unexport { Stmt::Decl(Decl::Fn(f)).into() } else { @@ -473,8 +478,14 @@ impl Fold for ExportRenamer<'_> { })) } } - Decl::Var(..) => { - ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(decl.fold_with(&mut actual))) + Decl::Var(ref mut v) => { + v.decls.visit_mut_with(&mut ActualMarker { + dep_ctxt: self.dep_ctxt, + imports: self.imports, + remarks: &mut self.remark_map, + }); + + ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(decl)) } }; } @@ -494,90 +505,51 @@ impl Fold for ExportRenamer<'_> { } } -struct ActualMarker<'a> { +struct ActualMarker<'a, 'b> { dep_ctxt: SyntaxContext, /// Dependant module's import imports: Option<&'a [Specifier]>, + + remarks: &'b mut RemarkMap, } -impl ActualMarker<'_> { - fn rename(&self, ident: Ident, only_if_aliased: bool) -> Result { +impl ActualMarker<'_, '_> { + fn rename(&mut self, ident: &mut Ident) { if self.imports.is_none() { - return Err(ident); + return; } - if let Some(mut ident) = self.imports.as_ref().unwrap().iter().find_map(|s| match s { + if let Some(id) = self.imports.as_ref().unwrap().iter().find_map(|s| match s { Specifier::Specific { alias: Some(alias), local, - } if *alias == ident.sym => Some(Ident::new(local.sym().clone(), ident.span)), - Specifier::Specific { alias: None, local } - if !only_if_aliased && *local == ident.sym => - { - Some(local.clone().into_ident()) + } if *alias == ident.sym => Some((local.sym().clone(), ident.span.ctxt)), + Specifier::Specific { alias: None, local } if *local == ident.sym => { + Some(local.to_id()) } _ => None, }) { - ident.span = ident.span.with_ctxt(self.dep_ctxt); - - return Ok(ident); + ident.sym = id.0.clone(); + self.remarks.insert(id, self.dep_ctxt); } - - Err(ident) } } -impl Fold for ActualMarker<'_> { - noop_fold_type!(); +impl VisitMut for ActualMarker<'_, '_> { + noop_visit_mut_type!(); - fn fold_expr(&mut self, node: Expr) -> Expr { - node + fn visit_mut_expr(&mut self, _: &mut Expr) {} + + fn visit_mut_ident(&mut self, ident: &mut Ident) { + self.rename(ident) } - fn fold_ident(&mut self, ident: Ident) -> Ident { - match self.rename(ident, false) { - Ok(v) => v, - Err(v) => v, - } - } + fn visit_mut_private_name(&mut self, _: &mut PrivateName) {} - fn fold_private_name(&mut self, i: PrivateName) -> PrivateName { - i + fn visit_mut_export_named_specifier(&mut self, s: &mut ExportNamedSpecifier) { + s.orig.visit_mut_with(self); } - - fn fold_export_named_specifier(&mut self, s: ExportNamedSpecifier) -> ExportNamedSpecifier { - if let Some(..) = s.exported { - ExportNamedSpecifier { - orig: self.fold_ident(s.orig), - ..s - } - } else { - match self.rename(s.orig.clone(), false) { - Ok(exported) => ExportNamedSpecifier { - orig: s.orig, - exported: Some(exported), - ..s - }, - Err(orig) => ExportNamedSpecifier { orig, ..s }, - } - } - } - - fn fold_prop(&mut self, p: Prop) -> Prop { - match p { - Prop::Shorthand(i) => match self.rename(i.clone(), false) { - Ok(renamed) => Prop::KeyValue(KeyValueProp { - key: i.into(), - value: Box::new(renamed.into()), - }), - Err(orig) => Prop::Shorthand(orig), - }, - _ => p.fold_with(self), - } - } - - // TODO: shorthand, etc } struct RemarkIdents<'a> { diff --git a/bundler/src/bundler/export.rs b/bundler/src/bundler/export.rs index 9fa8a92d98b..8ef8c39fc4d 100644 --- a/bundler/src/bundler/export.rs +++ b/bundler/src/bundler/export.rs @@ -4,7 +4,7 @@ use super::{ }; use crate::{id::Id, load::Load, resolve::Resolve}; use std::collections::HashMap; -use swc_atoms::js_word; +use swc_atoms::{js_word, JsWord}; use swc_common::{FileName, SyntaxContext}; use swc_ecma_ast::*; use swc_ecma_utils::find_ids; @@ -63,7 +63,7 @@ where L: Load, R: Resolve, { - fn ctxt_for(&self, src: &str) -> Option { + fn ctxt_for(&self, src: &JsWord) -> Option { // Don't apply mark if it's a core module. if self .bundler @@ -80,6 +80,27 @@ where Some(ctxt.apply_mark(mark)) } + + fn mark_as_wrapping_required(&self, src: &JsWord) { + // Don't apply mark if it's a core module. + if self + .bundler + .config + .external_modules + .iter() + .any(|v| v == src) + { + return; + } + let path = self.bundler.resolve(self.file_name, src); + let path = match path { + Ok(v) => v, + _ => return, + }; + let (id, _) = self.bundler.scope.module_id_gen.gen(&path); + + self.bundler.scope.mark_as_wrapping_required(id); + } } impl VisitMut for ExportFinder<'_, '_, L, R> @@ -178,16 +199,20 @@ where let ctxt = named .src .as_ref() - .map(|s| &*s.value) + .map(|s| &s.value) .and_then(|src| self.ctxt_for(src)); + let mut need_wrapping = false; let v = self.info.items.entry(named.src.clone()).or_default(); for s in &mut named.specifiers { match s { - ExportSpecifier::Namespace(n) => v.push(Specifier::Namespace { - local: n.name.clone().into(), - all: true, - }), + ExportSpecifier::Namespace(n) => { + need_wrapping = true; + v.push(Specifier::Namespace { + local: n.name.clone().into(), + all: true, + }) + } ExportSpecifier::Default(d) => { v.push(Specifier::Specific { local: d.exported.clone().into(), @@ -213,6 +238,11 @@ where } } } + + if need_wrapping { + self.mark_as_wrapping_required(&named.src.as_ref().unwrap().value); + } + return; } diff --git a/bundler/src/bundler/import/mod.rs b/bundler/src/bundler/import/mod.rs index 1918ac2e2bf..ecc539ccaac 100644 --- a/bundler/src/bundler/import/mod.rs +++ b/bundler/src/bundler/import/mod.rs @@ -127,7 +127,7 @@ where L: Load, R: Resolve, { - fn ctxt_for(&self, src: &str) -> Option { + fn ctxt_for(&self, src: &JsWord) -> Option { // Don't apply mark if it's a core module. if self .bundler @@ -144,6 +144,27 @@ where Some(ctxt.apply_mark(mark)) } + + fn mark_as_wrapping_required(&self, src: &JsWord) { + // Don't apply mark if it's a core module. + if self + .bundler + .config + .external_modules + .iter() + .any(|v| v == src) + { + return; + } + let path = self.bundler.resolve(self.path, src); + let path = match path { + Ok(v) => v, + Err(_) => return, + }; + let (id, _) = self.bundler.scope.module_id_gen.gen(&path); + + self.bundler.scope.mark_as_wrapping_required(id); + } } impl Fold for ImportHandler<'_, '_, L, R> @@ -268,10 +289,13 @@ where }); if self.deglob_phase { + let mut wrapping_required = vec![]; for import in self.info.imports.iter_mut() { let use_ns = self.info.forced_ns.contains(&import.src.value); if use_ns { + wrapping_required.push(import.src.value.clone()); + import.specifiers.retain(|s| match s { ImportSpecifier::Namespace(_) => true, _ => false, @@ -290,6 +314,10 @@ where }); } } + + for id in wrapping_required { + self.mark_as_wrapping_required(&id); + } } items diff --git a/bundler/src/bundler/import/tests.rs b/bundler/src/bundler/import/tests.rs index 28213eb0d56..8cec1eec639 100644 --- a/bundler/src/bundler/import/tests.rs +++ b/bundler/src/bundler/import/tests.rs @@ -1,74 +1,75 @@ -use super::ImportHandler; -use crate::bundler::tests::suite; -use std::path::Path; -use swc_common::{FileName, Mark, SyntaxContext}; -use swc_ecma_visit::FoldWith; +// use super::ImportHandler; +// use crate::bundler::tests::suite; +// use std::path::Path; +// use swc_common::{FileName, Mark, SyntaxContext}; +// use swc_ecma_visit::FoldWith; -#[test] -#[ignore] -fn ns_import_deglob_simple() { - suite().run(|t| { - let m = t.parse( - " -import * as ns from 'foo'; -ns.foo(); -", - ); - let mut v = ImportHandler { - path: &FileName::Real(Path::new("index.js").to_path_buf()), - bundler: &t.bundler, - top_level: false, - info: Default::default(), - ns_usage: Default::default(), - deglob_phase: false, - imported_idents: Default::default(), - module_ctxt: SyntaxContext::empty().apply_mark(Mark::fresh(Mark::root())), - idents_to_deglob: Default::default(), - }; - let m = m.fold_with(&mut v); - assert!(v.info.forced_ns.is_empty()); - assert_eq!(v.info.imports.len(), 1); +// #[test] +// #[ignore] +// fn ns_import_deglob_simple() { +// suite().run(|t| { +// let m = t.parse( +// " +// import * as ns from 'foo'; +// ns.foo(); +// ", +// ); +// let mut v = ImportHandler { +// path: &FileName::Real(Path::new("index.js").to_path_buf()), +// bundler: &t.bundler, +// top_level: false, +// info: Default::default(), +// ns_usage: Default::default(), +// deglob_phase: false, +// imported_idents: Default::default(), +// module_ctxt: +// SyntaxContext::empty().apply_mark(Mark::fresh(Mark::root())), +// idents_to_deglob: Default::default(), }; +// let m = m.fold_with(&mut v); +// assert!(v.info.forced_ns.is_empty()); +// assert_eq!(v.info.imports.len(), 1); - t.assert_eq(&m, "foo();"); +// t.assert_eq(&m, "foo();"); - Ok(()) - }) -} +// Ok(()) +// }) +// } -#[test] -#[ignore] -fn ns_import_deglob_multi() { - suite().run(|t| { - let m = t.parse( - " -import * as ns from 'foo'; -ns.foo(); -ns.bar(); -", - ); - let mut v = ImportHandler { - path: &FileName::Real(Path::new("index.js").to_path_buf()), - bundler: &t.bundler, - top_level: false, - info: Default::default(), - ns_usage: Default::default(), - deglob_phase: false, - imported_idents: Default::default(), - module_ctxt: SyntaxContext::empty().apply_mark(Mark::fresh(Mark::root())), - idents_to_deglob: Default::default(), - }; - let m = m.fold_with(&mut v); - assert!(v.info.forced_ns.is_empty()); - assert_eq!(v.info.imports.len(), 1); - assert_eq!(v.info.imports[0].specifiers.len(), 2); - assert!(!format!("{:?}", v.info.imports[0].specifiers).contains("ns")); +// #[test] +// #[ignore] +// fn ns_import_deglob_multi() { +// suite().run(|t| { +// let m = t.parse( +// " +// import * as ns from 'foo'; +// ns.foo(); +// ns.bar(); +// ", +// ); +// let mut v = ImportHandler { +// path: &FileName::Real(Path::new("index.js").to_path_buf()), +// bundler: &t.bundler, +// top_level: false, +// info: Default::default(), +// ns_usage: Default::default(), +// deglob_phase: false, +// imported_idents: Default::default(), +// module_ctxt: +// SyntaxContext::empty().apply_mark(Mark::fresh(Mark::root())), +// idents_to_deglob: Default::default(), }; +// let m = m.fold_with(&mut v); +// assert!(v.info.forced_ns.is_empty()); +// assert_eq!(v.info.imports.len(), 1); +// assert_eq!(v.info.imports[0].specifiers.len(), 2); +// assert!(!format!("{:?}", +// v.info.imports[0].specifiers).contains("ns")); - t.assert_eq( - &m, - "foo(); -bar();", - ); +// t.assert_eq( +// &m, +// "foo(); +// bar();", +// ); - Ok(()) - }) -} +// Ok(()) +// }) +// } diff --git a/bundler/src/bundler/load.rs b/bundler/src/bundler/load.rs index 845eac34afd..075ed66de69 100644 --- a/bundler/src/bundler/load.rs +++ b/bundler/src/bundler/load.rs @@ -75,7 +75,7 @@ where .context("failed to analyze module")?; files.dedup_by_key(|v| v.1.clone()); - log::debug!("({}) Storing module: {}", v.id, file_name); + log::debug!("({}, {:?}) Storing module: {}", v.id, v.ctxt(), file_name); self.scope.store_module(v.clone()); // Load dependencies and store them in the `Scope` diff --git a/bundler/src/bundler/mod.rs b/bundler/src/bundler/mod.rs index d73b27754ea..5919d0b2fa9 100644 --- a/bundler/src/bundler/mod.rs +++ b/bundler/src/bundler/mod.rs @@ -7,7 +7,6 @@ use swc_common::{sync::Lrc, FileName, Globals, Mark, SourceMap, SyntaxContext, D use swc_ecma_ast::Module; mod chunk; -mod computed_key; mod export; mod finalize; mod helpers; diff --git a/bundler/src/bundler/scope.rs b/bundler/src/bundler/scope.rs index cc612003f23..32fab380f3a 100644 --- a/bundler/src/bundler/scope.rs +++ b/bundler/src/bundler/scope.rs @@ -3,7 +3,8 @@ use crate::{ id::{ModuleId, ModuleIdGenerator}, util::CloneMap, }; -use swc_common::FileName; +use std::sync::atomic::{AtomicBool, Ordering}; +use swc_common::{sync::Lrc, FileName}; #[derive(Debug, Default)] pub(super) struct Metadata { @@ -18,6 +19,8 @@ pub(super) struct Scope { /// Cached after applying basical transformations. transformed_modules: CloneMap, + + accessed_with_computed_key: CloneMap>, } impl Scope { @@ -40,4 +43,23 @@ impl Scope { pub fn get_module(&self, id: ModuleId) -> Option { Some(self.transformed_modules.get(&id)?.clone()) } + + /// Set the module as + pub fn mark_as_wrapping_required(&self, id: ModuleId) { + if let Some(v) = self.accessed_with_computed_key.get(&id) { + v.store(true, Ordering::SeqCst); + return; + } + + self.accessed_with_computed_key + .insert(id, Lrc::new(AtomicBool::from(true))); + } + + pub fn should_be_wrapped_with_a_fn(&self, id: ModuleId) -> bool { + if let Some(v) = self.accessed_with_computed_key.get(&id) { + v.load(Ordering::SeqCst) + } else { + false + } + } } diff --git a/bundler/src/bundler/tests.rs b/bundler/src/bundler/tests.rs index c4868856be5..38bbed8992f 100644 --- a/bundler/src/bundler/tests.rs +++ b/bundler/src/bundler/tests.rs @@ -70,6 +70,7 @@ impl<'a> Tester<'a> { .unwrap_or_else(|| panic!("failed to find module named {}", name)) } + #[allow(dead_code)] pub fn parse(&self, s: &str) -> Module { let fm = self .cm @@ -85,6 +86,7 @@ impl<'a> Tester<'a> { parser.parse_module().unwrap() } + #[allow(dead_code)] pub fn assert_eq(&self, m: &Module, expected: &str) { let expected = self.parse(expected); diff --git a/bundler/src/debug/mod.rs b/bundler/src/debug/mod.rs index 2f0c9880ba6..cbe1f2a2220 100644 --- a/bundler/src/debug/mod.rs +++ b/bundler/src/debug/mod.rs @@ -1,7 +1,7 @@ #![allow(dead_code)] use std::io::{stderr, Write}; -use swc_common::{sync::Lrc, SourceMap}; +use swc_common::{sync::Lrc, SourceMap, SyntaxContext}; use swc_ecma_ast::{Ident, Module}; use swc_ecma_codegen::{text_writer::JsWriter, Emitter}; use swc_ecma_visit::{noop_fold_type, Fold, FoldWith}; @@ -30,6 +30,9 @@ impl Fold for HygieneVisualizer { noop_fold_type!(); fn fold_ident(&mut self, node: Ident) -> Ident { + if node.span.ctxt == SyntaxContext::empty() { + return node; + } Ident { sym: format!("{}{:?}", node.sym, node.span.ctxt()).into(), ..node diff --git a/bundler/src/util.rs b/bundler/src/util.rs index 11f4c929ad9..940c5527bf4 100644 --- a/bundler/src/util.rs +++ b/bundler/src/util.rs @@ -1,7 +1,103 @@ -use std::hash::Hash; -use swc_common::{Span, SyntaxContext}; +use std::{hash::Hash, mem::replace}; +use swc_atoms::js_word; +use swc_common::{Span, SyntaxContext, DUMMY_SP}; +use swc_ecma_ast::*; use swc_ecma_visit::{noop_visit_mut_type, VisitMut}; +/// Helper for migration from [Fold] to [VisitMut] +pub(crate) trait MapWithMut: Sized { + fn dummy() -> Self; + + fn take(&mut self) -> Self { + replace(self, Self::dummy()) + } + + #[inline] + fn map_with_mut(&mut self, op: F) + where + F: FnOnce(Self) -> Self, + { + let dummy = Self::dummy(); + let v = replace(self, dummy); + let v = op(v); + let _dummy = replace(self, v); + } +} + +impl MapWithMut for ModuleItem { + #[inline(always)] + fn dummy() -> Self { + ModuleItem::Stmt(Stmt::Empty(EmptyStmt { span: DUMMY_SP })) + } +} + +impl MapWithMut for Stmt { + #[inline(always)] + fn dummy() -> Self { + Stmt::Empty(EmptyStmt { span: DUMMY_SP }) + } +} + +impl MapWithMut for Expr { + #[inline(always)] + fn dummy() -> Self { + Expr::Invalid(Invalid { span: DUMMY_SP }) + } +} + +impl MapWithMut for Pat { + #[inline(always)] + fn dummy() -> Self { + Pat::Invalid(Invalid { span: DUMMY_SP }) + } +} + +impl MapWithMut for Option { + #[inline(always)] + fn dummy() -> Self { + None + } +} + +impl MapWithMut for Vec { + #[inline(always)] + fn dummy() -> Self { + Vec::new() + } +} + +impl MapWithMut for Box +where + T: MapWithMut, +{ + #[inline(always)] + fn dummy() -> Self { + Box::new(T::dummy()) + } +} + +impl MapWithMut for Ident { + fn dummy() -> Self { + Ident::new(js_word!(""), DUMMY_SP) + } +} + +impl MapWithMut for ObjectPatProp { + fn dummy() -> Self { + ObjectPatProp::Assign(AssignPatProp { + span: DUMMY_SP, + key: Ident::dummy(), + value: None, + }) + } +} + +impl MapWithMut for PatOrExpr { + fn dummy() -> Self { + PatOrExpr::Pat(Box::new(Pat::Ident(Ident::dummy()))) + } +} + #[derive(Debug)] pub(crate) struct CHashSet where diff --git a/bundler/tests/deno.rs b/bundler/tests/deno.rs index 8ba9301c0fd..83b945ae1e0 100644 --- a/bundler/tests/deno.rs +++ b/bundler/tests/deno.rs @@ -2,12 +2,16 @@ //! //! This module exists because this is way easier than using copying requires //! files. - use anyhow::{Context, Error}; -use std::collections::HashMap; +use std::{ + collections::HashMap, + fs::write, + process::{Command, Stdio}, +}; use swc_bundler::{Bundler, Load, Resolve}; use swc_common::{sync::Lrc, FileName, SourceFile, SourceMap, Span, GLOBALS}; use swc_ecma_ast::{Expr, Lit, Module, Str}; +use swc_ecma_codegen::{text_writer::JsWriter, Emitter}; use swc_ecma_parser::{lexer::Lexer, JscTarget, Parser, StringInput, Syntax, TsConfig}; use swc_ecma_transforms::typescript::strip; use swc_ecma_visit::FoldWith; @@ -15,11 +19,53 @@ use url::Url; #[test] #[ignore = "Too slow"] -fn oak_6_2_0_application() { - bundle("https://deno.land/x/oak@v6.2.0/application.ts"); +fn oak_6_3_1_application() { + run("https://deno.land/x/oak@v6.3.1/application.ts"); } -fn bundle(url: &str) -> Module { +#[test] +#[ignore = "Too slow"] +fn oak_6_3_1_mod() { + run("https://deno.land/x/oak@v6.3.1/mod.ts"); +} + +#[test] +#[ignore = "Too slow"] +fn std_0_74_9_http_server() { + run("https://deno.land/std@0.74.0/http/server.ts"); +} + +#[test] +#[ignore = "Too slow"] +fn oak_6_3_1_example() { + run("https://deno.land/x/oak@v6.3.1/examples/server.ts"); +} + +fn run(url: &str) { + let dir = tempfile::tempdir().expect("failed to crate temp file"); + let path = dir.path().join("main.js"); + println!("{}", path.display()); + + let src = bundle(url); + write(&path, &src).unwrap(); + + let output = Command::new("deno") + .arg("run") + .arg("--allow-all") + .arg("--no-check") + .arg(&path) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .status() + .unwrap(); + + std::mem::forget(dir); + + dbg!(output); + assert!(output.success()); +} + +fn bundle(url: &str) -> String { let result = testing::run_test2(false, |cm, _handler| { GLOBALS.with(|globals| { let bundler = Bundler::new( @@ -29,7 +75,7 @@ fn bundle(url: &str) -> Module { Resolver, swc_bundler::Config { require: false, - disable_inliner: true, + disable_inliner: false, ..Default::default() }, Box::new(Hook), @@ -37,8 +83,21 @@ fn bundle(url: &str) -> Module { let mut entries = HashMap::new(); entries.insert("main".to_string(), FileName::Custom(url.to_string())); let output = bundler.bundle(entries).unwrap(); + let module = output.into_iter().next().unwrap().module; - Ok(output.into_iter().next().unwrap().module) + let mut buf = vec![]; + { + Emitter { + cfg: swc_ecma_codegen::Config { minify: false }, + cm: cm.clone(), + comments: None, + wr: Box::new(JsWriter::new(cm.clone(), "\n", &mut buf, None)), + } + .emit_module(&module) + .unwrap(); + } + + Ok(String::from_utf8_lossy(&buf).to_string()) }) }) .unwrap(); diff --git a/bundler/tests/fixture/issue-1155-1-fn/output/entry.ts b/bundler/tests/fixture/issue-1155-1-fn/output/entry.ts index c63408c1a29..b160f9c1145 100644 --- a/bundler/tests/fixture/issue-1155-1-fn/output/entry.ts +++ b/bundler/tests/fixture/issue-1155-1-fn/output/entry.ts @@ -1,7 +1,8 @@ function a() { console.log("a"); } +const a1 = a; function b() { - a(); + a1(); } b(); diff --git a/bundler/tests/fixture/issue-1155-2-var/output/entry.ts b/bundler/tests/fixture/issue-1155-2-var/output/entry.ts index ca091bc5b91..39cc681f98b 100644 --- a/bundler/tests/fixture/issue-1155-2-var/output/entry.ts +++ b/bundler/tests/fixture/issue-1155-2-var/output/entry.ts @@ -1,5 +1,6 @@ const a = 'a'; +const a1 = a; function b() { - return a; + return a1; } b(); diff --git a/bundler/tests/fixture/issue-1155-3-class/output/entry.ts b/bundler/tests/fixture/issue-1155-3-class/output/entry.ts index bda571dbe4a..60b388c21ec 100644 --- a/bundler/tests/fixture/issue-1155-3-class/output/entry.ts +++ b/bundler/tests/fixture/issue-1155-3-class/output/entry.ts @@ -1,6 +1,7 @@ class a { } +const a1 = a; function b() { - return new a(); + return new a1(); } b(); diff --git a/spack/tests/pass/deno-001/simple-4/output/entry.js b/spack/tests/pass/deno-001/simple-4/output/entry.js index c36df896260..50b8c51271a 100644 --- a/spack/tests/pass/deno-001/simple-4/output/entry.js +++ b/spack/tests/pass/deno-001/simple-4/output/entry.js @@ -1,10 +1,10 @@ function deferred() { } +const deferred1 = deferred; class MuxAsyncIterator { constructor(){ this.signal = deferred(); } } const MuxAsyncIterator1 = MuxAsyncIterator; -const deferred1 = deferred; console.log(deferred1, MuxAsyncIterator1); diff --git a/spack/tests/pass/pr-1105/example-5/output/entry.js b/spack/tests/pass/pr-1105/example-5/output/entry.js index 1289cca5f69..212bffe4585 100644 --- a/spack/tests/pass/pr-1105/example-5/output/entry.js +++ b/spack/tests/pass/pr-1105/example-5/output/entry.js @@ -1,2 +1,3 @@ const a = "a"; -console.log(a); +const a1 = a; +console.log(a1);