bundler: Handle computed accesses correctly (#1159)

swc_bundler:
 - Handle computed accesses to imports correctly.
 - Make planning deterministic.
 - Prefer exports over imports while planning.
 - Fix https://deno.land/x/oak@v6.3.1/examples/server.ts
This commit is contained in:
강동윤 2020-10-14 23:28:38 +09:00 committed by GitHub
parent a5e6242601
commit ad7cb6544d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 853 additions and 478 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.11.0" version = "0.11.1"
# 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]
@ -36,6 +36,7 @@ swc_ecma_visit = {version = "0.19.0", path = "../ecmascript/visit"}
[dev-dependencies] [dev-dependencies]
reqwest = {version = "0.10.8", features = ["blocking"]} reqwest = {version = "0.10.8", features = ["blocking"]}
tempfile = "3.1.0"
testing = {version = "0.10.0", path = "../testing"} testing = {version = "0.10.0", path = "../testing"}
url = "2.1.1" url = "2.1.1"
walkdir = "2" walkdir = "2"

View File

@ -1,5 +1,5 @@
use super::load::TransformedModule; use super::plan::Plan;
use crate::{Bundler, Load, Resolve}; use crate::{bundler::load::TransformedModule, util::CHashSet, Bundler, Load, ModuleId, Resolve};
use anyhow::Error; use anyhow::Error;
use std::mem::take; use std::mem::take;
use swc_atoms::js_word; use swc_atoms::js_word;
@ -31,20 +31,27 @@ where
/// ``` /// ```
pub(super) fn wrap_esm_as_a_var( pub(super) fn wrap_esm_as_a_var(
&self, &self,
_info: &TransformedModule, plan: &Plan,
module: Module, module: Module,
info: &TransformedModule,
merged: &CHashSet<ModuleId>,
id: Ident, id: Ident,
) -> Result<Module, Error> { ) -> Result<Module, Error> {
let span = module.span; let span = module.span;
let mut module_items = vec![];
let stmts = { let stmts = {
let mut module = module.fold_with(&mut ExportToReturn::default()); let mut module = module.fold_with(&mut ExportToReturn::default());
take(&mut module.body) take(&mut module.body)
.into_iter() .into_iter()
.map(|v| match v { .filter_map(|v| match v {
ModuleItem::ModuleDecl(_) => unreachable!(), ModuleItem::Stmt(s) => Some(s),
ModuleItem::Stmt(s) => s, _ => {
module_items.push(v);
None
}
}) })
.collect() .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, span: DUMMY_SP,
shebang: None, 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 { let stmt = match decl {
ModuleDecl::Import(_) => None, ModuleDecl::Import(_) => return ModuleItem::ModuleDecl(decl),
ModuleDecl::ExportDecl(export) => { ModuleDecl::ExportDecl(export) => {
match &export.decl { match &export.decl {
Decl::Class(ClassDecl { ident, .. }) | Decl::Fn(FnDecl { ident, .. }) => { Decl::Class(ClassDecl { ident, .. }) | Decl::Fn(FnDecl { ident, .. }) => {

View File

@ -62,6 +62,7 @@ where
// Transitive dependencies // Transitive dependencies
let mut additional_modules = vec![]; let mut additional_modules = vec![];
let mut reexports = vec![]; let mut reexports = vec![];
let mut decls_for_reexport = vec![];
// Remove transitive dependencies which is merged by parent moudle. // Remove transitive dependencies which is merged by parent moudle.
for v in info.exports.reexports.clone() { 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 let deps = reexports
.into_par_iter() .into_par_iter()
.map(|(src, specifiers)| -> Result<_, Error> { .map(|(src, specifiers)| -> Result<_, Error> {
@ -110,7 +146,20 @@ where
}); });
if let Some(id) = id_of_export_namespace_from { 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 { } else {
dep = self.remark_exports(dep, src.ctxt, None, false); dep = self.remark_exports(dep, src.ctxt, None, false);
} }
@ -256,6 +305,16 @@ where
assert_eq!(injector.imported, vec![]); 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(()) Ok(())
} }
} }

View File

@ -1,6 +1,6 @@
use super::plan::Plan; use super::plan::{NormalPlan, Plan};
use crate::{ use crate::{
bundler::load::{Imports, Specifier}, bundler::load::{Imports, Specifier, TransformedModule},
id::ModuleId, id::ModuleId,
load::Load, load::Load,
resolve::Resolve, resolve::Resolve,
@ -84,278 +84,295 @@ where
self.merge_reexports(plan, module_plan, &mut entry, &info, merged) self.merge_reexports(plan, module_plan, &mut entry, &info, merged)
.context("failed to merge reepxorts")?; .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); // print_hygiene("after: merge_reexports", &self.cm, &entry);
let to_merge: Vec<_> = info entry = self
.imports .merge_imports(plan, module_plan, entry, &info, merged, is_entry)
.specifiers .context("failed to merge imports")?;
.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<Option<_>, 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::<Vec<_>>()
},
|| {
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::<Vec<_>>();
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);
}
Ok(entry) Ok(entry)
}) })
} }
pub(super) fn merge_imports(
&self,
plan: &Plan,
module_plan: &NormalPlan,
mut entry: Module,
info: &TransformedModule,
merged: &CHashSet<ModuleId>,
is_entry: bool,
) -> Result<Module, Error> {
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<Option<_>, 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::<Vec<_>>()
},
|| {
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::<Vec<_>>();
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) { fn finalize_merging_of_entry(&self, plan: &Plan, entry: &mut Module) {
// print_hygiene("done", &self.cm, &entry); // print_hygiene("done", &self.cm, &entry);
@ -412,6 +429,15 @@ where
}); });
entry.visit_mut_with(&mut DefaultRenamer); entry.visit_mut_with(&mut DefaultRenamer);
// print_hygiene(
// "done-clean",
// &self.cm,
// &entry
// .clone()
// .fold_with(&mut hygiene())
// .fold_with(&mut fixer(None)),
// );
} }
} }

