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"
name = "swc_bundler"
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
[features]

View File

@ -284,6 +284,7 @@ where
let mut injector = Es6ModuleInjector {
imported: take(&mut dep.body),
ctxt: dep_info.ctxt(),
is_direct,
};
entry.body.visit_mut_with(&mut injector);
@ -495,6 +496,7 @@ impl Fold for Unexporter {
struct Es6ModuleInjector {
imported: Vec<ModuleItem>,
ctxt: SyntaxContext,
is_direct: bool,
}
impl VisitMut for Es6ModuleInjector {
@ -507,10 +509,43 @@ impl VisitMut for Es6ModuleInjector {
for item in items {
//
match item {
ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl { span, .. }))
if span.ctxt == self.ctxt =>
{
ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
span, specifiers, ..
})) if span.ctxt == self.ctxt => {
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),

View File

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

View File

@ -279,7 +279,7 @@ where
//
// a <- b
// 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();
@ -310,7 +310,7 @@ where
{
dependants[1]
} else {
least_common_ancestor(&builder.direct_deps, &dependants)
least_common_ancestor(&builder, &dependants)
};
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::Class(mut c) => {
c.ident = match actual.rename(c.ident, true) {
Ok(v) => v,
Err(v) => v,
};
c.ident = c.ident.fold_with(&mut actual);
if self.unexport {
Stmt::Decl(Decl::Class(c)).into()
@ -466,10 +463,7 @@ impl Fold for ExportRenamer<'_> {
}
}
Decl::Fn(mut f) => {
f.ident = match actual.rename(f.ident, true) {
Ok(v) => v,
Err(v) => v,
};
f.ident = f.ident.fold_with(&mut actual);
if self.unexport {
Stmt::Decl(Decl::Fn(f)).into()
} else {

View File

@ -16,7 +16,7 @@ use url::Url;
#[test]
#[ignore = "Too slow"]
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 {

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();