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"
name = "swc_bundler"
repository = "https://github.com/swc-project/swc.git"
version = "0.11.0"
version = "0.11.1"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
@ -36,6 +36,7 @@ swc_ecma_visit = {version = "0.19.0", path = "../ecmascript/visit"}
[dev-dependencies]
reqwest = {version = "0.10.8", features = ["blocking"]}
tempfile = "3.1.0"
testing = {version = "0.10.0", path = "../testing"}
url = "2.1.1"
walkdir = "2"

View File

@ -1,5 +1,5 @@
use super::load::TransformedModule;
use crate::{Bundler, Load, Resolve};
use super::plan::Plan;
use crate::{bundler::load::TransformedModule, util::CHashSet, Bundler, Load, ModuleId, Resolve};
use anyhow::Error;
use std::mem::take;
use swc_atoms::js_word;
@ -31,20 +31,27 @@ where
/// ```
pub(super) fn wrap_esm_as_a_var(
&self,
_info: &TransformedModule,
plan: &Plan,
module: Module,
info: &TransformedModule,
merged: &CHashSet<ModuleId>,
id: Ident,
) -> Result<Module, Error> {
let span = module.span;
let mut module_items = vec![];
let stmts = {
let mut module = module.fold_with(&mut ExportToReturn::default());
take(&mut module.body)
.into_iter()
.map(|v| match v {
ModuleItem::ModuleDecl(_) => unreachable!(),
ModuleItem::Stmt(s) => s,
.filter_map(|v| match v {
ModuleItem::Stmt(s) => Some(s),
_ => {
module_items.push(v);
None
}
})
.collect()
};
@ -85,11 +92,26 @@ where
}],
};
Ok(Module {
module_items.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(var_decl))));
let module = Module {
span: DUMMY_SP,
shebang: None,
body: vec![ModuleItem::Stmt(Stmt::Decl(Decl::Var(var_decl)))],
})
body: module_items,
};
let module_plan;
let module_plan = match plan.normal.get(&info.id) {
Some(v) => v,
None => {
module_plan = Default::default();
&module_plan
}
};
let module = self.merge_imports(&plan, module_plan, module, info, merged, false)?;
// print_hygiene("Imports", &self.cm, &module);
Ok(module)
}
}
@ -112,7 +134,7 @@ impl Fold for ExportToReturn {
};
let stmt = match decl {
ModuleDecl::Import(_) => None,
ModuleDecl::Import(_) => return ModuleItem::ModuleDecl(decl),
ModuleDecl::ExportDecl(export) => {
match &export.decl {
Decl::Class(ClassDecl { ident, .. }) | Decl::Fn(FnDecl { ident, .. }) => {

View File

@ -62,6 +62,7 @@ where
// Transitive dependencies
let mut additional_modules = vec![];
let mut reexports = vec![];
let mut decls_for_reexport = vec![];
// Remove transitive dependencies which is merged by parent moudle.
for v in info.exports.reexports.clone() {
@ -76,6 +77,41 @@ where
}
}
for (src, specifiers) in info.exports.reexports.iter() {
let imported = self.scope.get_module(src.module_id).unwrap();
// export * from './foo';
if specifiers.is_empty() {
decls_for_reexport.extend(
imported
.exports
.items
.iter()
.chain(imported.exports.reexports.iter().map(|v| &*v.1).flatten())
.map(|specifier| match specifier {
Specifier::Specific { local, alias } => VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(
local.clone().replace_mark(info.mark()).into_ident(),
),
init: Some(Box::new(Expr::Ident(
alias.clone().unwrap_or_else(|| local.clone()).into_ident(),
))),
definite: false,
},
Specifier::Namespace { local, .. } => VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(
local.clone().replace_mark(info.mark()).into_ident(),
),
init: Some(Box::new(Expr::Ident(local.clone().into_ident()))),
definite: false,
},
}),
);
};
}
let deps = reexports
.into_par_iter()
.map(|(src, specifiers)| -> Result<_, Error> {
@ -110,7 +146,20 @@ where
});
if let Some(id) = id_of_export_namespace_from {
dep = self.wrap_esm_as_a_var(info, dep, id)?;
dep = self.wrap_esm_as_a_var(plan, dep, &imported, merged, id)?;
let module_plan;
let module_plan = match plan.normal.get(&info.id) {
Some(v) => v,
None => {
module_plan = Default::default();
&module_plan
}
};
dep = self
.merge_imports(plan, &module_plan, dep, &info, merged, false)
.context("failed to merge imports")?;
} else {
dep = self.remark_exports(dep, src.ctxt, None, false);
}
@ -256,6 +305,16 @@ where
assert_eq!(injector.imported, vec![]);
}
if !decls_for_reexport.is_empty() {
entry
.body
.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(VarDecl {
span: DUMMY_SP,
kind: VarDeclKind::Const,
declare: false,
decls: decls_for_reexport,
}))));
}
Ok(())
}
}

