bundler: Fix bugs (#1154)

swc_bundler:
 - Fix importing from transitive dependencies with aliases.
 - Fix stack overflow while computing plan for modules with cyclic dependencies.
 - Handle `export *` for function / class properly. (#1155)
This commit is contained in:
강동윤 2020-10-10 18:32:31 +09:00 committed by GitHub
parent f0ea70cb25
commit 6f006208ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 206 additions and 33 deletions

View File

@ -6,7 +6,7 @@ edition = "2018"
license = "Apache-2.0/MIT" license = "Apache-2.0/MIT"
name = "swc_bundler" name = "swc_bundler"
repository = "https://github.com/swc-project/swc.git" repository = "https://github.com/swc-project/swc.git"
version = "0.10.2" version = "0.10.3"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features] [features]

View File

@ -284,6 +284,7 @@ where
let mut injector = Es6ModuleInjector { let mut injector = Es6ModuleInjector {
imported: take(&mut dep.body), imported: take(&mut dep.body),
ctxt: dep_info.ctxt(), ctxt: dep_info.ctxt(),
is_direct,
}; };
entry.body.visit_mut_with(&mut injector); entry.body.visit_mut_with(&mut injector);
@ -495,6 +496,7 @@ impl Fold for Unexporter {
struct Es6ModuleInjector { struct Es6ModuleInjector {
imported: Vec<ModuleItem>, imported: Vec<ModuleItem>,
ctxt: SyntaxContext, ctxt: SyntaxContext,
is_direct: bool,
} }
impl VisitMut for Es6ModuleInjector { impl VisitMut for Es6ModuleInjector {
@ -507,10 +509,43 @@ impl VisitMut for Es6ModuleInjector {
for item in items { for item in items {
// //
match item { match item {
ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl { span, .. })) ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
if span.ctxt == self.ctxt => span, specifiers, ..
{ })) if span.ctxt == self.ctxt => {
buf.extend(take(&mut self.imported)); buf.extend(take(&mut self.imported));
if !self.is_direct {
let decls = specifiers
.iter()
.filter_map(|specifier| match specifier {
ImportSpecifier::Named(ImportNamedSpecifier {
local,
imported: Some(imported),
..
}) => {
let mut imported = imported.clone();
imported.span = imported.span.with_ctxt(self.ctxt);
Some(VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(local.clone()),
init: Some(Box::new(Expr::Ident(imported))),
definite: false,
})
}
_ => None,
})
.collect::<Vec<_>>();
if !decls.is_empty() {
buf.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(VarDecl {
span: DUMMY_SP,
kind: VarDeclKind::Const,
declare: false,
decls,
}))));
}
}
} }
_ => buf.push(item), _ => buf.push(item),

View File