View File

@ -9,6 +9,7 @@ use swc_ecma_visit::FoldWith;
mod circular; mod circular;
mod cjs; mod cjs;
mod computed_key;
mod export; mod export;
mod merge; mod merge;
mod plan; mod plan;

View File

@ -26,7 +26,9 @@ struct PlanBuilder {
/// This contains all dependencies, including transitive ones. For example, /// This contains all dependencies, including transitive ones. For example,
/// if `a` dependes on `b` and `b` depdends on `c`, all of /// if `a` dependes on `b` and `b` depdends on `c`, all of
/// `(a, b)`, `(a, c)`,`(b, c)` will be inserted. /// `(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 /// Graph to compute direct dependencies (direct means it will be merged
/// directly) /// directly)
@ -168,7 +170,7 @@ where
None => {} 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::<ModuleId, Metadata>::default(); let mut metadata = HashMap::<ModuleId, Metadata>::default();
@ -222,11 +224,14 @@ where
let root_entry = *root_entry; let root_entry = *root_entry;
let mut bfs = Bfs::new(&builder.direct_deps, 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) { while let Some(entry) = bfs.next(&builder.direct_deps) {
let deps: Vec<_> = builder let mut deps: Vec<_> = builder
.direct_deps .direct_deps
.neighbors_directed(entry, Outgoing) .neighbors_directed(entry, Outgoing)
.collect(); .collect();
deps.sort();
for &dep in &deps { for &dep in &deps {
if let Some(circular_members) = builder.circular.get(entry) { if let Some(circular_members) = builder.circular.get(entry) {
@ -237,21 +242,33 @@ where
entry entry
); );
if entry != root_entry && dep != root_entry { if entry != root_entry && dep != root_entry {
done.insert(dep);
plans.normal.entry(entry).or_default().chunks.push(dep); plans.normal.entry(entry).or_default().chunks.push(dep);
} }
continue; continue;
} }
} }
if done.contains(&dep) {
continue;
}
let is_es6 = self.scope.get_module(entry).unwrap().is_es6; let is_es6 = self.scope.get_module(entry).unwrap().is_es6;
let dependants = builder let mut dependants = builder
.direct_deps .direct_deps
.neighbors_directed(dep, Incoming) .neighbors_directed(dep, Incoming)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
dependants.sort();
if metadata.get(&dep).map(|md| md.bundle_cnt).unwrap_or(0) == 1 { if metadata.get(&dep).map(|md| md.bundle_cnt).unwrap_or(0) == 1 {
log::debug!("{:?} depends on {:?}", entry, dep); log::debug!("{:?} depends on {:?}", entry, dep);
let is_reexport = builder
.all_deps
.get(&(entry, dep))
.copied()
.unwrap_or(false);
// Common js support. // Common js support.
if !is_es6 { if !is_es6 {
// Dependancy of // Dependancy of
@ -280,7 +297,6 @@ where
// a <- b // a <- b
// a <- c // a <- c
let module = least_common_ancestor(&builder, &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();
for &dep in &deps { for &dep in &deps {
@ -301,6 +317,17 @@ where
continue; 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() { if 2 <= dependants.len() {
// Should be merged as a transitive dependency. // Should be merged as a transitive dependency.
let higher_module = if plans.entries.contains(&dependants[0]) { let higher_module = if plans.entries.contains(&dependants[0]) {
@ -314,8 +341,11 @@ where
}; };
if dependants.len() == 2 && dependants.contains(&higher_module) { if dependants.len() == 2 && dependants.contains(&higher_module) {
let mut entry = let mut entry = if is_reexport {
*dependants.iter().find(|&&v| v != higher_module).unwrap(); higher_module
} else {
*dependants.iter().find(|&&v| v != higher_module).unwrap()
};
// We choose higher node if import is circular // We choose higher node if import is circular
if builder.is_circular(entry) { if builder.is_circular(entry) {
@ -325,6 +355,7 @@ where
let normal_plan = plans.normal.entry(entry).or_default(); let normal_plan = plans.normal.entry(entry).or_default();
if !normal_plan.chunks.contains(&dep) { if !normal_plan.chunks.contains(&dep) {
log::trace!("Normal: {:?} => {:?}", entry, dep); log::trace!("Normal: {:?} => {:?}", entry, dep);
done.insert(dep);
normal_plan.chunks.push(dep); normal_plan.chunks.push(dep);
} }
} else { } else {
@ -335,12 +366,14 @@ where
.transitive_chunks; .transitive_chunks;
if !t.contains(&dep) { if !t.contains(&dep) {
log::trace!("Transitive: {:?} => {:?}", entry, dep); log::trace!("Transitive: {:?} => {:?}", entry, dep);
done.insert(dep);
t.push(dep) t.push(dep)
} }
} }
} else { } else {
// Direct dependency. // Direct dependency.
log::trace!("Normal: {:?} => {:?}", entry, dep); log::trace!("Normal: {:?} => {:?}", entry, dep);
done.insert(dep);
plans.normal.entry(entry).or_default().chunks.push(dep); plans.normal.entry(entry).or_default().chunks.push(dep);
} }
@ -436,6 +469,7 @@ where
builder: &mut PlanBuilder, builder: &mut PlanBuilder,
module_id: ModuleId, module_id: ModuleId,
path: &mut Vec<ModuleId>, path: &mut Vec<ModuleId>,
is_in_reexports: bool,
) { ) {
builder.direct_deps.add_node(module_id); builder.direct_deps.add_node(module_id);
@ -444,34 +478,44 @@ where
.get_module(module_id) .get_module(module_id)
.expect("failed to get module"); .expect("failed to get module");
for src in m for (src, is_export) in m
.imports .imports
.specifiers .specifiers
.iter() .iter()
.map(|v| &v.0) .map(|v| (&v.0, false))
.chain(m.exports.reexports.iter().map(|v| &v.0)) .chain(m.exports.reexports.iter().map(|v| (&v.0, true)))
{ {
if !builder.direct_deps.contains_edge(module_id, src.module_id) { 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); builder.direct_deps.add_edge(module_id, src.module_id, 0);
for &id in &*path { 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); 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)); assert_eq!(path.pop(), Some(module_id));
} }
} }
// Prevent dejavu // Prevent dejavu
for (src, _) in &m.imports.specifiers { 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); 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);

View File

@ -109,8 +109,8 @@ fn concurrency_001() {
assert_eq!(p.circular.len(), 0); assert_eq!(p.circular.len(), 0);
assert_normal(t, &p, "main", &["a"]); assert_normal(t, &p, "main", &["a", "b"]);
assert_normal(t, &p, "a", &["b"]); assert_normal(t, &p, "a", &[]);
assert_normal(t, &p, "b", &[]); assert_normal(t, &p, "b", &[]);
Ok(()) Ok(())
@ -153,8 +153,8 @@ fn concurrency_002() {
assert_eq!(p.circular.len(), 0); assert_eq!(p.circular.len(), 0);
assert_normal(t, &p, "main", &["a"]); assert_normal(t, &p, "main", &["a", "b"]);
assert_normal(t, &p, "a", &["b"]); assert_normal(t, &p, "a", &[]);
assert_normal(t, &p, "b", &[]); assert_normal(t, &p, "b", &[]);
Ok(()) Ok(())
@ -702,7 +702,6 @@ fn circular_002() {
} }
#[test] #[test]
#[ignore = "Not deterministic yet"]
fn deno_002() { fn deno_002() {
suite() suite()
.file( .file(
@ -799,7 +798,13 @@ fn deno_002() {
dbg!(&p); 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_normal_transitive(t, &p, "http-server", &["async-mod"], &[]);
assert_circular(t, &p, "http-server", &["http-_io"]); assert_circular(t, &p, "http-server", &["http-_io"]);
@ -810,25 +815,22 @@ fn deno_002() {
assert_normal(t, &p, "_util-assert", &[]); assert_normal(t, &p, "_util-assert", &[]);
assert_normal_transitive( assert_normal(t, &p, "http-_io", &["textproto-mod", "http-http_status"]);
t,
&p,
"http-_io",
&["textproto-mod", "http-http_status"],
&["encoding-utf8", "io-bufio", "_util-assert"],
);
assert_circular(t, &p, "http-_io", &["http-server"]); assert_circular(t, &p, "http-_io", &["http-server"]);
assert_normal(t, &p, "textproto-mod", &[]); assert_normal(t, &p, "textproto-mod", &[]);
assert_normal(t, &p, "_util-assert", &[]); 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, "bytes-mod", &[]);
assert_normal(t, &p, "async-mux_async_iterator", &["async-deferred"]);
Ok(()) Ok(())
}); });
} }
@ -908,7 +910,6 @@ fn circular_root_entry_2() {
} }
#[test] #[test]
#[ignore = "Not deterministic yet"]
fn deno_003() { fn deno_003() {
suite() suite()
.file( .file(
@ -945,9 +946,14 @@ fn deno_003() {
assert_normal(t, &p, "main", &["async-mod"]); 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(()) Ok(())
}); });

View File

@ -121,20 +121,17 @@ impl ExportRenamer<'_> {
impl Fold for ExportRenamer<'_> { impl Fold for ExportRenamer<'_> {
noop_fold_type!(); noop_fold_type!();
/// no-op, as imports or exports are only related to top level nodes.
fn fold_class(&mut self, node: Class) -> Class { fn fold_class(&mut self, node: Class) -> Class {
node node
} }
/// no-op, as imports or exports are only related to top level nodes.
fn fold_function(&mut self, node: Function) -> Function { fn fold_function(&mut self, node: Function) -> Function {
node node
} }
fn fold_module_item(&mut self, item: ModuleItem) -> ModuleItem { 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 span = item.span();
let item: ModuleItem = item.fold_children_with(self); 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 { return match decl.decl {
Decl::TsInterface(_) Decl::TsInterface(_)
@ -451,7 +448,11 @@ 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 = 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 { if self.unexport {
Stmt::Decl(Decl::Class(c)).into() Stmt::Decl(Decl::Class(c)).into()
@ -463,7 +464,11 @@ impl Fold for ExportRenamer<'_> {
} }
} }
Decl::Fn(mut f) => { 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 { if self.unexport {
Stmt::Decl(Decl::Fn(f)).into() Stmt::Decl(Decl::Fn(f)).into()
} else { } else {
@ -473,8 +478,14 @@ impl Fold for ExportRenamer<'_> {
})) }))
} }
} }
Decl::Var(..) => { Decl::Var(ref mut v) => {
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(decl.fold_with(&mut actual))) 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, dep_ctxt: SyntaxContext,
/// Dependant module's import /// Dependant module's import
imports: Option<&'a [Specifier]>, imports: Option<&'a [Specifier]>,
remarks: &'b mut RemarkMap,
} }
impl ActualMarker<'_> { impl ActualMarker<'_, '_> {
fn rename(&self, ident: Ident, only_if_aliased: bool) -> Result<Ident, Ident> { fn rename(&mut self, ident: &mut Ident) {
if self.imports.is_none() { 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 { Specifier::Specific {
alias: Some(alias), alias: Some(alias),
local, local,
} if *alias == ident.sym => Some(Ident::new(local.sym().clone(), ident.span)), } if *alias == ident.sym => Some((local.sym().clone(), ident.span.ctxt)),
Specifier::Specific { alias: None, local } Specifier::Specific { alias: None, local } if *local == ident.sym => {
if !only_if_aliased && *local == ident.sym => Some(local.to_id())
{
Some(local.clone().into_ident())
} }
_ => None, _ => None,
}) { }) {
ident.span = ident.span.with_ctxt(self.dep_ctxt); ident.sym = id.0.clone();
self.remarks.insert(id, self.dep_ctxt);
return Ok(ident);
} }
Err(ident)
} }
} }
impl Fold for ActualMarker<'_> { impl VisitMut for ActualMarker<'_, '_> {
noop_fold_type!(); noop_visit_mut_type!();
fn fold_expr(&mut self, node: Expr) -> Expr { fn visit_mut_expr(&mut self, _: &mut Expr) {}
node
fn visit_mut_ident(&mut self, ident: &mut Ident) {
self.rename(ident)
} }
fn fold_ident(&mut self, ident: Ident) -> Ident { fn visit_mut_private_name(&mut self, _: &mut PrivateName) {}
match self.rename(ident, false) {
Ok(v) => v,
Err(v) => v,
}
}
fn fold_private_name(&mut self, i: PrivateName) -> PrivateName { fn visit_mut_export_named_specifier(&mut self, s: &mut ExportNamedSpecifier) {
i 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> { struct RemarkIdents<'a> {

View File

@ -4,7 +4,7 @@ use super::{
}; };
use crate::{id::Id, load::Load, resolve::Resolve}; use crate::{id::Id, load::Load, resolve::Resolve};
use std::collections::HashMap; use std::collections::HashMap;
use swc_atoms::js_word; use swc_atoms::{js_word, JsWord};
use swc_common::{FileName, SyntaxContext}; use swc_common::{FileName, SyntaxContext};
use swc_ecma_ast::*; use swc_ecma_ast::*;
use swc_ecma_utils::find_ids; use swc_ecma_utils::find_ids;
@ -63,7 +63,7 @@ where
L: Load, L: Load,
R: Resolve, R: Resolve,
{ {
fn ctxt_for(&self, src: &str) -> Option<SyntaxContext> { fn ctxt_for(&self, src: &JsWord) -> Option<SyntaxContext> {
// Don't apply mark if it's a core module. // Don't apply mark if it's a core module.
if self if self
.bundler .bundler
@ -80,6 +80,27 @@ where
Some(ctxt.apply_mark(mark)) 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<L, R> VisitMut for ExportFinder<'_, '_, L, R> impl<L, R> VisitMut for ExportFinder<'_, '_, L, R>
@ -178,16 +199,20 @@ where
let ctxt = named let ctxt = named
.src .src
.as_ref() .as_ref()
.map(|s| &*s.value) .map(|s| &s.value)
.and_then(|src| self.ctxt_for(src)); .and_then(|src| self.ctxt_for(src));
let mut need_wrapping = false;
let v = self.info.items.entry(named.src.clone()).or_default(); let v = self.info.items.entry(named.src.clone()).or_default();
for s in &mut named.specifiers { for s in &mut named.specifiers {
match s { match s {
ExportSpecifier::Namespace(n) => v.push(Specifier::Namespace { ExportSpecifier::Namespace(n) => {
local: n.name.clone().into(), need_wrapping = true;
all: true, v.push(Specifier::Namespace {
}), local: n.name.clone().into(),
all: true,
})
}
ExportSpecifier::Default(d) => { ExportSpecifier::Default(d) => {
v.push(Specifier::Specific { v.push(Specifier::Specific {
local: d.exported.clone().into(), 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; return;
} }

View File

@ -127,7 +127,7 @@ where
L: Load, L: Load,
R: Resolve, R: Resolve,
{ {
fn ctxt_for(&self, src: &str) -> Option<SyntaxContext> { fn ctxt_for(&self, src: &JsWord) -> Option<SyntaxContext> {
// Don't apply mark if it's a core module. // Don't apply mark if it's a core module.
if self if self
.bundler .bundler
@ -144,6 +144,27 @@ where
Some(ctxt.apply_mark(mark)) 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<L, R> Fold for ImportHandler<'_, '_, L, R> impl<L, R> Fold for ImportHandler<'_, '_, L, R>
@ -268,10 +289,13 @@ where
}); });
if self.deglob_phase { if self.deglob_phase {
let mut wrapping_required = vec![];
for import in self.info.imports.iter_mut() { for import in self.info.imports.iter_mut() {
let use_ns = self.info.forced_ns.contains(&import.src.value); let use_ns = self.info.forced_ns.contains(&import.src.value);
if use_ns { if use_ns {
wrapping_required.push(import.src.value.clone());
import.specifiers.retain(|s| match s { import.specifiers.retain(|s| match s {
ImportSpecifier::Namespace(_) => true, ImportSpecifier::Namespace(_) => true,
_ => false, _ => false,
@ -290,6 +314,10 @@ where
}); });
} }
} }
for id in wrapping_required {
self.mark_as_wrapping_required(&id);
}
} }
items items

View File

@ -1,74 +1,75 @@
use super::ImportHandler; // use super::ImportHandler;
use crate::bundler::tests::suite; // use crate::bundler::tests::suite;
use std::path::Path; // use std::path::Path;
use swc_common::{FileName, Mark, SyntaxContext}; // use swc_common::{FileName, Mark, SyntaxContext};
use swc_ecma_visit::FoldWith; // use swc_ecma_visit::FoldWith;
#[test] // #[test]
#[ignore] // #[ignore]
fn ns_import_deglob_simple() { // fn ns_import_deglob_simple() {
suite().run(|t| { // suite().run(|t| {
let m = t.parse( // let m = t.parse(
" // "
import * as ns from 'foo'; // import * as ns from 'foo';
ns.foo(); // ns.foo();
", // ",
); // );
let mut v = ImportHandler { // let mut v = ImportHandler {
path: &FileName::Real(Path::new("index.js").to_path_buf()), // path: &FileName::Real(Path::new("index.js").to_path_buf()),
bundler: &t.bundler, // bundler: &t.bundler,
top_level: false, // top_level: false,
info: Default::default(), // info: Default::default(),
ns_usage: Default::default(), // ns_usage: Default::default(),
deglob_phase: false, // deglob_phase: false,
imported_idents: Default::default(), // imported_idents: Default::default(),
module_ctxt: SyntaxContext::empty().apply_mark(Mark::fresh(Mark::root())), // module_ctxt:
idents_to_deglob: Default::default(), // SyntaxContext::empty().apply_mark(Mark::fresh(Mark::root())),
}; // idents_to_deglob: Default::default(), };
let m = m.fold_with(&mut v); // let m = m.fold_with(&mut v);
assert!(v.info.forced_ns.is_empty()); // assert!(v.info.forced_ns.is_empty());
assert_eq!(v.info.imports.len(), 1); // assert_eq!(v.info.imports.len(), 1);
t.assert_eq(&m, "foo();"); // t.assert_eq(&m, "foo();");
Ok(()) // Ok(())
}) // })
} // }
#[test] // #[test]
#[ignore] // #[ignore]
fn ns_import_deglob_multi() { // fn ns_import_deglob_multi() {
suite().run(|t| { // suite().run(|t| {
let m = t.parse( // let m = t.parse(
" // "
import * as ns from 'foo'; // import * as ns from 'foo';
ns.foo(); // ns.foo();
ns.bar(); // ns.bar();
", // ",
); // );
let mut v = ImportHandler { // let mut v = ImportHandler {
path: &FileName::Real(Path::new("index.js").to_path_buf()), // path: &FileName::Real(Path::new("index.js").to_path_buf()),
bundler: &t.bundler, // bundler: &t.bundler,
top_level: false, // top_level: false,
info: Default::default(), // info: Default::default(),
ns_usage: Default::default(), // ns_usage: Default::default(),
deglob_phase: false, // deglob_phase: false,
imported_idents: Default::default(), // imported_idents: Default::default(),
module_ctxt: SyntaxContext::empty().apply_mark(Mark::fresh(Mark::root())), // module_ctxt:
idents_to_deglob: Default::default(), // SyntaxContext::empty().apply_mark(Mark::fresh(Mark::root())),
}; // idents_to_deglob: Default::default(), };
let m = m.fold_with(&mut v); // let m = m.fold_with(&mut v);
assert!(v.info.forced_ns.is_empty()); // assert!(v.info.forced_ns.is_empty());
assert_eq!(v.info.imports.len(), 1); // assert_eq!(v.info.imports.len(), 1);
assert_eq!(v.info.imports[0].specifiers.len(), 2); // assert_eq!(v.info.imports[0].specifiers.len(), 2);
assert!(!format!("{:?}", v.info.imports[0].specifiers).contains("ns")); // assert!(!format!("{:?}",
// v.info.imports[0].specifiers).contains("ns"));
t.assert_eq( // t.assert_eq(
&m, // &m,
"foo(); // "foo();
bar();", // bar();",
); // );
Ok(()) // Ok(())
}) // })
} // }

View File

@ -75,7 +75,7 @@ where
.context("failed to analyze module")?; .context("failed to analyze module")?;
files.dedup_by_key(|v| v.1.clone()); 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()); self.scope.store_module(v.clone());
// Load dependencies and store them in the `Scope` // Load dependencies and store them in the `Scope`

View File

@ -7,7 +7,6 @@ use swc_common::{sync::Lrc, FileName, Globals, Mark, SourceMap, SyntaxContext, D
use swc_ecma_ast::Module; use swc_ecma_ast::Module;
mod chunk; mod chunk;
mod computed_key;
mod export; mod export;
mod finalize; mod finalize;
mod helpers; mod helpers;

View File

@ -3,7 +3,8 @@ use crate::{
id::{ModuleId, ModuleIdGenerator}, id::{ModuleId, ModuleIdGenerator},
util::CloneMap, util::CloneMap,
}; };
use swc_common::FileName; use std::sync::atomic::{AtomicBool, Ordering};
use swc_common::{sync::Lrc, FileName};
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub(super) struct Metadata { pub(super) struct Metadata {
@ -18,6 +19,8 @@ pub(super) struct Scope {
/// Cached after applying basical transformations. /// Cached after applying basical transformations.
transformed_modules: CloneMap<ModuleId, TransformedModule>, transformed_modules: CloneMap<ModuleId, TransformedModule>,
accessed_with_computed_key: CloneMap<ModuleId, Lrc<AtomicBool>>,
} }
impl Scope { impl Scope {
@ -40,4 +43,23 @@ impl Scope {
pub fn get_module(&self, id: ModuleId) -> Option<TransformedModule> { pub fn get_module(&self, id: ModuleId) -> Option<TransformedModule> {
Some(self.transformed_modules.get(&id)?.clone()) 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
}
}
} }

View File

@ -70,6 +70,7 @@ impl<'a> Tester<'a> {
.unwrap_or_else(|| panic!("failed to find module named {}", name)) .unwrap_or_else(|| panic!("failed to find module named {}", name))
} }
#[allow(dead_code)]
pub fn parse(&self, s: &str) -> Module { pub fn parse(&self, s: &str) -> Module {
let fm = self let fm = self
.cm .cm
@ -85,6 +86,7 @@ impl<'a> Tester<'a> {
parser.parse_module().unwrap() parser.parse_module().unwrap()
} }
#[allow(dead_code)]
pub fn assert_eq(&self, m: &Module, expected: &str) { pub fn assert_eq(&self, m: &Module, expected: &str) {
let expected = self.parse(expected); let expected = self.parse(expected);

View File

@ -1,7 +1,7 @@
#![allow(dead_code)] #![allow(dead_code)]
use std::io::{stderr, Write}; 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_ast::{Ident, Module};
use swc_ecma_codegen::{text_writer::JsWriter, Emitter}; use swc_ecma_codegen::{text_writer::JsWriter, Emitter};
use swc_ecma_visit::{noop_fold_type, Fold, FoldWith}; use swc_ecma_visit::{noop_fold_type, Fold, FoldWith};
@ -30,6 +30,9 @@ impl Fold for HygieneVisualizer {
noop_fold_type!(); noop_fold_type!();
fn fold_ident(&mut self, node: Ident) -> Ident { fn fold_ident(&mut self, node: Ident) -> Ident {
if node.span.ctxt == SyntaxContext::empty() {
return node;
}
Ident { Ident {
sym: format!("{}{:?}", node.sym, node.span.ctxt()).into(), sym: format!("{}{:?}", node.sym, node.span.ctxt()).into(),
..node ..node

View File

@ -1,7 +1,103 @@
use std::hash::Hash; use std::{hash::Hash, mem::replace};
use swc_common::{Span, SyntaxContext}; 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}; 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<F>(&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<T> MapWithMut for Option<T> {
#[inline(always)]
fn dummy() -> Self {
None
}
}
impl<T> MapWithMut for Vec<T> {
#[inline(always)]
fn dummy() -> Self {
Vec::new()
}
}
impl<T> MapWithMut for Box<T>
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)] #[derive(Debug)]
pub(crate) struct CHashSet<V> pub(crate) struct CHashSet<V>
where where

View File

@ -2,12 +2,16 @@
//! //!
//! This module exists because this is way easier than using copying requires //! This module exists because this is way easier than using copying requires
//! files. //! files.
use anyhow::{Context, Error}; 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_bundler::{Bundler, Load, Resolve};
use swc_common::{sync::Lrc, FileName, SourceFile, SourceMap, Span, GLOBALS}; use swc_common::{sync::Lrc, FileName, SourceFile, SourceMap, Span, GLOBALS};
use swc_ecma_ast::{Expr, Lit, Module, Str}; 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_parser::{lexer::Lexer, JscTarget, Parser, StringInput, Syntax, TsConfig};
use swc_ecma_transforms::typescript::strip; use swc_ecma_transforms::typescript::strip;
use swc_ecma_visit::FoldWith; use swc_ecma_visit::FoldWith;
@ -15,11 +19,53 @@ use url::Url;
#[test] #[test]
#[ignore = "Too slow"] #[ignore = "Too slow"]
fn oak_6_2_0_application() { fn oak_6_3_1_application() {
bundle("https://deno.land/x/oak@v6.2.0/application.ts"); 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| { let result = testing::run_test2(false, |cm, _handler| {
GLOBALS.with(|globals| { GLOBALS.with(|globals| {
let bundler = Bundler::new( let bundler = Bundler::new(
@ -29,7 +75,7 @@ fn bundle(url: &str) -> Module {
Resolver, Resolver,
swc_bundler::Config { swc_bundler::Config {
require: false, require: false,
disable_inliner: true, disable_inliner: false,
..Default::default() ..Default::default()
}, },
Box::new(Hook), Box::new(Hook),
@ -37,8 +83,21 @@ fn bundle(url: &str) -> Module {
let mut entries = HashMap::new(); let mut entries = HashMap::new();
entries.insert("main".to_string(), FileName::Custom(url.to_string())); entries.insert("main".to_string(), FileName::Custom(url.to_string()));
let output = bundler.bundle(entries).unwrap(); 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(); .unwrap();

View File

@ -1,7 +1,8 @@
function a() { function a() {
console.log("a"); console.log("a");
} }
const a1 = a;
function b() { function b() {
a(); a1();
} }
b(); b();

View File

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

View File

@ -1,6 +1,7 @@
class a { class a {
} }
const a1 = a;
function b() { function b() {
return new a(); return new a1();
} }
b(); b();

View File

@ -1,10 +1,10 @@
function deferred() { function deferred() {
} }
const deferred1 = deferred;
class MuxAsyncIterator { class MuxAsyncIterator {
constructor(){ constructor(){
this.signal = deferred(); this.signal = deferred();
} }
} }
const MuxAsyncIterator1 = MuxAsyncIterator; const MuxAsyncIterator1 = MuxAsyncIterator;
const deferred1 = deferred;
console.log(deferred1, MuxAsyncIterator1); console.log(deferred1, MuxAsyncIterator1);

View File

@ -1,2 +1,3 @@
const a = "a"; const a = "a";
console.log(a); const a1 = a;
console.log(a1);