View File

@ -1,6 +1,6 @@
use super::plan::Plan;
use super::plan::{NormalPlan, Plan};
use crate::{
bundler::load::{Imports, Specifier},
bundler::load::{Imports, Specifier, TransformedModule},
id::ModuleId,
load::Load,
resolve::Resolve,
@ -84,278 +84,295 @@ where
self.merge_reexports(plan, module_plan, &mut entry, &info, merged)
.context("failed to merge reepxorts")?;
// We handle this kind of modules specially.
if self.scope.should_be_wrapped_with_a_fn(info.id) {
return Ok(entry);
}
// print_hygiene("after: merge_reexports", &self.cm, &entry);
let to_merge: Vec<_> = info
.imports
.specifiers
.iter()
.filter(|(src, _)| {
log::trace!("Checking: {} <= {}", info.fm.name, src.src.value);
// Import and export from same file. We use export to merge it.
if info
.exports
.reexports
.iter()
.any(|(es, _)| es.module_id == src.module_id)
{
return false;
}
// Skip if a dependency is going to be merged by other dependency
module_plan.chunks.contains(&src.module_id)
})
.collect();
let (deps, transitive_deps) = util::join(
|| {
to_merge
.into_par_iter()
.map(|(src, specifiers)| -> Result<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);
}
entry = self
.merge_imports(plan, module_plan, entry, &info, merged, is_entry)
.context("failed to merge imports")?;
Ok(entry)
})
}
pub(super) fn merge_imports(
&self,
plan: &Plan,
module_plan: &NormalPlan,
mut entry: Module,
info: &TransformedModule,
merged: &CHashSet<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) {
// print_hygiene("done", &self.cm, &entry);
@ -412,6 +429,15 @@ where
});
entry.visit_mut_with(&mut DefaultRenamer);
// print_hygiene(
// "done-clean",
// &self.cm,
// &entry
// .clone()
// .fold_with(&mut hygiene())
// .fold_with(&mut fixer(None)),
// );
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -75,7 +75,7 @@ where
.context("failed to analyze module")?;
files.dedup_by_key(|v| v.1.clone());
log::debug!("({}) Storing module: {}", v.id, file_name);
log::debug!("({}, {:?}) Storing module: {}", v.id, v.ctxt(), file_name);
self.scope.store_module(v.clone());
// Load dependencies and store them in the `Scope`

View File

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

View File

