From 1af1840d010c9ed5443c446dd43939a4fe3452a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EB=8F=99=EC=9C=A4?= Date: Wed, 16 Sep 2020 18:28:10 +0900 Subject: [PATCH] Fix swc_bundler (#1075) swc_bundler: - Skip least_common_ancestor for roots. - Correct planning for circular imports mixed with normal imports. - Correct merging of circular imports mixed with normal imports. --- bundler/Cargo.toml | 2 +- bundler/src/bundler/chunk/circular.rs | 53 +- bundler/src/bundler/chunk/circular/tests.rs | 83 +++ bundler/src/bundler/chunk/cjs.rs | 4 +- bundler/src/bundler/chunk/export.rs | 35 +- bundler/src/bundler/chunk/merge.rs | 46 +- bundler/src/bundler/chunk/mod.rs | 3 +- bundler/src/bundler/chunk/plan/lca.rs | 14 +- bundler/src/bundler/chunk/plan/mod.rs | 532 +++++++++--------- bundler/src/bundler/chunk/plan/tests.rs | 135 ++++- bundler/src/bundler/chunk/remark.rs | 26 +- bundler/src/bundler/load.rs | 6 +- bundler/src/bundler/mod.rs | 4 +- bundler/src/util.rs | 36 +- ecmascript/Cargo.toml | 4 +- ecmascript/transforms/Cargo.toml | 2 +- .../src/optimization/simplify/dce/mod.rs | 12 +- .../optimization/simplify/dce/side_effect.rs | 2 +- .../src/optimization/simplify/inlining/mod.rs | 2 +- .../optimization/simplify/inlining/scope.rs | 18 +- spack/.gitignore | 3 +- spack/tests/deno-std/.swcrc | 9 + .../pass/circular/imports-same/input/a.js | 5 + .../pass/circular/imports-same/input/b.js | 5 + .../circular/imports-same/input/common-foo.js | 1 + .../circular/imports-same/input/common.js | 1 + .../pass/circular/imports-same/input/entry.js | 1 + .../circular/imports-same/output/entry.js | 4 + .../tests/pass/circular/mixed/output/entry.js | 2 +- .../pass/export/complex-1/output/entry.js | 1 - 30 files changed, 686 insertions(+), 365 deletions(-) create mode 100644 bundler/src/bundler/chunk/circular/tests.rs create mode 100644 spack/tests/deno-std/.swcrc create mode 100644 spack/tests/pass/circular/imports-same/input/a.js create mode 100644 spack/tests/pass/circular/imports-same/input/b.js create mode 100644 spack/tests/pass/circular/imports-same/input/common-foo.js create mode 100644 spack/tests/pass/circular/imports-same/input/common.js create mode 100644 spack/tests/pass/circular/imports-same/input/entry.js create mode 100644 spack/tests/pass/circular/imports-same/output/entry.js diff --git a/bundler/Cargo.toml b/bundler/Cargo.toml index b9f3f04574e..28834f51254 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.7.1" +version = "0.7.2" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] diff --git a/bundler/src/bundler/chunk/circular.rs b/bundler/src/bundler/chunk/circular.rs index c1910084daa..a38621557dc 100644 --- a/bundler/src/bundler/chunk/circular.rs +++ b/bundler/src/bundler/chunk/circular.rs @@ -2,13 +2,16 @@ use super::{ merge::{ImportDropper, Unexporter}, plan::{CircularPlan, Plan}, }; -use crate::{bundler::load::TransformedModule, Bundler, Load, ModuleId, Resolve}; +use crate::{bundler::load::TransformedModule, util::CHashSet, Bundler, Load, ModuleId, Resolve}; use anyhow::{Context, Error}; use std::borrow::Borrow; use swc_common::DUMMY_SP; use swc_ecma_ast::*; use swc_ecma_visit::{noop_visit_type, FoldWith, Node, Visit, VisitMutWith, VisitWith}; +#[cfg(test)] +mod tests; + /// Circular imports are hard to handle. /// /// We use some dedicated method to handle circular dependencies. @@ -22,6 +25,7 @@ where plan: &Plan, circular_plan: &CircularPlan, entry_id: ModuleId, + merged: &CHashSet, ) -> Result { assert!( circular_plan.chunks.len() >= 1, @@ -29,28 +33,42 @@ where circular_plan ); let entry_module = self.scope.get_module(entry_id).unwrap(); + let direct_deps = entry_module + .imports + .specifiers + .iter() + .map(|v| v.0.module_id) + .chain(entry_module.exports.reexports.iter().map(|v| v.0.module_id)) + .collect::>(); let modules = circular_plan .chunks .iter() .map(|&id| self.scope.get_module(id).unwrap()) .collect::>(); - + merged.insert(entry_id); let mut entry = self - .merge_modules(plan, entry_id, false, true) + .merge_modules(plan, entry_id, false, true, merged) .context("failed to merge dependency of a cyclic module")?; entry.visit_mut_with(&mut ImportDropper { imports: &entry_module.imports, }); // print_hygiene("entry:drop_imports", &self.cm, &entry); + let mut deps = circular_plan.chunks.clone(); + deps.sort_by_key(|&dep| (!direct_deps.contains(&dep), dep)); - for &dep in &*circular_plan.chunks { + for dep in deps { if dep == entry_id { continue; } + if !merged.insert(dep) { + log::debug!("[circular merge] Already merged: {:?}", dep); + continue; + } + log::debug!("Circular merge: {:?}", dep); - let new_module = self.merge_two_circular_modules(plan, &modules, entry, dep)?; + let new_module = self.merge_two_circular_modules(plan, &modules, entry, dep, merged)?; entry = new_module; @@ -68,10 +86,11 @@ where _circular_modules: &[TransformedModule], mut entry: Module, dep: ModuleId, + merged: &CHashSet, ) -> Result { self.run(|| { let mut dep = self - .merge_modules(plan, dep, false, false) + .merge_modules(plan, dep, false, true, merged) .context("failed to merge dependency of a cyclic module")?; // print_hygiene("dep:init", &self.cm, &dep); @@ -94,7 +113,7 @@ fn merge_respecting_order(mut entry: Vec, mut dep: Vec) // While looping over items from entry, we check for dependency. loop { if entry.is_empty() { - log::debug!("entry is empty"); + log::trace!("entry is empty"); break; } let item = entry.drain(..=0).next().unwrap(); @@ -103,7 +122,7 @@ fn merge_respecting_order(mut entry: Vec, mut dep: Vec) if dep.is_empty() { log::trace!("dep is empty"); new.push(item); - new.extend(entry); + new.append(&mut entry); break; } @@ -121,7 +140,7 @@ fn merge_respecting_order(mut entry: Vec, mut dep: Vec) if let Some(pos) = dependency_index(&dep[0], &[&item]) { log::trace!("Found reverse depndency (index[0]): {}", pos); - new.extend(entry.drain(..=pos)); + new.push(item); new.extend(dep.drain(..=0)); continue; } @@ -129,16 +148,19 @@ fn merge_respecting_order(mut entry: Vec, mut dep: Vec) if let Some(pos) = dependency_index(&dep[0], &entry) { log::trace!("Found reverse depndency: {}", pos); + new.push(item); new.extend(entry.drain(..=pos)); new.extend(dep.drain(..=0)); continue; } - log::debug!("No dependency"); + log::trace!("No dependency"); new.push(item); } + new.extend(entry); + // Append remaining statements. new.extend(dep); @@ -176,8 +198,12 @@ where for (idx, dep) in self.deps.iter().enumerate() { match dep.borrow() { - ModuleItem::Stmt(Stmt::Decl(Decl::Class(decl))) => { - log::debug!( + ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { + decl: Decl::Class(decl), + .. + })) + | ModuleItem::Stmt(Stmt::Decl(Decl::Class(decl))) => { + log::trace!( "Decl (from dep) = {}{:?}, Ident = {}{:?}", decl.ident.sym, decl.ident.span.ctxt, @@ -186,10 +212,11 @@ where ); if decl.ident.sym == i.sym && decl.ident.span.ctxt == i.span.ctxt { self.idx = Some(idx); - log::info!("Index is {}", idx); + log::debug!("Index is {}", idx); break; } } + _ => {} } } diff --git a/bundler/src/bundler/chunk/circular/tests.rs b/bundler/src/bundler/chunk/circular/tests.rs new file mode 100644 index 00000000000..d721692a7df --- /dev/null +++ b/bundler/src/bundler/chunk/circular/tests.rs @@ -0,0 +1,83 @@ +use super::*; +use swc_common::{sync::Lrc, FileName, SourceMap}; +use swc_ecma_parser::{lexer::Lexer, JscTarget, Parser, StringInput, Syntax}; +use swc_ecma_utils::drop_span; +use testing::assert_eq; + +fn parse(cm: Lrc, name: &str, src: &str) -> Module { + let fm = cm.new_source_file(FileName::Custom(name.into()), src.into()); + let lexer = Lexer::new( + Syntax::default(), + JscTarget::Es2020, + StringInput::from(&*fm), + None, + ); + let mut parser = Parser::new_from(lexer); + + let module = parser.parse_module().unwrap(); + + drop_span(module) +} + +#[track_caller] +fn assert_merge_respecting_order(modules: &[&str], output: &str) { + for i in 0..modules.len() { + log::info!("[{}] Testing", i); + ::testing::run_test2(false, |cm, _handler| { + let mut entry = parse(cm.clone(), &format!("entry-{}", i), modules[i]).body; + + for j in 0..modules.len() { + if i == j { + continue; + } + + let dep = parse(cm.clone(), &format!("deps-{}-{}", i, j), modules[j]); + entry = merge_respecting_order(entry, dep.body); + } + + let output = parse(cm.clone(), "output", output); + assert_eq!(entry, output.body, "[{}]", i); + + log::info!("[{}] Success", i); + + Ok(()) + }) + .unwrap() + } +} + +#[test] +fn simple_two() { + assert_merge_respecting_order( + &["export class A {}", "export class B extends A {}"], + " + export class A {} + export class B extends A {} + ", + ); +} + +#[track_caller] +fn assert_dependency_index(entry: &str, dep: &str, expected: usize) { + ::testing::run_test2(false, |cm, _handler| { + let entry = parse(cm.clone(), "entry", entry); + let dep = parse(cm.clone(), "dep", dep); + + let calculated = dependency_index(&entry.body[0], &dep.body); + + assert_eq!(calculated, Some(expected)); + + Ok(()) + }) + .unwrap(); +} + +#[test] +fn dep_index_class() { + assert_dependency_index("class A extends B {}", "class B {}", 0); +} + +#[test] +fn dep_index_export_class() { + assert_dependency_index("class A extends B {}", "export class B {}", 0); +} diff --git a/bundler/src/bundler/chunk/cjs.rs b/bundler/src/bundler/chunk/cjs.rs index 1769ec6c3d6..614fc166a28 100644 --- a/bundler/src/bundler/chunk/cjs.rs +++ b/bundler/src/bundler/chunk/cjs.rs @@ -43,7 +43,7 @@ where dep_info: &TransformedModule, targets: &mut Vec, ) -> Result<(), Error> { - log::info!("Merging as a common js module: {}", info.fm.name); + log::debug!("Merging as a common js module: {}", info.fm.name); // If src is none, all requires are transpiled let mut v = RequireReplacer { is_entry, @@ -315,7 +315,7 @@ impl VisitMut for RequireReplacer { self.replaced = true; *node = load.clone(); - log::debug!("Found, and replacing require"); + log::trace!("Found, and replacing require"); } } _ => {} diff --git a/bundler/src/bundler/chunk/export.rs b/bundler/src/bundler/chunk/export.rs index 97cef0076fd..bd4c12a55d1 100644 --- a/bundler/src/bundler/chunk/export.rs +++ b/bundler/src/bundler/chunk/export.rs @@ -1,8 +1,8 @@ -use super::plan::Plan; +use super::plan::{NormalPlan, Plan}; use crate::{ bundler::load::{Specifier, TransformedModule}, - util::IntoParallelIterator, - Bundler, Load, Resolve, + util::{CHashSet, IntoParallelIterator}, + Bundler, Load, ModuleId, Resolve, }; use anyhow::{Context, Error}; #[cfg(feature = "concurrent")] @@ -52,15 +52,25 @@ where pub(super) fn merge_reexports( &self, plan: &Plan, + nomral_plan: &NormalPlan, entry: &mut Module, info: &TransformedModule, + merged: &CHashSet, ) -> Result<(), Error> { - log::debug!("merge_reexports: {}", info.fm.name); + log::trace!("merge_reexports: {}", info.fm.name); - let deps = (&*info.exports.reexports) + let mut reexports = info.exports.reexports.clone(); + // Remove transitive dependencies which is merged by parent moudle. + reexports.retain(|(src, _)| nomral_plan.chunks.contains(&src.module_id)); + + let deps = reexports .into_par_iter() .map(|(src, specifiers)| -> Result<_, Error> { - log::info!("Merging exports: {} <- {}", info.fm.name, src.src.value); + if !merged.insert(src.module_id) { + return Ok(None); + } + + log::debug!("Merging exports: {} <- {}", info.fm.name, src.src.value); let imported = self.scope.get_module(src.module_id).unwrap(); assert!(imported.is_es6, "Reexports are es6 only"); @@ -68,7 +78,7 @@ where info.helpers.extend(&imported.helpers); let mut dep = self - .merge_modules(plan, src.module_id, false, false) + .merge_modules(plan, src.module_id, false, false, merged) .with_context(|| { format!( "failed to merge for reexport: ({}):{} <= ({}):{}", @@ -98,12 +108,17 @@ where // print_hygiene(&format!("dep: unexport"), &self.cm, &dep); } - Ok((src, dep)) + Ok(Some((src, dep))) }) .collect::>(); for dep in deps { - let (src, dep) = dep?; + let dep = dep?; + let dep = match dep { + Some(v) => v, + None => continue, + }; + let (src, dep) = dep; // Replace import statement / require with module body let mut injector = ExportInjector { @@ -241,7 +256,7 @@ impl VisitMut for UnexportAsVar<'_> { } } None => { - log::debug!("Alias: {:?} -> {:?}", n.orig, self.dep_ctxt); + log::trace!("Alias: {:?} -> {:?}", n.orig, self.dep_ctxt); decls.push(VarDeclarator { span: n.span, diff --git a/bundler/src/bundler/chunk/merge.rs b/bundler/src/bundler/chunk/merge.rs index 9b8193314d4..78a104dcad6 100644 --- a/bundler/src/bundler/chunk/merge.rs +++ b/bundler/src/bundler/chunk/merge.rs @@ -10,12 +10,14 @@ use crate::{ use anyhow::{Context, Error}; #[cfg(feature = "concurrent")] use rayon::iter::ParallelIterator; +use retain_mut::RetainMut; use std::{borrow::Cow, mem::take}; use swc_atoms::js_word; use swc_common::{SyntaxContext, DUMMY_SP}; use swc_ecma_ast::*; use swc_ecma_utils::prepend_stmts; use swc_ecma_visit::{noop_fold_type, noop_visit_mut_type, Fold, FoldWith, VisitMut, VisitMutWith}; +use util::CHashSet; impl Bundler<'_, L, R> where @@ -29,15 +31,18 @@ where entry: ModuleId, is_entry: bool, force_not_cyclic: bool, + merged: &CHashSet, ) -> Result { self.run(|| { + merged.insert(entry); + let info = self.scope.get_module(entry).unwrap(); if !force_not_cyclic { // Handle circular imports if let Some(circular_plan) = plan.entry_as_circular(info.id) { - log::info!("Circular dependency detected: ({})", info.fm.name); - return Ok(self.merge_circular_modules(plan, circular_plan, entry)?); + log::debug!("Circular dependency detected: ({})", info.fm.name); + return Ok(self.merge_circular_modules(plan, circular_plan, entry, merged)?); } } @@ -65,7 +70,7 @@ where plan.normal.get(&info.id) ); - self.merge_reexports(plan, &mut entry, &info) + self.merge_reexports(plan, module_plan, &mut entry, &info, merged) .context("failed to merge reepxorts")?; let to_merge: Vec<_> = info @@ -94,8 +99,13 @@ where || { to_merge .into_par_iter() - .map(|(src, specifiers)| -> Result<_, Error> { + .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(); @@ -110,7 +120,7 @@ where // a <- b + chunk(c) // let mut dep = self - .merge_modules(plan, src.module_id, false, false) + .merge_modules(plan, src.module_id, false, false, merged) .with_context(|| { format!( "failed to merge: ({}):{} <= ({}):{}", @@ -194,7 +204,7 @@ where } // print_hygiene("dep:before-injection", &self.cm, &dep); - Ok((dep, dep_info)) + Ok(Some((dep, dep_info))) }) }) .collect::>() @@ -205,15 +215,19 @@ where .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)?; + let mut dep = self.merge_modules(plan, id, false, true, merged)?; 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((dep, dep_info)) + Ok(Some((dep, dep_info))) }) .collect::>(); @@ -228,7 +242,14 @@ where .map(|v| (v, true)) .chain(transitive_deps.into_iter().map(|v| (v, false))) { - let (mut dep, dep_info) = dep?; + 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) { @@ -307,8 +328,12 @@ where // } if is_entry { - entry.body.retain(|item| { + entry.body.retain_mut(|item| { match item { + ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(export)) => { + export.src = None; + } + ModuleItem::ModuleDecl(ModuleDecl::Import(import)) => { for (id, p) in &plan.normal { if import.span.ctxt == self.scope.get_module(*id).unwrap().ctxt() { @@ -352,6 +377,7 @@ where } } } + _ => {} } diff --git a/bundler/src/bundler/chunk/mod.rs b/bundler/src/bundler/chunk/mod.rs index 7070a6bb063..f8568114219 100644 --- a/bundler/src/bundler/chunk/mod.rs +++ b/bundler/src/bundler/chunk/mod.rs @@ -44,6 +44,7 @@ where entries: HashMap, ) -> Result, Error> { let plan = self.determine_entries(entries).context("failed to plan")?; + let merged = Default::default(); Ok((&*plan.entries) .into_par_iter() @@ -58,7 +59,7 @@ where .clone(); let module = self - .merge_modules(&plan, entry, true, false) + .merge_modules(&plan, entry, true, false, &merged) .context("failed to merge module") .unwrap(); // TODO diff --git a/bundler/src/bundler/chunk/plan/lca.rs b/bundler/src/bundler/chunk/plan/lca.rs index 2033438d863..1d4ad449270 100644 --- a/bundler/src/bundler/chunk/plan/lca.rs +++ b/bundler/src/bundler/chunk/plan/lca.rs @@ -13,6 +13,13 @@ pub(super) fn least_common_ancestor(g: &ModuleGraph, module_ids: &[ModuleId]) -> return module_ids[0]; } + // Check for roots + for &mid in module_ids { + if g.neighbors_directed(mid, Incoming).count() == 0 { + return mid; + } + } + let first = module_ids[0]; let second = module_ids[1]; @@ -22,11 +29,14 @@ pub(super) fn least_common_ancestor(g: &ModuleGraph, module_ids: &[ModuleId]) -> } if let Some(id) = check_itself_and_parent(g, &[first], &[second]) { - log::info!("Found lca: {:?}", id); + log::debug!("Found lca: {:?}", id); return id; } - unreachable!("failed to calculagte least common ancestor") + unreachable!( + "failed to calculate least common ancestors of {:?}", + module_ids + ) } return module_ids diff --git a/bundler/src/bundler/chunk/plan/mod.rs b/bundler/src/bundler/chunk/plan/mod.rs index 3952061d522..12fd14dace6 100644 --- a/bundler/src/bundler/chunk/plan/mod.rs +++ b/bundler/src/bundler/chunk/plan/mod.rs @@ -4,8 +4,16 @@ use crate::{ }; use anyhow::{bail, Error}; use lca::least_common_ancestor; -use petgraph::{algo::all_simple_paths, graphmap::DiGraphMap, visit::Bfs}; -use std::collections::{hash_map::Entry, HashMap}; +use petgraph::{ + algo::all_simple_paths, + graphmap::DiGraphMap, + visit::Bfs, + EdgeDirection::{Incoming, Outgoing}, +}; +use std::{ + collections::{hash_map::Entry, HashMap, HashSet}, + ops::{Deref, DerefMut}, +}; mod lca; #[cfg(test)] @@ -13,89 +21,75 @@ mod tests; #[derive(Debug, Default)] struct PlanBuilder { - entry_graph: ModuleGraph, + /// A hashmap to check if a module import is circular. + /// + /// 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)>, /// Graph to compute direct dependencies (direct means it will be merged /// directly) - tracking_graph: ModuleGraph, + direct_deps: ModuleGraph, - circular: HashMap>, - direct_deps: HashMap>, - - /// Used to calcuate transitive dependencies. - reverse: HashMap>, - - /// Used for normalization - /// - /// This is required because we cannot know the order file is - /// loaded. It means we cannot know order - /// calls to add_to_graph. - /// Thus, we cannot track import order in add_to_graph. - pending_direct_deps: HashMap>, + circular: Circulars, kinds: HashMap, } +#[derive(Debug, Default)] +struct Circulars(Vec>); + +impl Circulars { + pub fn get(&self, id: ModuleId) -> Option<&HashSet> { + let pos = self.0.iter().position(|set| set.contains(&id))?; + + Some(&self.0[pos]) + } + // pub fn remove(&mut self, id: ModuleId) -> Option> { + // let pos = self.0.iter().position(|set| set.contains(&id))?; + // Some(self.0.remove(pos)) + // } +} + +impl Deref for Circulars { + type Target = Vec>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Circulars { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + impl PlanBuilder { fn mark_as_circular(&mut self, src: ModuleId, imported: ModuleId) { - if let Some(v) = self.circular.get_mut(&src) { - if !v.contains(&src) { - v.push(src); + for set in self.circular.iter_mut() { + if set.contains(&src) || set.contains(&imported) { + set.insert(src); + set.insert(imported); + return; } - if !v.contains(&imported) { - v.push(imported); - } - return; } - if let Some(v) = self.circular.iter_mut().find_map(|(_, v)| { - if v.contains(&src) || v.contains(&imported) { - Some(v) - } else { - None - } - }) { - if !v.contains(&src) { - v.push(src); - } - if !v.contains(&imported) { - v.push(imported); - } - return; - } - - self.circular.insert(src, vec![imported]); + let mut set = HashSet::default(); + set.insert(src); + set.insert(imported); + self.circular.push(set); } fn is_circular(&self, id: ModuleId) -> bool { - if self.circular.get(&id).is_some() { - return true; + for set in self.circular.iter() { + if set.contains(&id) { + return true; + } } - self.circular - .iter() - .any(|(_, v)| v.iter().any(|&v| v == id)) - } - - fn try_add_direct_dep(&mut self, root_id: ModuleId, dep: ModuleId, dep_of_dep: ModuleId) { - if let None = self.tracking_graph.add_edge(root_id, dep_of_dep, 0) { - if self.circular.contains_key(&dep_of_dep) { - self.direct_deps.entry(root_id).or_default().push(dep); - return; - } - - // Track direct dependencies, but exclude if it will be recursively merged. - self.direct_deps.entry(dep).or_default().push(dep_of_dep); - } else { - if self.circular.contains_key(&dep_of_dep) { - return; - } - - self.pending_direct_deps - .entry(dep) - .or_default() - .push(dep_of_dep); - } + false } } @@ -148,6 +142,19 @@ where &self, entries: HashMap, ) -> Result { + let plan = self.calculate_plan(entries)?; + let plan = self.handle_duplicates(plan); + + Ok(plan) + } + + /// 1. For entry -> a -> b -> a, entry -> a->c, entry -> b -> c, + /// we change c as transitive dependancy of entry. + fn handle_duplicates(&self, plan: Plan) -> Plan { + plan + } + + fn calculate_plan(&self, entries: HashMap) -> Result { let mut builder = PlanBuilder::default(); for (name, module) in entries { @@ -156,16 +163,16 @@ where None => {} } - self.add_to_graph(&mut builder, module.id, module.id); + self.add_to_graph(&mut builder, module.id, &mut vec![]); } let mut metadata = HashMap::::default(); // Draw dependency graph to calculte for (id, _) in &builder.kinds { - let mut bfs = Bfs::new(&builder.entry_graph, *id); + let mut bfs = Bfs::new(&builder.direct_deps, *id); - while let Some(dep) = bfs.next(&builder.entry_graph) { + while let Some(dep) = bfs.next(&builder.direct_deps) { if dep == *id { // Useless continue; @@ -201,160 +208,182 @@ where plans.bundle_kinds.insert(*id, kind.clone()); } - // Fix direct dependencies. See the doc of pending_direct_deps for more - // information. - for (entry, deps) in builder.pending_direct_deps.drain() { - for (key, direct_deps) in builder.direct_deps.iter_mut() { - if direct_deps.contains(&entry) { - if *key == entry { - direct_deps.extend_from_slice(&deps); - } else { - direct_deps.retain(|&id| { - if *key == id { - return true; - } - if deps.contains(&id) { - return false; - } - true - }); - } - } - } - - if !builder.direct_deps.contains_key(&entry) { - builder.direct_deps.insert(entry, deps); - } - } - // Handle circular imports - for (k, members) in &builder.circular { - for (_entry, deps) in builder.direct_deps.iter_mut() { - deps.retain(|v| !members.contains(v)); - } + for (root_entry, _) in builder.kinds.iter() { + let mut bfs = Bfs::new(&builder.direct_deps, *root_entry); - builder.direct_deps.remove(k); - } + while let Some(entry) = bfs.next(&builder.direct_deps) { + let deps: Vec<_> = builder + .direct_deps + .neighbors_directed(entry, Outgoing) + .collect(); - // Calculate actual chunking plans - for (id, _) in builder.kinds.iter() { - let mut bfs = Bfs::new(&builder.entry_graph, *id); + for dep in deps { + // Check if it's circular. + if let Some(members) = builder.circular.get(dep) { + // Exclude circular imnports from normal dependencies + for &circular_member in members { + if entry == circular_member { + continue; + } - let mut prev = *id; - - while let Some(dep) = bfs.next(&builder.entry_graph) { - if dep == *id { - // Useless - continue; - } - // Check if it's circular. - if builder.is_circular(dep) { - // Entry is `dep`. - match plans.circular.entry(dep) { - // Already added - Entry::Occupied(_) => { - // TODO: assert! - } - - // We need to mark modules as circular. - Entry::Vacant(e) => { - let plan = e.insert(CircularPlan::default()); - if let Some(mut v) = builder.circular.remove(&dep) { - if let Some(index) = v.iter().position(|&id| id == dep) { - v.remove(index); - } - plan.chunks.extend(v); + if builder + .direct_deps + .remove_edge(dep, circular_member) + .is_some() + { + log::debug!( + "[circular] Removing {:?} => {:?}", + dep, + circular_member + ); } } - } - if !builder.is_circular(prev) { - plans.normal.entry(prev).or_default().chunks.push(dep); + // Add circular plans + match plans.circular.entry(dep) { + // Already added + Entry::Occupied(_) => { + // TODO: assert! + } + + // We need to mark modules as circular. + Entry::Vacant(e) => { + let circular_plan = e.insert(CircularPlan::default()); + if let Some(v) = builder.circular.get(dep) { + circular_plan + .chunks + .extend(v.iter().copied().filter(|&v| v != dep)); + } + } + } + + // if !builder.is_circular(dep) { + // plans.normal.entry(dep).or_default().chunks. + // push(entry); } } } - prev = dep; } } - for (id, deps) in builder.direct_deps.drain() { - for &dep in &deps { - if builder.circular.get(&id).is_some() { - plans.normal.entry(id).or_default().chunks.push(dep); - continue; - } - let is_es6 = self.scope.get_module(dep).unwrap().is_es6; - let dependants = builder.reverse.get(&dep).map(|s| &**s).unwrap_or(&[]); + for (root_entry, _) in &builder.kinds { + let root_entry = *root_entry; + let mut bfs = Bfs::new(&builder.direct_deps, root_entry); - if metadata.get(&dep).map(|md| md.bundle_cnt).unwrap_or(0) == 1 { - log::info!("Module dep: {} => {}", id, dep); + while let Some(entry) = bfs.next(&builder.direct_deps) { + let deps: Vec<_> = builder + .direct_deps + .neighbors_directed(entry, Outgoing) + .collect(); + + for &dep in &deps { + if builder.is_circular(entry) { + log::debug!( + "Adding a circular dependencuy {:?} to normal entry {:?}", + entry, + root_entry + ); + plans.normal.entry(entry).or_default().chunks.push(dep); + continue; + } + let is_es6 = self.scope.get_module(entry).unwrap().is_es6; + let dependants = builder + .direct_deps + .neighbors_directed(dep, Incoming) + .collect::>(); + + if metadata.get(&dep).map(|md| md.bundle_cnt).unwrap_or(0) == 1 { + log::debug!("{:?} depends on {:?}", entry, dep); + + // Common js support. + if !is_es6 { + // Dependancy of + // + // a -> b + // b -> c + // + // results in + // + // a <- b + // b <- c + // + if dependants.len() <= 1 { + plans.normal.entry(entry).or_default().chunks.push(dep); + continue; + } + + // We now have a module depended by multiple modules. Let's say + // + // a -> b + // a -> c + // b -> c + // + // results in + // + // a <- b + // a <- c + let module = least_common_ancestor(&builder.direct_deps, &dependants); + + let normal_plan = plans.normal.entry(module).or_default(); + + for &dep in &deps { + if !normal_plan.chunks.contains(&dep) + && !normal_plan.transitive_chunks.contains(&dep) + { + if dependants.contains(&module) { + // `entry` depends on `module` directly + normal_plan.chunks.push(dep); + } else { + normal_plan.transitive_chunks.push(dep); + } + } + } - // Common js support. - if !is_es6 { - // Dependancy of - // - // a -> b - // b -> c - // - // results in - // - // a <- b - // b <- c - // - if dependants.len() <= 1 { - plans.normal.entry(id).or_default().chunks.push(dep); continue; } - // We now have a module depended by multiple modules. Let's say - // - // a -> b - // a -> c - // b -> c - // - // results in - // - // a <- b - // a <- c - let module = least_common_ancestor(&builder.entry_graph, dependants); - - let normal_plan = plans.normal.entry(module).or_default(); - normal_plan.transitive_chunks.reserve(deps.len()); - - for &dep in &deps { - if !normal_plan.chunks.contains(&dep) - && !normal_plan.transitive_chunks.contains(&dep) + if 2 <= dependants.len() { + // Should be merged as a transitive dependency. + let higher_module = if plans.entries.contains(&dependants[0]) { + dependants[0] + } else if dependants.len() == 2 + && plans.entries.contains(&dependants[1]) { - if dependants.contains(&module) { - // `entry` depends on `module` directly + dependants[1] + } else { + least_common_ancestor(&builder.direct_deps, &dependants) + }; + + if dependants.len() == 2 && dependants.contains(&higher_module) { + let mut entry = + *dependants.iter().find(|&&v| v != higher_module).unwrap(); + + // We choose higher node if import is circular + if builder.is_circular(entry) { + entry = higher_module; + } + + let normal_plan = plans.normal.entry(entry).or_default(); + if !normal_plan.chunks.contains(&dep) { normal_plan.chunks.push(dep); - } else { - normal_plan.transitive_chunks.push(dep); + } + } else { + let t = &mut plans + .normal + .entry(higher_module) + .or_default() + .transitive_chunks; + if !t.contains(&dep) { + t.push(dep) } } + } else { + // Direct dependency. + plans.normal.entry(entry).or_default().chunks.push(dep); } continue; } - - if 2 <= dependants.len() { - // Should be merged as a transitive dependency. - let module = least_common_ancestor(&builder.entry_graph, dependants); - - if dependants.len() == 2 && dependants.contains(&module) { - let entry = *dependants.iter().find(|&&v| v != module).unwrap(); - plans.normal.entry(entry).or_default().chunks.push(dep); - } else { - let t = &mut plans.normal.entry(module).or_default().transitive_chunks; - if !t.contains(&dep) { - t.push(dep) - } - } - } else { - // Direct dependency. - plans.normal.entry(id).or_default().chunks.push(dep); - } - - continue; } } } @@ -364,86 +393,67 @@ where } // dbg!(&plans); - Ok(plans) } - fn add_to_graph(&self, builder: &mut PlanBuilder, module_id: ModuleId, root_id: ModuleId) { - let contains = builder.entry_graph.contains_node(module_id); - - builder.entry_graph.add_node(module_id); - builder.tracking_graph.add_node(module_id); + fn add_to_graph( + &self, + builder: &mut PlanBuilder, + module_id: ModuleId, + path: &mut Vec, + ) { + builder.direct_deps.add_node(module_id); let m = self .scope .get_module(module_id) .expect("failed to get module"); - for (src, _) in &m.imports.specifiers { - log::trace!("({:?}) {:?} => {:?}", root_id, module_id, src.module_id); + for src in m + .imports + .specifiers + .iter() + .map(|v| &v.0) + .chain(m.exports.reexports.iter().map(|v| &v.0)) + { + log::debug!("Dependency: {:?} => {:?}", module_id, src.module_id); + + 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((module_id, src.module_id)); + + if !builder.all_deps.contains(&(src.module_id, module_id)) { + path.push(module_id); + self.add_to_graph(builder, src.module_id, path); + assert_eq!(path.pop(), Some(module_id)); + } } // Prevent dejavu - if contains { - for (src, _) in &m.imports.specifiers { - if builder.entry_graph.contains_edge(module_id, src.module_id) { - log::debug!( - "({:?}) Maybe circular dep: {:?} => {:?}", - root_id, - module_id, - src.module_id - ); + for (src, _) in &m.imports.specifiers { + if builder.all_deps.contains(&(src.module_id, module_id)) { + log::debug!("Circular dep: {:?} => {:?}", module_id, src.module_id); - // builder.mark_as_circular(module_id, src.module_id); + builder.mark_as_circular(module_id, src.module_id); - let circular_paths = all_simple_paths::, _>( - &builder.entry_graph, - src.module_id, - module_id, - 0, - None, - ) - .collect::>(); + let circular_paths = all_simple_paths::, _>( + &builder.direct_deps, + src.module_id, + module_id, + 0, + None, + ) + .collect::>(); - for path in circular_paths { - for dep in path { - builder.mark_as_circular(module_id, dep) - } + for path in circular_paths { + for dep in path { + builder.mark_as_circular(module_id, dep) } - - return; - } else { - builder.entry_graph.add_edge( - module_id, - src.module_id, - if src.is_unconditional { 2 } else { 1 }, - ); } } } - - for (src, _) in m.imports.specifiers.iter().chain(&m.exports.reexports) { - self.add_to_graph(builder, src.module_id, root_id); - - builder.entry_graph.add_edge( - module_id, - src.module_id, - if src.is_unconditional { 2 } else { 1 }, - ); - if self.scope.get_module(src.module_id).unwrap().is_es6 { - builder.try_add_direct_dep(root_id, module_id, src.module_id); - } else { - // Common js support. - let v = builder.direct_deps.entry(module_id).or_default(); - if !v.contains(&src.module_id) { - v.push(src.module_id); - } - } - - let rev = builder.reverse.entry(src.module_id).or_default(); - if !rev.contains(&module_id) { - rev.push(module_id); - } - } } } diff --git a/bundler/src/bundler/chunk/plan/tests.rs b/bundler/src/bundler/chunk/plan/tests.rs index 67e65598d79..dbd09c82007 100644 --- a/bundler/src/bundler/chunk/plan/tests.rs +++ b/bundler/src/bundler/chunk/plan/tests.rs @@ -54,6 +54,7 @@ fn assert_normal_transitive( ) } +#[track_caller] fn assert_circular(t: &mut Tester, p: &Plan, entry: &str, members: &[&str]) { assert_eq!( p.circular[&t.id(&format!("{}.js", entry))] @@ -66,6 +67,9 @@ fn assert_circular(t: &mut Tester, p: &Plan, entry: &str, members: &[&str]) { .map(|s| format!("{}.js", s)) .map(|s| t.id(&s)) .collect::>(), + "[circular] `{}` should merge {:?}", + entry, + members ); } @@ -101,7 +105,7 @@ fn concurrency_001() { let mut entries = HashMap::default(); entries.insert("main.js".to_string(), module); - let p = t.bundler.determine_entries(entries)?; + let p = t.bundler.calculate_plan(entries)?; assert_eq!(p.circular.len(), 0); @@ -145,7 +149,7 @@ fn concurrency_002() { let mut entries = HashMap::default(); entries.insert("main.js".to_string(), module); - let p = t.bundler.determine_entries(entries)?; + let p = t.bundler.calculate_plan(entries)?; assert_eq!(p.circular.len(), 0); @@ -191,7 +195,7 @@ fn concurrency_003() { let mut entries = HashMap::default(); entries.insert("main.js".to_string(), module); - let p = t.bundler.determine_entries(entries)?; + let p = t.bundler.calculate_plan(entries)?; assert_eq!(p.circular.len(), 0); assert_eq!(p.normal.len(), 2); @@ -219,21 +223,12 @@ fn circular_001() { "a.js", " import { B } from './b' - - export class A { - method() { - return new B(); - } - } ", ) .file( "b.js", " import { A } from './a'; - - export class B extends A { - } ", ) .run(|t| { @@ -244,10 +239,12 @@ fn circular_001() { let mut entries = HashMap::default(); entries.insert("main.js".to_string(), module.clone()); - let p = t.bundler.determine_entries(entries)?; + let p = t.bundler.calculate_plan(entries)?; + + dbg!(&p); assert_circular(t, &p, "a", &["b"]); - assert_normal(t, &p, "main", &["a"]); + assert_normal(t, &p, "main", &["a", "b"]); assert_normal(t, &p, "a", &[]); assert_normal(t, &p, "b", &[]); @@ -276,7 +273,7 @@ fn transitive_001() { let mut entries = HashMap::default(); entries.insert("main.js".to_string(), module); - let p = t.bundler.determine_entries(entries)?; + let p = t.bundler.calculate_plan(entries)?; dbg!(&p); @@ -341,7 +338,7 @@ fn transitive_002() { let mut entries = HashMap::default(); entries.insert("main.js".to_string(), module); - let p = t.bundler.determine_entries(entries)?; + let p = t.bundler.calculate_plan(entries)?; dbg!(&p); @@ -393,7 +390,7 @@ fn cjs_001() { let mut entries = HashMap::default(); entries.insert("main.js".to_string(), module); - let p = t.bundler.determine_entries(entries)?; + let p = t.bundler.calculate_plan(entries)?; dbg!(&p); @@ -434,7 +431,7 @@ fn cjs_002() { let mut entries = HashMap::default(); entries.insert("main.js".to_string(), module); - let p = t.bundler.determine_entries(entries)?; + let p = t.bundler.calculate_plan(entries)?; dbg!(&p); @@ -482,7 +479,7 @@ fn cjs_003() { let mut entries = HashMap::default(); entries.insert("main.js".to_string(), module); - let p = t.bundler.determine_entries(entries)?; + let p = t.bundler.calculate_plan(entries)?; dbg!(&p); @@ -539,7 +536,7 @@ fn cjs_004() { let mut entries = HashMap::default(); entries.insert("main.js".to_string(), module); - let p = t.bundler.determine_entries(entries)?; + let p = t.bundler.calculate_plan(entries)?; dbg!(&p); @@ -592,7 +589,7 @@ fn cjs_005() { let mut entries = HashMap::default(); entries.insert("main.js".to_string(), module); - let p = t.bundler.determine_entries(entries)?; + let p = t.bundler.calculate_plan(entries)?; dbg!(&p); @@ -607,3 +604,99 @@ fn cjs_005() { Ok(()) }); } + +#[test] +fn deno_001() { + suite() + .file( + "main.js", + r#" + import { listenAndServe } from "./http-server"; + "#, + ) + .file( + "http-server.js", + r#" + import { BufReader, BufWriter } from "./io-bufio"; + import { bodyReader } from "./_io"; + "#, + ) + .file( + "_io.js", + r#" + import { BufReader, BufWriter } from "./io-bufio"; + import { ServerRequest, Response } from "./http-server"; + "#, + ) + .file( + "io-bufio.js", + r#" + "#, + ) + .run(|t| { + let module = t + .bundler + .load_transformed(&FileName::Real("main.js".into()))? + .unwrap(); + let mut entries = HashMap::default(); + entries.insert("main.js".to_string(), module); + + let p = t.bundler.calculate_plan(entries)?; + + dbg!(&p); + + assert_normal(t, &p, "main", &["http-server"]); + assert_normal(t, &p, "io-bufio", &[]); + + assert_circular(t, &p, "http-server", &["_io"]); + // assert_circular(t, &p, "_io", &["http-server"]); + + Ok(()) + }); +} + +#[test] +fn circular_002() { + suite() + .file( + "main.js", + " + import './a'; + ", + ) + .file( + "a.js", + " + import './b'; + ", + ) + .file( + "b.js", + " + import './c'; + ", + ) + .file( + "c.js", + " + import './a'; + ", + ) + .run(|t| { + let module = t + .bundler + .load_transformed(&FileName::Real("main.js".into()))? + .unwrap(); + let mut entries = HashMap::default(); + entries.insert("main.js".to_string(), module.clone()); + + let p = t.bundler.calculate_plan(entries)?; + + dbg!(&p); + + assert_normal(t, &p, "main", &["a"]); + assert_circular(t, &p, "a", &["b", "c"]); + + Ok(()) + }); +} diff --git a/bundler/src/bundler/chunk/remark.rs b/bundler/src/bundler/chunk/remark.rs index 7335aca9bd1..492b1985570 100644 --- a/bundler/src/bundler/chunk/remark.rs +++ b/bundler/src/bundler/chunk/remark.rs @@ -33,12 +33,14 @@ where }; dep = dep.fold_with(&mut v); - log::info!("Remark map: {:?}", v.remark_map); + if !v.remark_map.is_empty() { + log::debug!("Remark map: {:?}", v.remark_map); - // Swap syntax context. Although name is remark, it's actually - // swapping because ExportRenamer inserts two-side conversion - // rule. - self.remark(&mut dep, &v.remark_map); + // Swap syntax context. Although name is remark, it's actually + // swapping because ExportRenamer inserts two-side conversion + // rule. + self.remark(&mut dep, &v.remark_map); + } dep } @@ -63,7 +65,7 @@ struct ExportRenamer<'a> { impl ExportRenamer<'_> { /// Returns [SyntaxContext] for the name of variable. fn mark_as_remarking_required(&mut self, exported: Id, orig: Id) -> SyntaxContext { - log::info!("Remarking required: {:?} -> {:?}", exported, orig); + log::debug!("Remarking required: {:?} -> {:?}", exported, orig); let ctxt = SyntaxContext::empty().apply_mark(Mark::fresh(Mark::root())); self.remark_map @@ -167,7 +169,7 @@ impl Fold for ExportRenamer<'_> { let ident = if let Some(id) = ident { id } else { - log::info!("Dropping export default declaration because it's not used"); + log::debug!("Dropping export default declaration because it's not used"); return Stmt::Empty(EmptyStmt { span: DUMMY_SP }).into(); }; @@ -206,7 +208,7 @@ impl Fold for ExportRenamer<'_> { }))) } DefaultDecl::TsInterfaceDecl(_) => { - log::info!( + log::debug!( "Dropping export default declaration because ts interface declaration \ is not supported yet" ); @@ -239,7 +241,7 @@ impl Fold for ExportRenamer<'_> { }], }))) } else { - log::debug!("Removing default export expression as it's not imported"); + log::trace!("Removing default export expression as it's not imported"); // Expression statement cannot start with function ModuleItem::Stmt(Stmt::Expr(ExprStmt { @@ -323,7 +325,7 @@ impl Fold for ExportRenamer<'_> { definite: false, }) } else { - log::debug!( + log::trace!( "Removing export specifier {:?} as it's not imported", specifier ); @@ -417,7 +419,7 @@ impl Fold for ExportRenamer<'_> { // definite: false, // }) } else { - log::debug!( + log::trace!( "Removing export specifier {:?} as it's not imported (`unexport` \ is false, but it's not used)", specifier @@ -581,7 +583,7 @@ impl VisitMut for RemarkIdents<'_> { let id = (*n).to_id(); if let Some(&ctxt) = self.map.get(&id) { n.span = n.span.with_ctxt(ctxt); - log::info!("Remark: {:?} -> {:?}", id, ctxt) + log::debug!("Remark: {:?} -> {:?}", id, ctxt) } } } diff --git a/bundler/src/bundler/load.rs b/bundler/src/bundler/load.rs index 7aeef73e57a..225b6ec1796 100644 --- a/bundler/src/bundler/load.rs +++ b/bundler/src/bundler/load.rs @@ -65,7 +65,7 @@ where // In case of common module if let Some(cached) = self.scope.get_module_by_path(&file_name) { - log::info!("Cached: {}", file_name); + log::debug!("Cached: {}", file_name); return Ok(Some(cached)); } @@ -75,14 +75,14 @@ where .context("failed to analyze module")?; files.dedup_by_key(|v| v.1.clone()); - log::info!("Storing module: {}", file_name); + log::info!("({}) Storing module: {}", v.id, file_name); self.scope.store_module(v.clone()); // Load dependencies and store them in the `Scope` let results = files .into_par_iter() .map(|(_src, path)| { - log::debug!("loading dependency: {}", path); + log::trace!("loading dependency: {}", path); self.load_transformed(&path) }) .collect::>(); diff --git a/bundler/src/bundler/mod.rs b/bundler/src/bundler/mod.rs index 781d727c707..24837dd7e25 100644 --- a/bundler/src/bundler/mod.rs +++ b/bundler/src/bundler/mod.rs @@ -87,9 +87,9 @@ where ) -> Self { GLOBALS.set(&globals, || { let used_mark = Mark::fresh(Mark::root()); - log::info!("Used mark: {:?}", DUMMY_SP.apply_mark(used_mark).ctxt()); + log::debug!("Used mark: {:?}", DUMMY_SP.apply_mark(used_mark).ctxt()); let helper_ctxt = SyntaxContext::empty().apply_mark(Mark::fresh(Mark::root())); - log::info!("Helper ctxt: {:?}", helper_ctxt); + log::debug!("Helper ctxt: {:?}", helper_ctxt); Bundler { config, diff --git a/bundler/src/util.rs b/bundler/src/util.rs index a0df4d5d8ec..11f4c929ad9 100644 --- a/bundler/src/util.rs +++ b/bundler/src/util.rs @@ -2,6 +2,34 @@ use std::hash::Hash; use swc_common::{Span, SyntaxContext}; use swc_ecma_visit::{noop_visit_mut_type, VisitMut}; +#[derive(Debug)] +pub(crate) struct CHashSet +where + V: Eq + Hash, +{ + inner: CloneMap, +} + +impl CHashSet +where + V: Eq + Hash, +{ + pub fn insert(&self, v: V) -> bool { + self.inner.insert(v, ()).is_none() + } +} + +impl Default for CHashSet +where + V: Eq + Hash, +{ + fn default() -> Self { + Self { + inner: Default::default(), + } + } +} + #[derive(Debug)] pub(crate) struct CloneMap where @@ -50,13 +78,13 @@ where } #[cfg(feature = "concurrent")] - pub fn insert(&self, k: K, v: V) { - self.inner.insert(k, v); + pub fn insert(&self, k: K, v: V) -> Option { + self.inner.insert(k, v) } #[cfg(not(feature = "concurrent"))] - pub fn insert(&self, k: K, v: V) { - self.inner.borrow_mut().insert(k, v); + pub fn insert(&self, k: K, v: V) -> Option { + self.inner.borrow_mut().insert(k, v) } } diff --git a/ecmascript/Cargo.toml b/ecmascript/Cargo.toml index 4d515ff1adc..bc6498f1bd8 100644 --- a/ecmascript/Cargo.toml +++ b/ecmascript/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" license = "Apache-2.0/MIT" name = "swc_ecmascript" repository = "https://github.com/swc-project/swc.git" -version = "0.7.8" +version = "0.7.9" [features] codegen = ["swc_ecma_codegen"] @@ -24,7 +24,7 @@ swc_ecma_ast = {version = "0.31.0", path = "./ast"} swc_ecma_codegen = {version = "0.35.2", path = "./codegen", optional = true} swc_ecma_dep_graph = {version = "0.3.0", path = "./dep-graph", optional = true} swc_ecma_parser = {version = "0.37.2", path = "./parser", optional = true} -swc_ecma_transforms = {version = "0.23.13", path = "./transforms", optional = true} +swc_ecma_transforms = {version = "0.23.14", path = "./transforms", optional = true} swc_ecma_utils = {version = "0.21.0", path = "./utils", optional = true} swc_ecma_visit = {version = "0.17.2", path = "./visit", optional = true} diff --git a/ecmascript/transforms/Cargo.toml b/ecmascript/transforms/Cargo.toml index ee1e51759c6..273973e1436 100644 --- a/ecmascript/transforms/Cargo.toml +++ b/ecmascript/transforms/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" license = "Apache-2.0/MIT" name = "swc_ecma_transforms" repository = "https://github.com/swc-project/swc.git" -version = "0.23.13" +version = "0.23.14" [features] const-modules = ["dashmap"] diff --git a/ecmascript/transforms/src/optimization/simplify/dce/mod.rs b/ecmascript/transforms/src/optimization/simplify/dce/mod.rs index 7755a4b72e2..81b1f6450af 100644 --- a/ecmascript/transforms/src/optimization/simplify/dce/mod.rs +++ b/ecmascript/transforms/src/optimization/simplify/dce/mod.rs @@ -261,13 +261,13 @@ impl VisitMut for Dce<'_> { } fn visit_mut_expr_stmt(&mut self, node: &mut ExprStmt) { - log::debug!("ExprStmt ->"); + log::trace!("ExprStmt ->"); if self.is_marked(node.span) { return; } if self.should_include(&node.expr) { - log::debug!("\tIncluded"); + log::trace!("\tIncluded"); node.span = node.span.apply_mark(self.config.used_mark); self.mark(&mut node.expr); return; @@ -362,7 +362,7 @@ impl VisitMut for Dce<'_> { if self.marking_phase { if self.included.insert(i.to_id()) { - log::info!("{} is used", i.sym); + log::debug!("{} is used", i.sym); self.changed = true; } } @@ -404,7 +404,7 @@ impl VisitMut for Dce<'_> { } // Drop unused imports. - log::debug!("Removing unused import specifiers"); + log::trace!("Removing unused import specifiers"); import.specifiers.retain(|s| self.should_include(s)); if !import.specifiers.is_empty() { @@ -616,7 +616,7 @@ impl Dce<'_> { preserved.reserve(items.len()); loop { - log::info!("loop start"); + log::debug!("loop start"); self.changed = false; let mut idx = 0u32; @@ -737,7 +737,7 @@ impl Dce<'_> { { let old = self.marking_phase; self.marking_phase = true; - log::info!("Marking: {}", type_name::()); + log::debug!("Marking: {}", type_name::()); node.visit_mut_with(self); self.marking_phase = old; } diff --git a/ecmascript/transforms/src/optimization/simplify/dce/side_effect.rs b/ecmascript/transforms/src/optimization/simplify/dce/side_effect.rs index 93287243f51..14a3159909a 100644 --- a/ecmascript/transforms/src/optimization/simplify/dce/side_effect.rs +++ b/ecmascript/transforms/src/optimization/simplify/dce/side_effect.rs @@ -85,7 +85,7 @@ impl Visit for SideEffectVisitor<'_> { noop_visit_type!(); fn visit_expr(&mut self, node: &Expr, _: &dyn Node) { - log::debug!("Visit"); + log::trace!("Visit"); if self.found || node.is_pure_callee() { return; diff --git a/ecmascript/transforms/src/optimization/simplify/inlining/mod.rs b/ecmascript/transforms/src/optimization/simplify/inlining/mod.rs index 4bc02b7d793..4f7b6fd3261 100644 --- a/ecmascript/transforms/src/optimization/simplify/inlining/mod.rs +++ b/ecmascript/transforms/src/optimization/simplify/inlining/mod.rs @@ -774,7 +774,7 @@ impl Fold for Inlining<'_> { self.phase = Phase::Analysis; items = items.fold_children_with(self); - log::debug!("Switching to Inlining phase"); + log::trace!("Switching to Inlining phase"); // Inline self.phase = Phase::Inlining; diff --git a/ecmascript/transforms/src/optimization/simplify/inlining/scope.rs b/ecmascript/transforms/src/optimization/simplify/inlining/scope.rs index 994c666e577..be08ff2eedf 100644 --- a/ecmascript/transforms/src/optimization/simplify/inlining/scope.rs +++ b/ecmascript/transforms/src/optimization/simplify/inlining/scope.rs @@ -76,7 +76,7 @@ impl Inlining<'_> { }) { let v: VarInfo = v; - log::debug!("Hoisting a variable {:?}", id); + log::trace!("Hoisting a variable {:?}", id); if self.scope.unresolved_usages.contains(&id) { v.inline_prevented.set(true) @@ -192,7 +192,7 @@ impl Inlining<'_> { } if scope.kind == ScopeKind::Loop { - log::debug!("preventing inline as it's declared in a loop"); + log::trace!("preventing inline as it's declared in a loop"); self.scope.prevent_inline(&id); break; } @@ -342,11 +342,11 @@ impl<'a> Scope<'a> { log::trace!("found"); break; } - log::debug!("({}): {}: kind = {:?}", scope.depth(), id.0, scope.kind); + log::trace!("({}): {}: kind = {:?}", scope.depth(), id.0, scope.kind); match scope.kind { ScopeKind::Fn { .. } => { - log::debug!("{}: variable access from a nested function detected", id.0); + log::trace!("{}: variable access from a nested function detected", id.0); return true; } ScopeKind::Loop | ScopeKind::Cond => { @@ -363,7 +363,7 @@ impl<'a> Scope<'a> { pub fn add_read(&mut self, id: &Id) { if self.read_prevents_inlining(id) { - log::debug!("prevent inlining because of read: {}", id.0); + log::trace!("prevent inlining because of read: {}", id.0); self.prevent_inline(id) } @@ -402,11 +402,11 @@ impl<'a> Scope<'a> { if found { break; } - log::debug!("({}): {}: kind = {:?}", scope.depth(), id.0, scope.kind); + log::trace!("({}): {}: kind = {:?}", scope.depth(), id.0, scope.kind); match scope.kind { ScopeKind::Fn { .. } => { - log::debug!("{}: variable access from a nested function detected", id.0); + log::trace!("{}: variable access from a nested function detected", id.0); return true; } ScopeKind::Loop | ScopeKind::Cond => { @@ -423,7 +423,7 @@ impl<'a> Scope<'a> { pub fn add_write(&mut self, id: &Id, force_no_inline: bool) { if self.write_prevents_inline(id) { - log::debug!("prevent inlining because of write: {}", id.0); + log::trace!("prevent inlining because of write: {}", id.0); self.prevent_inline(id) } @@ -569,7 +569,7 @@ impl<'a> Scope<'a> { } pub fn prevent_inline(&self, id: &Id) { - log::debug!("({}) Prevent inlining: {:?}", self.depth(), id); + log::trace!("({}) Prevent inlining: {:?}", self.depth(), id); if let Some(v) = self.find_binding_from_current(id) { v.inline_prevented.set(true); diff --git a/spack/.gitignore b/spack/.gitignore index 8d0f61c986b..8b150ebdca4 100644 --- a/spack/.gitignore +++ b/spack/.gitignore @@ -1,3 +1,4 @@ /*.js *.d.ts -*.map \ No newline at end of file +*.map +deno-std/ \ No newline at end of file diff --git a/spack/tests/deno-std/.swcrc b/spack/tests/deno-std/.swcrc new file mode 100644 index 00000000000..fc5920ef248 --- /dev/null +++ b/spack/tests/deno-std/.swcrc @@ -0,0 +1,9 @@ +{ + "jsc": { + "target": "es2020", + "parser": { + "syntax": "typescript", + "decorators": true + } + } +} diff --git a/spack/tests/pass/circular/imports-same/input/a.js b/spack/tests/pass/circular/imports-same/input/a.js new file mode 100644 index 00000000000..bf86926ae67 --- /dev/null +++ b/spack/tests/pass/circular/imports-same/input/a.js @@ -0,0 +1,5 @@ +import { foo } from './common' + +console.log('a', foo); + +export const a = foo + 1; \ No newline at end of file diff --git a/spack/tests/pass/circular/imports-same/input/b.js b/spack/tests/pass/circular/imports-same/input/b.js new file mode 100644 index 00000000000..df412defd11 --- /dev/null +++ b/spack/tests/pass/circular/imports-same/input/b.js @@ -0,0 +1,5 @@ +import { foo } from './common' + +console.log('b', foo); + +export const b = foo + 2; diff --git a/spack/tests/pass/circular/imports-same/input/common-foo.js b/spack/tests/pass/circular/imports-same/input/common-foo.js new file mode 100644 index 00000000000..6a8018af412 --- /dev/null +++ b/spack/tests/pass/circular/imports-same/input/common-foo.js @@ -0,0 +1 @@ +export const foo = 1; \ No newline at end of file diff --git a/spack/tests/pass/circular/imports-same/input/common.js b/spack/tests/pass/circular/imports-same/input/common.js new file mode 100644 index 00000000000..9d453a594a1 --- /dev/null +++ b/spack/tests/pass/circular/imports-same/input/common.js @@ -0,0 +1 @@ +export { foo } from './common-foo'; \ No newline at end of file diff --git a/spack/tests/pass/circular/imports-same/input/entry.js b/spack/tests/pass/circular/imports-same/input/entry.js new file mode 100644 index 00000000000..0d1cb74de4a --- /dev/null +++ b/spack/tests/pass/circular/imports-same/input/entry.js @@ -0,0 +1 @@ +export * from './a'; \ No newline at end of file diff --git a/spack/tests/pass/circular/imports-same/output/entry.js b/spack/tests/pass/circular/imports-same/output/entry.js new file mode 100644 index 00000000000..8016bd09e78 --- /dev/null +++ b/spack/tests/pass/circular/imports-same/output/entry.js @@ -0,0 +1,4 @@ +const foo = 1; +const foo1 = foo; +console.log('a', foo1); +export const a = foo1 + 1; diff --git a/spack/tests/pass/circular/mixed/output/entry.js b/spack/tests/pass/circular/mixed/output/entry.js index 974142d7b43..33705c0885a 100644 --- a/spack/tests/pass/circular/mixed/output/entry.js +++ b/spack/tests/pass/circular/mixed/output/entry.js @@ -1,9 +1,9 @@ +console.log('c'); class A { method() { return new B(); } } -console.log('c'); class B extends A { } console.log(A, B); diff --git a/spack/tests/pass/export/complex-1/output/entry.js b/spack/tests/pass/export/complex-1/output/entry.js index 048acfa33cf..853923a78d8 100644 --- a/spack/tests/pass/export/complex-1/output/entry.js +++ b/spack/tests/pass/export/complex-1/output/entry.js @@ -2,5 +2,4 @@ const b = '1'; const a = '1'; console.log(b); export { a }; -const b = '1'; export { b };