@ -1,9 +1,9 @@
use super::ModuleGraph; use super::PlanBuilder;
use crate::ModuleId; use crate::ModuleId;
use petgraph::EdgeDirection::Incoming; use petgraph::EdgeDirection::Incoming;
// TODO: Optimize with cache. // TODO: Optimize with cache.
pub(super) fn least_common_ancestor(g: &ModuleGraph, module_ids: &[ModuleId]) -> ModuleId { pub(super) fn least_common_ancestor(b: &PlanBuilder, module_ids: &[ModuleId]) -> ModuleId {
assert_ne!( assert_ne!(
module_ids, module_ids,
&[], &[],
@ -12,6 +12,7 @@ pub(super) fn least_common_ancestor(g: &ModuleGraph, module_ids: &[ModuleId]) ->
if module_ids.len() == 1 { if module_ids.len() == 1 {
return module_ids[0]; return module_ids[0];
} }
let g = &b.direct_deps;
// Check for roots // Check for roots
for &mid in module_ids { for &mid in module_ids {
@ -28,7 +29,7 @@ pub(super) fn least_common_ancestor(g: &ModuleGraph, module_ids: &[ModuleId]) ->
return first; return first;
} }
if let Some(id) = check_itself_and_parent(g, &[first], &[second]) { if let Some(id) = check_itself_and_parent(b, &[first], &[second]) {
log::debug!("Found lca: {:?}", id); log::debug!("Found lca: {:?}", id);
return id; return id;
} }
@ -43,15 +44,16 @@ pub(super) fn least_common_ancestor(g: &ModuleGraph, module_ids: &[ModuleId]) ->
.iter() .iter()
.skip(2) .skip(2)
.cloned() .cloned()
.fold(least_common_ancestor(g, &[first, second]), |prev, item| { .fold(least_common_ancestor(b, &[first, second]), |prev, item| {
least_common_ancestor(g, &[prev, item]) least_common_ancestor(b, &[prev, item])
}); });
} }
fn check_itself<I>(g: &ModuleGraph, li: I, ri: &[ModuleId]) -> Option<ModuleId> fn check_itself<I>(b: &PlanBuilder, li: I, ri: &[ModuleId]) -> Option<ModuleId>
where where
I: IntoIterator<Item = ModuleId>, I: IntoIterator<Item = ModuleId>,
{ {
let g = &b.direct_deps;
for l in li { for l in li {
// Root // Root
if g.neighbors_directed(l, Incoming).count() == 0 { if g.neighbors_directed(l, Incoming).count() == 0 {
@ -73,27 +75,46 @@ where
None None
} }
fn check_itself_and_parent(g: &ModuleGraph, li: &[ModuleId], ri: &[ModuleId]) -> Option<ModuleId> { fn check_itself_and_parent(b: &PlanBuilder, li: &[ModuleId], ri: &[ModuleId]) -> Option<ModuleId> {
if let Some(id) = check_itself(g, li.iter().copied(), ri) { let g = &b.direct_deps;
if let Some(id) = check_itself(b, li.iter().copied(), ri) {
return Some(id); return Some(id);
} }
for &l in li { for &l in li {
if let Some(id) = check_itself_and_parent( let mut l_dependants = g.neighbors_directed(l, Incoming).collect::<Vec<_>>();
g, for &l_dependant in &l_dependants {
&g.neighbors_directed(l, Incoming).collect::<Vec<_>>(), if g.neighbors_directed(l_dependant, Incoming).count() == 0 {
ri, return Some(l_dependant);
) { }
}
l_dependants.retain(|&id| !b.is_circular(id));
if l_dependants.is_empty() {
return Some(l);
}
if let Some(id) = check_itself_and_parent(b, &l_dependants, ri) {
return Some(id); return Some(id);
} }
} }
for &r in ri { for &r in ri {
if let Some(id) = check_itself_and_parent( let mut r_dependants = g.neighbors_directed(r, Incoming).collect::<Vec<_>>();
g, for &r_dependant in &r_dependants {
&g.neighbors_directed(r, Incoming).collect::<Vec<_>>(), if g.neighbors_directed(r_dependant, Incoming).count() == 0 {
li, return Some(r_dependant);
) { }
}
r_dependants.retain(|&id| !b.is_circular(id));
if r_dependants.is_empty() {
return Some(r);
}
if let Some(id) = check_itself_and_parent(b, &r_dependants, li) {
return Some(id); return Some(id);
} }
} }

View File

@ -279,7 +279,7 @@ where
// //
// a <- b // a <- b
// a <- c // a <- c
let module = least_common_ancestor(&builder.direct_deps, &dependants); let module = least_common_ancestor(&builder, &dependants);
let normal_plan = plans.normal.entry(module).or_default(); let normal_plan = plans.normal.entry(module).or_default();
@ -310,7 +310,7 @@ where
{ {
dependants[1] dependants[1]
} else { } else {
least_common_ancestor(&builder.direct_deps, &dependants) least_common_ancestor(&builder, &dependants)
}; };
if dependants.len() == 2 && dependants.contains(&higher_module) { if dependants.len() == 2 && dependants.contains(&higher_module) {

View File

@ -451,10 +451,7 @@ impl Fold for ExportRenamer<'_> {
| Decl::TsModule(_) => ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(decl)), | Decl::TsModule(_) => ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(decl)),
Decl::Class(mut c) => { Decl::Class(mut c) => {
c.ident = match actual.rename(c.ident, true) { c.ident = c.ident.fold_with(&mut actual);
Ok(v) => v,
Err(v) => v,
};
if self.unexport { if self.unexport {
Stmt::Decl(Decl::Class(c)).into() Stmt::Decl(Decl::Class(c)).into()
@ -466,10 +463,7 @@ impl Fold for ExportRenamer<'_> {
} }
} }
Decl::Fn(mut f) => { Decl::Fn(mut f) => {
f.ident = match actual.rename(f.ident, true) { f.ident = f.ident.fold_with(&mut actual);
Ok(v) => v,
Err(v) => v,
};
if self.unexport { if self.unexport {
Stmt::Decl(Decl::Fn(f)).into() Stmt::Decl(Decl::Fn(f)).into()
} else { } else {

View File

@ -16,7 +16,7 @@ use url::Url;
#[test] #[test]
#[ignore = "Too slow"] #[ignore = "Too slow"]
fn oak_6_2_0_application() { fn oak_6_2_0_application() {
bundle("https://deno.land/x/oak@v6.2.0/mod.ts"); bundle("https://deno.land/x/oak@v6.2.0/application.ts");
} }
fn bundle(url: &str) -> Module { fn bundle(url: &str) -> Module {

View File

@ -0,0 +1,33 @@
import { a as defaultA, O } from "./m.ts";
export { O } from "./m.ts";
interface AOptions {
a?(): void;
c?: O;
}
class A {
#a: () => void;
#c?: O;
constructor(o: AOptions = {}) {
const {
a = defaultA,
c,
} = o;
this.#a = a;
this.#c = c;
}
a() {
this.#a();
}
c() {
console.log(this.#c);
}
}
let a = new A();
a.a();
a.c();

View File

@ -0,0 +1,2 @@
export { a } from "./n.ts";
export { O } from "./o.ts";

View File

@ -0,0 +1,3 @@
export function a() {
console.log("a");
}

View File

@ -0,0 +1,5 @@
export enum O {
A,
B,
C,
}

View File

@ -0,0 +1,33 @@
function a() {
console.log("a");
}
const a1 = a;
var O;
(function(O1) {
O1[O1["A"] = 0] = "A";
O1[O1["B"] = 1] = "B";
O1[O1["C"] = 2] = "C";
})(O || (O = {
}));
const O1 = O;
const defaultA = a1;
export { O1 as O };
class A {
#a;
#c;
constructor(o = {
}){
const { a: a2 = defaultA , c , } = o;
this.#a = a2;
this.#c = c;
}
a() {
this.#a();
}
c() {
console.log(this.#c);
}
}
let a3 = new A();
a3.a();
a3.c();

View File

@ -0,0 +1,7 @@
import { a } from "./p.ts";
function b() {
a();
}
b();

View File

@ -0,0 +1,3 @@
export function a() {
console.log("a");
}

View File

@ -0,0 +1 @@
export * from "./i.ts";

View File

@ -0,0 +1,7 @@
function a() {
console.log("a");
}
function b() {
a();
}
b();

View File

@ -0,0 +1,7 @@
import { a } from "./p.ts";
function b() {
return a;
}
b();

View File

@ -0,0 +1 @@
export const a = 'a';

View File

@ -0,0 +1 @@
export * from "./i.ts";

View File

@ -0,0 +1,5 @@
const a = 'a';
function b() {
return a;
}
b();

View File

@ -0,0 +1,7 @@
import { a } from "./p.ts";
function b() {
return new a();
}
b();

View File

@ -0,0 +1 @@
export class a { }

View File

@ -0,0 +1 @@
export * from "./i.ts";

View File

@ -0,0 +1,6 @@
class a {
}
function b() {
return new a();
}
b();