@ -3,7 +3,8 @@ use crate::{
id::{ModuleId, ModuleIdGenerator},
util::CloneMap,
};
use swc_common::FileName;
use std::sync::atomic::{AtomicBool, Ordering};
use swc_common::{sync::Lrc, FileName};
#[derive(Debug, Default)]
pub(super) struct Metadata {
@ -18,6 +19,8 @@ pub(super) struct Scope {
/// Cached after applying basical transformations.
transformed_modules: CloneMap<ModuleId, TransformedModule>,
accessed_with_computed_key: CloneMap<ModuleId, Lrc<AtomicBool>>,
}
impl Scope {
@ -40,4 +43,23 @@ impl Scope {
pub fn get_module(&self, id: ModuleId) -> Option<TransformedModule> {
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))
}
#[allow(dead_code)]
pub fn parse(&self, s: &str) -> Module {
let fm = self
.cm
@ -85,6 +86,7 @@ impl<'a> Tester<'a> {
parser.parse_module().unwrap()
}
#[allow(dead_code)]
pub fn assert_eq(&self, m: &Module, expected: &str) {
let expected = self.parse(expected);

View File

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

View File

@ -1,7 +1,103 @@
use std::hash::Hash;
use swc_common::{Span, SyntaxContext};
use std::{hash::Hash, mem::replace};
use swc_atoms::js_word;
use swc_common::{Span, SyntaxContext, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_visit::{noop_visit_mut_type, VisitMut};
/// Helper for migration from [Fold] to [VisitMut]
pub(crate) trait MapWithMut: Sized {
fn dummy() -> Self;
fn take(&mut self) -> Self {
replace(self, Self::dummy())
}
#[inline]
fn map_with_mut<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)]
pub(crate) struct CHashSet<V>
where

View File

@ -2,12 +2,16 @@
//!
//! This module exists because this is way easier than using copying requires
//! files.
use anyhow::{Context, Error};
use std::collections::HashMap;
use std::{
collections::HashMap,
fs::write,
process::{Command, Stdio},
};
use swc_bundler::{Bundler, Load, Resolve};
use swc_common::{sync::Lrc, FileName, SourceFile, SourceMap, Span, GLOBALS};
use swc_ecma_ast::{Expr, Lit, Module, Str};
use swc_ecma_codegen::{text_writer::JsWriter, Emitter};
use swc_ecma_parser::{lexer::Lexer, JscTarget, Parser, StringInput, Syntax, TsConfig};
use swc_ecma_transforms::typescript::strip;
use swc_ecma_visit::FoldWith;
@ -15,11 +19,53 @@ use url::Url;
#[test]
#[ignore = "Too slow"]
fn oak_6_2_0_application() {
bundle("https://deno.land/x/oak@v6.2.0/application.ts");
fn oak_6_3_1_application() {
run("https://deno.land/x/oak@v6.3.1/application.ts");
}
fn bundle(url: &str) -> Module {
#[test]
#[ignore = "Too slow"]
fn oak_6_3_1_mod() {
run("https://deno.land/x/oak@v6.3.1/mod.ts");
}
#[test]
#[ignore = "Too slow"]
fn std_0_74_9_http_server() {
run("https://deno.land/std@0.74.0/http/server.ts");
}
#[test]
#[ignore = "Too slow"]
fn oak_6_3_1_example() {
run("https://deno.land/x/oak@v6.3.1/examples/server.ts");
}
fn run(url: &str) {
let dir = tempfile::tempdir().expect("failed to crate temp file");
let path = dir.path().join("main.js");
println!("{}", path.display());
let src = bundle(url);
write(&path, &src).unwrap();
let output = Command::new("deno")
.arg("run")
.arg("--allow-all")
.arg("--no-check")
.arg(&path)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.status()
.unwrap();
std::mem::forget(dir);
dbg!(output);
assert!(output.success());
}
fn bundle(url: &str) -> String {
let result = testing::run_test2(false, |cm, _handler| {
GLOBALS.with(|globals| {
let bundler = Bundler::new(
@ -29,7 +75,7 @@ fn bundle(url: &str) -> Module {
Resolver,
swc_bundler::Config {
require: false,
disable_inliner: true,
disable_inliner: false,
..Default::default()
},
Box::new(Hook),
@ -37,8 +83,21 @@ fn bundle(url: &str) -> Module {
let mut entries = HashMap::new();
entries.insert("main".to_string(), FileName::Custom(url.to_string()));
let output = bundler.bundle(entries).unwrap();
let module = output.into_iter().next().unwrap().module;
Ok(output.into_iter().next().unwrap().module)
let mut buf = vec![];
{
Emitter {
cfg: swc_ecma_codegen::Config { minify: false },
cm: cm.clone(),
comments: None,
wr: Box::new(JsWriter::new(cm.clone(), "\n", &mut buf, None)),
}
.emit_module(&module)
.unwrap();
}
Ok(String::from_utf8_lossy(&buf).to_string())
})
})
.unwrap();

View File

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

View File

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

View File

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

View File

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

View File

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