mirror of
https://github.com/swc-project/swc.git
synced 2024-12-29 00:23:10 +03:00
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:
parent
a5e6242601
commit
ad7cb6544d
@ -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"
|
||||
|
@ -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, .. }) => {
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
@ -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)),
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ use swc_ecma_visit::FoldWith;
|
||||
|
||||
mod circular;
|
||||
mod cjs;
|
||||
mod computed_key;
|
||||
mod export;
|
||||
mod merge;
|
||||
mod plan;
|
||||
|
@ -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);
|
||||
|
@ -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(())
|
||||
});
|
||||
|
@ -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> {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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(())
|
||||
// })
|
||||
// }
|
||||
|
@ -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`
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -1,7 +1,8 @@
|
||||
function a() {
|
||||
console.log("a");
|
||||
}
|
||||
const a1 = a;
|
||||
function b() {
|
||||
a();
|
||||
a1();
|
||||
}
|
||||
b();
|
||||
|
@ -1,5 +1,6 @@
|
||||
const a = 'a';
|
||||
const a1 = a;
|
||||
function b() {
|
||||
return a;
|
||||
return a1;
|
||||
}
|
||||
b();
|
||||
|
@ -1,6 +1,7 @@
|
||||
class a {
|
||||
}
|
||||
const a1 = a;
|
||||
function b() {
|
||||
return new a();
|
||||
return new a1();
|
||||
}
|
||||
b();
|
||||
|
@ -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);
|
||||
|
@ -1,2 +1,3 @@
|
||||
const a = "a";
|
||||
console.log(a);
|
||||
const a1 = a;
|
||||
console.log(a1);
|
||||
|
Loading…
Reference in New Issue
Block a user