bundler: Fix issues (#1212)

swc_bundler:
 - Bundler rework. (denoland/deno#6802)
 - Reexports are not transitive. (denoland/deno#8246)
 - Dependencies of module with circular dependency. (denoland/deno#8302)
 - Order of injection between import vs export. (denoland/deno#8302)
 - `export *` in wrapped modules. (denoland/deno#8308, denoland/deno#8399)
 - `export { a as b }` in wrapped modules.
 - Fix denoland/deno#8314.
 - Fix denoland/deno#8325.
 - Fix denoland/deno#8344.
 - Make deno test verify exported names. 
 - Handle `export * from './foo'`.

swc_ecma_parser:
 - Don't panic on private name in interface (Closes #1211)

swc_ecma_transforms:
 -  dce: Prevent infinite loop
 -  Faster constant propagation pass.
This commit is contained in:
강동윤 2020-11-19 20:42:56 +09:00 committed by GitHub
parent 723970db1f
commit 4294b5e7ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
257 changed files with 4709 additions and 2909 deletions

View File

@ -28,7 +28,7 @@ jobs:
- name: Set platform name
run: |
export NODE_PLATFORM_NAME=$(node -e "console.log(require('os').platform())")
echo "::set-env name=PLATFORM_NAME::$NODE_PLATFORM_NAME"
echo "name=PLATFORM_NAME::$NODE_PLATFORM_NAME" >> $GITHUB_ENV
shell: bash
- name: Prepare
@ -56,7 +56,6 @@ jobs:
(cd integration-tests/three-js/build/test && qunit -r failonlyreporter unit/three.source.unit.js)
# terser: contains with statement in test
# Rome.js: I forgot the cause, but it didn't work.
# jQuery: browser only (window.document is required)

View File

@ -37,7 +37,7 @@ jobs:
- name: Set platform name
run: |
export NODE_PLATFORM_NAME=$(node -e "console.log(require('os').platform())")
echo "::set-env name=PLATFORM_NAME::$NODE_PLATFORM_NAME"
echo "name=PLATFORM_NAME::$NODE_PLATFORM_NAME" >> $GITHUB_ENV
shell: bash
- name: Install llvm
@ -129,7 +129,7 @@ jobs:
node-version: 12
- name: Set release name
run: echo ::set-env name=RELEASE_VERSION::${GITHUB_REF#refs/*/}
run: echo name=RELEASE_VERSION::${GITHUB_REF#refs/*/} >> $GITHUB_ENV
# Do not cache node_modules, or yarn workspace links broken
- name: Install dependencies

View File

@ -8,7 +8,7 @@ edition = "2018"
license = "Apache-2.0/MIT"
name = "swc_bundler"
repository = "https://github.com/swc-project/swc.git"
version = "0.17.0"
version = "0.17.1"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
@ -33,7 +33,7 @@ swc_common = {version = "0.10.0", path = "../common"}
swc_ecma_ast = {version = "0.35.0", path = "../ecmascript/ast"}
swc_ecma_codegen = {version = "0.41.0", path = "../ecmascript/codegen"}
swc_ecma_parser = {version = "0.43.0", path = "../ecmascript/parser"}
swc_ecma_transforms = {version = "0.30.0", path = "../ecmascript/transforms"}
swc_ecma_transforms = {version = "0.30.1", path = "../ecmascript/transforms"}
swc_ecma_utils = {version = "0.25.0", path = "../ecmascript/utils"}
swc_ecma_visit = {version = "0.21.0", path = "../ecmascript/visit"}
@ -41,6 +41,7 @@ swc_ecma_visit = {version = "0.21.0", path = "../ecmascript/visit"}
hex = "0.4"
reqwest = {version = "0.10.8", features = ["blocking"]}
sha-1 = "0.9"
swc_ecma_transforms = {version = "0.30.1", path = "../ecmascript/transforms", features = ["react"]}
tempfile = "3.1.0"
testing = {version = "0.10.0", path = "../testing"}
url = "2.1.1"

5
bundler/scripts/test.sh Executable file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -eux
cargo test --test fixture
cargo test --test deno $@

View File

@ -1,13 +1,16 @@
use super::{
merge::{ImportDropper, Unexporter},
plan::{CircularPlan, Plan},
use super::plan::CircularPlan;
use crate::{
bundler::{
chunk::{merge::Ctx, sort::sort},
load::TransformedModule,
},
id::Id,
Bundler, Load, ModuleId, Resolve,
};
use crate::{bundler::load::TransformedModule, util::CHashSet, Bundler, Load, ModuleId, Resolve};
use anyhow::{Context, Error};
use std::borrow::Borrow;
use swc_common::DUMMY_SP;
use swc_ecma_ast::*;
use swc_ecma_visit::{noop_visit_type, FoldWith, Node, Visit, VisitMutWith, VisitWith};
use swc_ecma_utils::find_ids;
#[cfg(test)]
mod tests;
@ -20,309 +23,189 @@ where
L: Load,
R: Resolve,
{
pub(super) fn merge_circular_modules(
pub(super) fn merge_circular(
&self,
plan: &Plan,
circular_plan: &CircularPlan,
ctx: &Ctx,
plan: &CircularPlan,
entry_id: ModuleId,
merged: &CHashSet<ModuleId>,
) -> Result<Module, Error> {
assert!(
circular_plan.chunks.len() >= 1,
plan.chunks.len() >= 1,
"# of circular modules should be 2 or greater than 2 including entry. Got {:?}",
circular_plan
plan
);
let entry_module = self.scope.get_module(entry_id).unwrap();
let direct_deps = entry_module
.imports
.specifiers
.iter()
.map(|v| v.0.module_id)
.chain(entry_module.exports.reexports.iter().map(|v| v.0.module_id))
.collect::<Vec<_>>();
let modules = circular_plan
let modules = plan
.chunks
.iter()
.map(|&id| self.scope.get_module(id).unwrap())
.collect::<Vec<_>>();
merged.insert(entry_id);
let mut entry = self
.merge_modules(plan, entry_id, false, true, merged)
.merge_modules(ctx, entry_id, false, false)
.context("failed to merge dependency of a cyclic module")?;
entry.visit_mut_with(&mut ImportDropper {
imports: &entry_module.imports,
});
// print_hygiene("entry:drop_imports", &self.cm, &entry);
let mut deps = circular_plan.chunks.clone();
deps.sort_by_key(|&dep| (!direct_deps.contains(&dep), dep));
if !ctx.merged.insert(entry_id) {
log::debug!("[circular] skip: {:?}", entry_id);
return Ok(Module {
span: DUMMY_SP,
body: Default::default(),
shebang: Default::default(),
});
}
for dep in deps {
let mut exports = vec![];
for item in entry.body.iter_mut() {
match item {
ModuleItem::ModuleDecl(decl) => match decl {
ModuleDecl::ExportDecl(export) => match &export.decl {
Decl::Class(ClassDecl { ident, .. }) | Decl::Fn(FnDecl { ident, .. }) => {
let mut exported = ident.clone();
exported.span.ctxt = entry_module.export_ctxt();
exports.push(ExportSpecifier::Named(ExportNamedSpecifier {
span: export.span,
orig: exported,
exported: None,
}));
}
Decl::Var(var) => {
let ids: Vec<Id> = find_ids(var);
for ident in ids {
let mut exported = ident.into_ident();
exported.span.ctxt = entry_module.export_ctxt();
exports.push(ExportSpecifier::Named(ExportNamedSpecifier {
span: export.span,
orig: exported,
exported: None,
}));
}
}
_ => {}
},
ModuleDecl::ExportNamed(export) => {
for specifier in &mut export.specifiers {
match specifier {
ExportSpecifier::Namespace(_) => {}
ExportSpecifier::Default(_) => {}
ExportSpecifier::Named(named) => {
let mut orig = named.orig.clone();
orig.span.ctxt = entry_module.export_ctxt();
exports.push(ExportSpecifier::Named(ExportNamedSpecifier {
span: export.span,
orig,
exported: named.exported.clone(),
}));
}
}
}
}
ModuleDecl::ExportDefaultDecl(_) => {}
ModuleDecl::ExportDefaultExpr(_) => {}
ModuleDecl::ExportAll(_) => {}
_ => {}
},
ModuleItem::Stmt(_) => {}
}
}
// print_hygiene("[circular] entry:init", &self.cm, &entry);
self.handle_import_deps(ctx, &entry_module, &mut entry, true);
// print_hygiene("[circular] entry:reexport", &self.cm, &entry);
// self.handle_import_deps(&entry_module, &mut entry, true);
// entry.visit_mut_with(&mut ImportDropper {
// imports: &entry_module.imports,
// });
// print_hygiene("entry:drop_imports", &self.cm, &entry);
let mut deps = plan.chunks.clone();
deps.retain(|&dep| {
if dep == entry_id {
continue;
return false;
}
if !merged.insert(dep) {
log::debug!("[circular merge] Already merged: {:?}", dep);
continue;
if !ctx.merged.insert(dep) {
log::debug!("[circular] skip: {:?}", dep);
return false;
}
log::debug!("Circular merge: {:?}", dep);
let new_module = self.merge_two_circular_modules(plan, &modules, entry, dep, merged)?;
true
});
deps.sort();
entry = new_module;
let new_module = self.merge_circular_modules(ctx, &modules, entry, deps)?;
// print_hygiene(
// "[circular] entry:merge_two_circular_modules",
// &self.cm,
// &entry,
// );
entry = new_module;
if !exports.is_empty() {
entry
.body
.push(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
NamedExport {
span: DUMMY_SP,
specifiers: exports,
src: None,
type_only: false,
},
)));
}
// print_hygiene("[circular] done", &self.cm, &entry);
Ok(entry)
}
/// Merges `a` and `b` into one module.
fn merge_two_circular_modules(
fn merge_circular_modules(
&self,
plan: &Plan,
ctx: &Ctx,
_circular_modules: &[TransformedModule],
mut entry: Module,
dep: ModuleId,
merged: &CHashSet<ModuleId>,
deps: Vec<ModuleId>,
) -> Result<Module, Error> {
self.run(|| {
let mut dep = self
.merge_modules(plan, dep, false, true, merged)
.context("failed to merge dependency of a cyclic module")?;
let mut dep_body = vec![];
// print_hygiene("[circular] dep:init", &self.cm, &dep);
for dep in deps {
let dep_info = self.scope.get_module(dep).unwrap();
let mut dep = self
.merge_modules(ctx, dep, false, false)
.context("failed to merge dependency of a cyclic module")?;
dep = dep.fold_with(&mut Unexporter);
// print_hygiene("[circular] dep:init 1", &self.cm, &dep);
self.handle_import_deps(ctx, &dep_info, &mut dep, true);
// print_hygiene("[circular] dep:init 2", &self.cm, &dep);
dep_body.extend(dep.body);
}
// dep = dep.fold_with(&mut Unexporter);
// Merge code
entry.body = merge_respecting_order(dep.body, entry.body);
entry.body = merge_respecting_order(dep_body, entry.body);
// print_hygiene(
// "[circular] END :merge_two_circular_modules",
// &self.cm,
// &entry,
// );
Ok(entry)
})
}
}
/// Originally, this method should create a dependency graph, but
fn merge_respecting_order(mut dep: Vec<ModuleItem>, mut entry: Vec<ModuleItem>) -> Vec<ModuleItem> {
fn merge_respecting_order(dep: Vec<ModuleItem>, entry: Vec<ModuleItem>) -> Vec<ModuleItem> {
let mut new = Vec::with_capacity(dep.len() + entry.len());
// While looping over items from entry, we check for dependency.
loop {
if dep.is_empty() {
log::trace!("dep is empty");
break;
}
let item = dep.drain(..=0).next().unwrap();
// Everything from dep is injected
if entry.is_empty() {
log::trace!("dep is empty");
new.push(item);
new.append(&mut dep);
break;
}
// If the code of entry depends on dependency, we insert dependency source code
// at the position.
if let Some(pos) = dependency_index(&item, &entry) {
log::trace!("Found depndency: {}", pos);
new.extend(entry.drain(..=pos));
new.push(item);
continue;
}
// We checked the length of `dep`
if let Some(pos) = dependency_index(&entry[0], &[&item]) {
log::trace!("Found reverse depndency (index[0]): {}", pos);
new.push(item);
new.extend(entry.drain(..=0));
continue;
}
if let Some(pos) = dependency_index(&entry[0], &dep) {
log::trace!("Found reverse depndency: {}", pos);
new.push(item);
new.extend(dep.drain(..=pos));
new.extend(entry.drain(..=0));
continue;
}
log::trace!("No dependency");
new.push(item);
}
new.extend(entry);
new.extend(dep);
// Append remaining statements.
new.extend(entry);
sort(&mut new);
new
}
/// Searches for top level declaration which provides requirements for `deps`.
fn dependency_index<T>(item: &ModuleItem, deps: &[T]) -> Option<usize>
where
T: Borrow<ModuleItem>,
{
let mut v = DepFinder {
deps,
idx: None,
last_usage_idx: None,
};
item.visit_with(&Invalid { span: DUMMY_SP }, &mut v);
v.idx.or(v.last_usage_idx)
}
struct DepFinder<'a, T>
where
T: Borrow<ModuleItem>,
{
deps: &'a [T],
last_usage_idx: Option<usize>,
idx: Option<usize>,
}
impl<T> Visit for DepFinder<'_, T>
where
T: Borrow<ModuleItem>,
{
noop_visit_type!();
fn visit_ident(&mut self, i: &Ident, _: &dyn Node) {
if self.idx.is_some() {
return;
}
for (idx, dep) in self.deps.iter().enumerate() {
match dep.borrow() {
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
decl: Decl::Class(decl),
..
}))
| ModuleItem::Stmt(Stmt::Decl(Decl::Class(decl))) => {
log::trace!(
"Decl (from dep) = {}{:?}, Ident = {}{:?}",
decl.ident.sym,
decl.ident.span.ctxt,
i.sym,
i.span.ctxt
);
if decl.ident.sym == i.sym && decl.ident.span.ctxt == i.span.ctxt {
self.idx = Some(idx);
log::debug!("Index is {}", idx);
break;
}
}
dep => {
if DepUsageFinder::find(i, dep) {
self.last_usage_idx = Some(idx);
}
}
}
}
}
fn visit_member_expr(&mut self, e: &MemberExpr, _: &dyn Node) {
e.obj.visit_with(e as _, self);
if e.computed {
e.prop.visit_with(e as _, self)
}
}
#[inline]
fn visit_import_decl(&mut self, _: &ImportDecl, _: &dyn Node) {}
#[inline]
fn visit_class_member(&mut self, _: &ClassMember, _: &dyn Node) {}
#[inline]
fn visit_function(&mut self, _: &Function, _: &dyn Node) {}
#[inline]
fn visit_arrow_expr(&mut self, _: &ArrowExpr, _: &dyn Node) {}
/// We only search for top-level binding
#[inline]
fn visit_stmts(&mut self, _: &[Stmt], _: &dyn Node) {}
/// We only search for top-level binding
#[inline]
fn visit_block_stmt(&mut self, _: &BlockStmt, _: &dyn Node) {}
}
/// Finds usage of `ident`
struct DepUsageFinder<'a> {
ident: &'a Ident,
found: bool,
}
impl<'a> Visit for DepUsageFinder<'a> {
noop_visit_type!();
fn visit_call_expr(&mut self, e: &CallExpr, _: &dyn Node) {
if self.found {
return;
}
match &e.callee {
ExprOrSuper::Super(_) => {}
ExprOrSuper::Expr(callee) => match &**callee {
Expr::Ident(..) => {}
_ => {
callee.visit_with(e, self);
}
},
}
e.args.visit_with(e, self);
}
fn visit_ident(&mut self, i: &Ident, _: &dyn Node) {
if self.found {
return;
}
if i.span.ctxt() == self.ident.span.ctxt() && i.sym == self.ident.sym {
self.found = true;
}
}
fn visit_member_expr(&mut self, e: &MemberExpr, _: &dyn Node) {
if self.found {
return;
}
e.obj.visit_with(e as _, self);
if e.computed {
e.prop.visit_with(e as _, self);
}
}
}
impl<'a> DepUsageFinder<'a> {
pub fn find<N>(ident: &'a Ident, node: &N) -> bool
where
N: VisitWith<Self>,
{
let mut v = DepUsageFinder {
ident,
found: false,
};
node.visit_with(&Invalid { span: DUMMY_SP } as _, &mut v);
v.found
}
}

View File

@ -1,8 +1,8 @@
use super::*;
use crate::debug::print_hygiene;
use swc_common::{sync::Lrc, FileName, SourceMap};
use swc_ecma_parser::{lexer::Lexer, JscTarget, Parser, StringInput, Syntax};
use swc_ecma_utils::drop_span;
use testing::assert_eq;
fn parse(cm: Lrc<SourceMap>, name: &str, src: &str) -> Module {
let fm = cm.new_source_file(FileName::Custom(name.into()), src.into());
@ -21,29 +21,24 @@ fn parse(cm: Lrc<SourceMap>, name: &str, src: &str) -> Module {
#[track_caller]
fn assert_merge_respecting_order(modules: &[&str], output: &str) {
for i in 0..modules.len() {
log::info!("[{}] Testing", i);
::testing::run_test2(false, |cm, _handler| {
let mut entry = parse(cm.clone(), &format!("entry-{}", i), modules[i]).body;
::testing::run_test2(false, |cm, _handler| {
let mut entry = parse(cm.clone(), &format!("entry"), modules[0]);
for j in 0..modules.len() {
if i == j {
continue;
}
for i in 1..modules.len() {
let dep = parse(cm.clone(), &format!("deps-{}", i), modules[i]);
entry.body = merge_respecting_order(entry.body, dep.body);
let dep = parse(cm.clone(), &format!("deps-{}-{}", i, j), modules[j]);
entry = merge_respecting_order(entry, dep.body);
}
print_hygiene("merge", &cm, &entry);
}
let output = parse(cm.clone(), "output", output);
assert_eq!(entry, output.body, "[{}]", i);
let output = parse(cm.clone(), "output", output);
if entry.body != output.body {
panic!()
}
log::info!("[{}] Success", i);
Ok(())
})
.unwrap()
}
Ok(())
})
.unwrap()
}
#[test]
@ -57,27 +52,59 @@ fn simple_two() {
);
}
#[track_caller]
fn assert_dependency_index(entry: &str, dep: &str, expected: usize) {
::testing::run_test2(false, |cm, _handler| {
let entry = parse(cm.clone(), "entry", entry);
let dep = parse(cm.clone(), "dep", dep);
#[test]
fn many_vars_1() {
assert_merge_respecting_order(
&[
"
const A6 = A5;
class B6 extends A6 {
}
const B7 = B6;
let calculated = dependency_index(&entry.body[0], &dep.body);
assert_eq!(calculated, Some(expected));
Ok(())
})
.unwrap();
",
"
const B4 = B7;
class A4 {
method() {
return new B4();
}
}
const A5 = A4;
",
],
"
class A4 {
method() {
return new B4();
}
}
const A5 = A4;
const A6 = A5;
class B6 extends A6 {
}
const B7 = B6;
const B4 = B7;
",
);
}
#[test]
fn dep_index_class() {
assert_dependency_index("class A extends B {}", "class B {}", 0);
}
#[test]
fn dep_index_export_class() {
assert_dependency_index("class A extends B {}", "export class B {}", 0);
fn no_dep_first_01() {
assert_merge_respecting_order(
&[
"
const b1 = b2;
",
"
const b2 = 2;
const b3 = b1;
",
],
"
const b2 = 2;
const b1 = b2;
const b3 = b1;
",
);
}

View File

@ -1,10 +1,17 @@
use super::{merge::Unexporter, plan::Plan};
use crate::{bundler::load::TransformedModule, Bundler, Load, ModuleId, Resolve};
use super::merge::Unexporter;
use crate::{
bundler::{
chunk::{merge::Ctx, plan::Dependancy},
load::TransformedModule,
},
Bundler, Load, Resolve,
};
use anyhow::Error;
use std::{borrow::Cow, sync::atomic::Ordering};
use swc_common::{Mark, SyntaxContext, DUMMY_SP};
use swc_atoms::js_word;
use swc_common::{SyntaxContext, DUMMY_SP};
use swc_ecma_ast::{ModuleItem, *};
use swc_ecma_utils::{prepend, undefined, ExprFactory};
use swc_ecma_utils::{prepend, quote_ident, undefined, ExprFactory};
use swc_ecma_visit::{noop_visit_mut_type, FoldWith, VisitMut, VisitMutWith};
impl<L, R> Bundler<'_, L, R>
@ -35,26 +42,30 @@ where
/// modules.
pub(super) fn merge_cjs(
&self,
plan: &Plan,
ctx: &Ctx,
is_entry: bool,
entry: &mut Module,
info: &TransformedModule,
dep: Cow<Module>,
dep_info: &TransformedModule,
targets: &mut Vec<ModuleId>,
targets: &mut Vec<Dependancy>,
) -> Result<(), Error> {
if info.is_es6 && dep_info.is_es6 {
return Ok(());
}
log::debug!("Merging as a common js module: {}", info.fm.name);
// If src is none, all requires are transpiled
let mut v = RequireReplacer {
is_entry,
ctxt: dep_info.ctxt(),
load_var: Ident::new("load".into(), DUMMY_SP.with_ctxt(dep_info.ctxt())),
ctxt: dep_info.export_ctxt(),
load_var: Ident::new("load".into(), DUMMY_SP.with_ctxt(dep_info.export_ctxt())),
replaced: false,
};
entry.body.visit_mut_with(&mut v);
if v.replaced {
if let Some(idx) = targets.iter().position(|v| *v == dep_info.id) {
if let Some(idx) = targets.iter().position(|v| v.id == dep_info.id) {
targets.remove(idx);
}
@ -65,12 +76,15 @@ where
let mut dep = dep.into_owned().fold_with(&mut Unexporter);
dep.visit_mut_with(&mut ImportDropper);
dep.visit_mut_with(&mut DefaultHandler {
local_ctxt: dep_info.local_ctxt(),
});
prepend(
&mut entry.body,
ModuleItem::Stmt(wrap_module(
SyntaxContext::empty(),
dep_info.mark(),
dep_info.local_ctxt(),
load_var,
dep,
)),
@ -79,15 +93,15 @@ where
log::warn!("Injecting load");
}
if let Some(normal_plan) = plan.normal.get(&dep_info.id) {
for &dep_id in &normal_plan.chunks {
if !targets.contains(&dep_id) {
if let Some(normal_plan) = ctx.plan.normal.get(&dep_info.id) {
for dep in normal_plan.chunks.iter() {
if !targets.contains(&dep) {
continue;
}
let dep_info = self.scope.get_module(dep_id).unwrap();
let dep_info = self.scope.get_module(dep.id).unwrap();
self.merge_cjs(
plan,
ctx,
false,
entry,
info,
@ -105,7 +119,7 @@ where
fn wrap_module(
helper_ctxt: SyntaxContext,
top_level_mark: Mark,
local_ctxt: SyntaxContext,
load_var: Ident,
dep: Module,
) -> Stmt {
@ -118,19 +132,13 @@ fn wrap_module(
Param {
span: DUMMY_SP,
decorators: Default::default(),
pat: Pat::Ident(Ident::new(
"module".into(),
DUMMY_SP.apply_mark(top_level_mark),
)),
pat: Pat::Ident(Ident::new("module".into(), DUMMY_SP.with_ctxt(local_ctxt))),
},
// exports
Param {
span: DUMMY_SP,
decorators: Default::default(),
pat: Pat::Ident(Ident::new(
"exports".into(),
DUMMY_SP.apply_mark(top_level_mark),
)),
pat: Pat::Ident(Ident::new("exports".into(), DUMMY_SP.with_ctxt(local_ctxt))),
},
],
decorators: vec![],
@ -344,3 +352,41 @@ impl VisitMut for ImportDropper {
}
}
}
struct DefaultHandler {
local_ctxt: SyntaxContext,
}
impl VisitMut for DefaultHandler {
noop_visit_mut_type!();
fn visit_mut_expr(&mut self, e: &mut Expr) {
e.visit_mut_children_with(self);
match e {
Expr::Ident(i) => {
if i.sym == js_word!("default") {
*e = Expr::Member(MemberExpr {
span: i.span,
obj: ExprOrSuper::Expr(Box::new(Expr::Ident(Ident::new(
"module".into(),
DUMMY_SP.with_ctxt(self.local_ctxt),
)))),
prop: Box::new(Expr::Ident(quote_ident!("exports"))),
computed: false,
});
return;
}
}
_ => {}
}
}
fn visit_mut_member_expr(&mut self, e: &mut MemberExpr) {
e.obj.visit_mut_with(self);
if e.computed {
e.prop.visit_mut_with(self);
}
}
}

View File

@ -1,6 +1,5 @@
use super::plan::Plan;
use crate::{bundler::load::TransformedModule, util::CHashSet, Bundler, Load, ModuleId, Resolve};
use anyhow::Error;
use crate::{bundler::chunk::merge::Ctx, Bundler, Load, ModuleId, Resolve};
use anyhow::{bail, Error};
use std::mem::take;
use swc_atoms::js_word;
use swc_common::DUMMY_SP;
@ -29,15 +28,17 @@ where
/// };
/// })();
/// ```
pub(super) fn wrap_esm_as_a_var(
pub(super) fn wrap_esm(
&self,
plan: &Plan,
ctx: &Ctx,
id: ModuleId,
module: Module,
info: &TransformedModule,
merged: &CHashSet<ModuleId>,
id: Ident,
) -> Result<Module, Error> {
let span = module.span;
let var_name = match self.scope.wrapped_esm_id(id) {
Some(v) => v,
None => bail!("{:?} should not be wrapped with a function", id),
};
let mut module_items = vec![];
@ -48,6 +49,13 @@ where
.into_iter()
.filter_map(|v| match v {
ModuleItem::Stmt(s) => Some(s),
ModuleItem::ModuleDecl(ModuleDecl::ExportAll(ref export)) => {
// We handle this later.
let mut map = ctx.export_stars_in_wrapped.lock();
map.entry(id).or_default().push(export.span.ctxt);
module_items.push(v);
None
}
_ => {
module_items.push(v);
None
@ -87,31 +95,18 @@ where
decls: vec![VarDeclarator {
span: DUMMY_SP,
definite: false,
name: Pat::Ident(id.clone()),
name: Pat::Ident(var_name.into_ident()),
init: Some(Box::new(module_expr)),
}],
};
module_items.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(var_decl))));
let module = Module {
Ok(Module {
span: DUMMY_SP,
shebang: None,
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)
})
}
}
@ -206,11 +201,30 @@ impl Fold for ExportToReturn {
DefaultDecl::TsInterfaceDecl(_) => None,
},
ModuleDecl::ExportDefaultExpr(_) => None,
ModuleDecl::ExportAll(_) => {
unimplemented!("export * from 'foo' inside a module loaded with computed key")
ModuleDecl::ExportAll(export) => {
return ModuleItem::ModuleDecl(ModuleDecl::ExportAll(export))
}
ModuleDecl::ExportNamed(_) => {
unimplemented!("export {{ a }} from 'foo' inside a module loaded with computed key")
ModuleDecl::ExportNamed(named) => {
for specifier in &named.specifiers {
match specifier {
ExportSpecifier::Namespace(_) => {}
ExportSpecifier::Default(_) => {}
ExportSpecifier::Named(named) => match &named.exported {
Some(exported) => {
self.exports
.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(
KeyValueProp {
key: PropName::Ident(exported.clone()),
value: Box::new(Expr::Ident(named.orig.clone())),
},
))))
}
None => {}
},
}
}
return ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(named));
}
ModuleDecl::TsImportEquals(_) => None,
ModuleDecl::TsExportAssignment(_) => None,
@ -232,15 +246,13 @@ impl Fold for ExportToReturn {
_ => true,
});
if !self.exports.is_empty() {
new.push(ModuleItem::Stmt(Stmt::Return(ReturnStmt {
new.push(ModuleItem::Stmt(Stmt::Return(ReturnStmt {
span: DUMMY_SP,
arg: Some(Box::new(Expr::Object(ObjectLit {
span: DUMMY_SP,
arg: Some(Box::new(Expr::Object(ObjectLit {
span: DUMMY_SP,
props: take(&mut self.exports),
}))),
})));
}
props: take(&mut self.exports),
}))),
})));
new
}

View File

@ -1,19 +1,18 @@
use super::plan::{NormalPlan, Plan};
use crate::{
bundler::load::{Source, Specifier, TransformedModule},
util::{CHashSet, IntoParallelIterator},
bundler::{
chunk::merge::Ctx,
load::{Source, Specifier, TransformedModule},
},
util::{ExprExt, MapWithMut, VarDeclaratorExt},
Bundler, Load, ModuleId, Resolve,
};
use anyhow::{Context, Error};
#[cfg(feature = "concurrent")]
use rayon::iter::ParallelIterator;
use std::{
collections::HashMap,
mem::{replace, take},
};
use std::mem::{replace, take};
use swc_common::{Spanned, SyntaxContext, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_utils::{ident::IdentLike, Id};
use swc_ecma_utils::{find_ids, ident::IdentLike, Id};
use swc_ecma_visit::{noop_fold_type, noop_visit_mut_type, Fold, FoldWith, VisitMut, VisitMutWith};
impl<L, R> Bundler<'_, L, R>
@ -52,318 +51,196 @@ where
///
/// console.log(bar, baz);
/// ```
pub(super) fn merge_reexports(
pub(super) fn merge_export(
&self,
plan: &Plan,
nomral_plan: &NormalPlan,
entry: &mut Module,
info: &TransformedModule,
merged: &CHashSet<ModuleId>,
) -> Result<(), Error> {
log::trace!("merge_reexports: {}", info.fm.name);
ctx: &Ctx,
dep_id: ModuleId,
specifiers: &[Specifier],
) -> Result<Module, Error> {
self.run(|| {
log::debug!("Reexporting {:?}", dep_id);
let dep_info = self.scope.get_module(dep_id).unwrap();
let mut dep = self
.merge_modules(ctx, dep_id, false, true)
.context("failed to get module for merging")?;
// Transitive dependencies
let mut additional_modules = vec![];
let mut reexports = vec![];
let mut decls_for_reexport: HashMap<_, Vec<VarDeclarator>> = HashMap::new();
// print_hygiene(
// &format!(
// "reexport: load dep: {} ({:?}, {:?})",
// dep_info.fm.name,
// dep_info.local_ctxt(),
// dep_info.export_ctxt()
// ),
// &self.cm,
// &dep,
// );
// Remove transitive dependencies which is merged by parent moudle.
for v in info.exports.reexports.clone() {
if nomral_plan.chunks.contains(&v.0.module_id) {
if v.1.is_empty() {
additional_modules.push(v.clone());
}
self.handle_reexport(&dep_info, &mut dep);
reexports.push(v);
} else {
additional_modules.push(v);
}
}
// print_hygiene(&format!("dep: handle reexport"), &self.cm, &dep);
for (src, specifiers) in info.exports.reexports.iter() {
let imported = self.scope.get_module(src.module_id).unwrap();
// for stmt in &mut dep.body {
// let decl = match stmt {
// ModuleItem::ModuleDecl(decl) => decl,
// ModuleItem::Stmt(_) => continue,
// };
// export * from './foo';
if specifiers.is_empty() {
let vars = decls_for_reexport.entry(src.module_id).or_default();
// match decl {
// ModuleDecl::ExportDecl(_) => {}
// ModuleDecl::ExportNamed(export) => {
// for specifier in &mut export.specifiers {
// match specifier {
// ExportSpecifier::Namespace(ns) => {}
// ExportSpecifier::Default(default) => {}
// ExportSpecifier::Named(named) => match &mut
// named.exported { Some(exported) => {
// if exported.span.ctxt != dep_info.local_ctxt() {
// continue;
// }
for specifier in imported.exports.items.iter() {
let var = match specifier {
Specifier::Specific { local, alias } => {
let init = Some(Box::new(Expr::Ident(
alias.clone().unwrap_or_else(|| local.clone()).into_ident(),
)));
// exported.span =
//
// exported.span.with_ctxt(dep_info.export_ctxt());
// } None => {
// if named.orig.span.ctxt != dep_info.local_ctxt()
// { continue;
// }
VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(
local.clone().replace_mark(info.mark()).into_ident(),
),
init,
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,
},
};
vars.push(var)
}
// named.exported = Some(Ident::new(
// named.orig.sym.clone(),
//
// named.orig.span.with_ctxt(dep_info.export_ctxt()),
// )); }
// },
// }
// }
// }
// ModuleDecl::ExportDefaultDecl(_) => {}
// ModuleDecl::ExportDefaultExpr(_) => {}
// ModuleDecl::ExportAll(_) => {}
// _ => {}
// }
// }
for (_, specifiers) in imported.exports.reexports.iter() {
for specifier in specifiers {
let var = match specifier {
Specifier::Specific { local, alias } => {
let init = match alias {
Some(alias) => {
Some(Box::new(Expr::Ident(alias.clone().into_ident())))
}
None => continue,
};
VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(
local.clone().replace_mark(info.mark()).into_ident(),
),
init,
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,
},
};
vars.push(var)
}
}
};
}
let deps = reexports
.into_par_iter()
.map(|(src, specifiers)| -> Result<_, Error> {
let imported = self.scope.get_module(src.module_id).unwrap();
assert!(imported.is_es6, "Reexports are es6 only");
info.helpers.extend(&imported.helpers);
info.swc_helpers.extend_from(&imported.swc_helpers);
if !merged.insert(src.module_id) {
return Ok(None);
}
log::debug!("Merging exports: {} <- {}", info.fm.name, src.src.value);
let mut dep = self
.merge_modules(plan, src.module_id, false, false, merged)
.with_context(|| {
format!(
"failed to merge for reexport: ({}):{} <= ({}):{}",
info.id, info.fm.name, src.module_id, src.src.value
)
})?;
// print_hygiene(&format!("dep: start"), &self.cm, &dep);
let id_of_export_namespace_from = specifiers.iter().find_map(|s| match s {
Specifier::Namespace { local, all: true } => Some(Ident::new(
local.sym().clone(),
DUMMY_SP.with_ctxt(info.ctxt()),
)),
_ => None,
});
if let Some(id) = id_of_export_namespace_from {
dep = self.wrap_esm_as_a_var(plan, dep, &imported, merged, id)?;
} else {
dep = self.remark_exports(dep, src.ctxt, None, false);
}
// print_hygiene(&format!("dep: remark exports"), &self.cm, &dep);
if !specifiers.is_empty() {
dep.visit_mut_with(&mut UnexportAsVar {
dep_ctxt: src.ctxt,
_entry_ctxt: info.ctxt(),
_exports: &specifiers,
});
// print_hygiene(&format!("dep: unexport as var"), &self.cm, &dep);
dep = dep.fold_with(&mut DepUnexporter {
exports: &specifiers,
});
// print_hygiene(&format!("dep: unexport"), &self.cm, &dep);
}
Ok(Some((src, dep)))
})
.collect::<Vec<_>>();
{
let mut normal_reexports = vec![];
let mut star_reexports = vec![];
for (src, specifiers) in additional_modules {
if specifiers.is_empty() {
continue;
}
// If a dependency is indirect, we need to export items from it manually.
let is_indirect = !nomral_plan.chunks.contains(&src.module_id);
let add_to = if specifiers.is_empty() && is_indirect {
// User provided code like `export * from './foo';`, but planner decide to merge
// it within dependency module. So we reexport them using a named export.
&mut star_reexports
} else {
&mut normal_reexports
};
if let Some(module_name) = self.scope.wrapped_esm_id(dep_info.id) {
dep = self.wrap_esm(ctx, dep_info.id, dep)?;
for specifier in specifiers {
let (imported, exported) = match specifier {
Specifier::Specific { local, alias } => {
let alias = alias.unwrap_or_else(|| local.clone());
let local = local.replace_mark(info.mark());
(local.into_ident(), alias.into_ident())
match specifier {
Specifier::Namespace { local, .. } => {
dep.body.push(
module_name
.assign_to(local.clone())
.into_module_item("merge_export"),
);
break;
}
Specifier::Namespace { .. } => continue,
_ => {}
}
}
}
if !specifiers.is_empty() {
dep.visit_mut_with(&mut UnexportAsVar {
dep_export_ctxt: dep_info.export_ctxt(),
_specifiers: &specifiers,
});
// print_hygiene(&format!("dep: unexport as var"), &self.cm, &dep);
dep = dep.fold_with(&mut DepUnexporter {
exports: &specifiers,
});
// print_hygiene(&format!("dep: unexport"), &self.cm, &dep);
}
// TODO: Add varaible based on specifers
Ok(dep)
})
}
/// # ExportDecl
///
/// For exported declarations, We should inject named exports.
///
/// ```ts
/// export const b__9 = 1;
/// console.log(b__9);
/// ```
///
/// ```ts
/// const b__9 = 1;
/// export { b__9 as b__10 };
/// console.log(b__9);
/// ```
fn handle_reexport(&self, info: &TransformedModule, module: &mut Module) {
let mut new_body = Vec::with_capacity(module.body.len() + 20);
for stmt in &mut module.body {
let mut vars = vec![];
let mut stmt = stmt.take();
match &mut stmt {
ModuleItem::ModuleDecl(ModuleDecl::Import(import)) => {
for specifier in &import.specifiers {
match specifier {
ImportSpecifier::Named(named) => match &named.imported {
Some(imported) => {
vars.push(imported.clone().assign_to(named.local.clone()));
}
None => {}
},
_ => {}
}
}
import.specifiers.clear();
}
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export)) => {
match &export.decl {
Decl::Class(ClassDecl { ident, .. }) | Decl::Fn(FnDecl { ident, .. }) => {
let mut exported = ident.clone();
exported.span.ctxt = info.export_ctxt();
vars.push(ident.clone().assign_to(exported));
}
Decl::Var(var) => {
//
let ids: Vec<Ident> = find_ids(&var.decls);
vars.extend(
ids.into_iter()
.map(|i| {
let mut exported = i.clone();
exported.span.ctxt = info.export_ctxt();
i.assign_to(exported)
})
.map(From::from),
);
}
_ => {}
};
add_to.push((imported, exported));
}
_ => {}
}
if !normal_reexports.is_empty() {
entry
.body
.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(VarDecl {
span: DUMMY_SP,
kind: VarDeclKind::Const,
declare: false,
decls: normal_reexports
.into_iter()
.map(|(imported, exported)| {
let var = VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(imported),
init: Some(Box::new(Expr::Ident(exported))),
definite: false,
};
var
})
.collect(),
}))));
}
if !star_reexports.is_empty() {
entry
.body
.push(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
NamedExport {
span: DUMMY_SP,
specifiers: star_reexports
.into_iter()
.map(|(imported, exported)| {
ExportNamedSpecifier {
span: DUMMY_SP,
orig: exported.clone(),
exported: Some(imported.clone()),
}
.into()
})
.collect(),
src: None,
type_only: false,
},
)));
new_body.push(stmt);
for var in vars {
new_body.push(var.into_module_item("from_export_rs"))
}
}
for dep in deps {
let dep = dep?;
let dep = match dep {
Some(v) => v,
None => continue,
};
let (src, dep) = dep;
// print_hygiene(
// &format!(
// "entry: before reexport injection {:?} <- {:?}",
// info.ctxt(),
// src.ctxt,
// ),
// &self.cm,
// &entry,
// );
// Replace import statement / require with module body
let mut injector = ExportInjector {
imported: dep.body,
source: src.clone(),
};
entry.body.visit_mut_with(&mut injector);
// Inject variables
if let Some(decls) = decls_for_reexport.remove(&src.module_id) {
if !decls.is_empty() {
entry
.body
.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(VarDecl {
span: DUMMY_SP,
kind: VarDeclKind::Const,
declare: false,
decls,
}))));
}
}
// print_hygiene(
// &format!(
// "entry:reexport injection {:?} <- {:?}",
// info.ctxt(),
// src.ctxt,
// ),
// &self.cm,
// &entry,
// );
assert_eq!(injector.imported, vec![]);
}
let decls_for_reexport: Vec<_> = decls_for_reexport
.into_iter()
.map(|(_, decls)| decls)
.flatten()
.collect();
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(())
module.body = new_body;
}
}
struct ExportInjector {
imported: Vec<ModuleItem>,
source: Source,
pub(super) struct ExportInjector {
pub imported: Vec<ModuleItem>,
pub source: Source,
}
impl VisitMut for ExportInjector {
@ -400,7 +277,7 @@ impl VisitMut for ExportInjector {
..
}) => {
let mut imported = imported.clone();
imported.span = imported.span.with_ctxt(self.source.ctxt);
imported.span = imported.span.with_ctxt(self.source.export_ctxt);
Some(VarDeclarator {
span: DUMMY_SP,
@ -413,13 +290,8 @@ impl VisitMut for ExportInjector {
})
.collect::<Vec<_>>();
if !decls.is_empty() {
buf.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(VarDecl {
span: DUMMY_SP,
kind: VarDeclKind::Const,
declare: false,
decls,
}))));
for var in decls {
buf.push(var.into_module_item("ExportInjector"));
}
}
@ -492,12 +364,10 @@ impl VisitMut for ExportInjector {
/// export { foo#7 as foo#5 } where #5 is mark of current entry.
struct UnexportAsVar<'a> {
/// Syntax context for the generated variables.
dep_ctxt: SyntaxContext,
_entry_ctxt: SyntaxContext,
dep_export_ctxt: SyntaxContext,
/// Exports to preserve
_exports: &'a [Specifier],
_specifiers: &'a [Specifier],
}
impl VisitMut for UnexportAsVar<'_> {
@ -513,20 +383,16 @@ impl VisitMut for UnexportAsVar<'_> {
Box::new(Expr::Invalid(Invalid { span: DUMMY_SP })),
);
*n = ModuleItem::Stmt(Stmt::Decl(Decl::Var(VarDecl {
span: export.span,
kind: VarDeclKind::Const,
declare: false,
decls: vec![VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(Ident::new(
"__default".into(),
expr.span().with_ctxt(self.dep_ctxt),
)),
init: Some(expr),
definite: false,
}],
})));
*n = VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(Ident::new(
"__default".into(),
expr.span().with_ctxt(self.dep_export_ctxt),
)),
init: Some(expr),
definite: false,
}
.into_module_item("UnexportAsVar");
}
ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
ref export @ NamedExport { src: None, .. },
@ -554,17 +420,23 @@ impl VisitMut for UnexportAsVar<'_> {
}
}
None => {
log::trace!("Alias: {:?} -> {:?}", n.orig, self.dep_ctxt);
if n.orig.span.ctxt != self.dep_export_ctxt {
log::trace!(
"Alias: {:?} -> {:?}",
n.orig,
self.dep_export_ctxt
);
decls.push(VarDeclarator {
span: n.span,
name: Pat::Ident(Ident::new(
n.orig.sym.clone(),
n.orig.span.with_ctxt(self.dep_ctxt),
)),
init: Some(Box::new(Expr::Ident(n.orig.clone()))),
definite: false,
})
decls.push(VarDeclarator {
span: n.span,
name: Pat::Ident(Ident::new(
n.orig.sym.clone(),
n.orig.span.with_ctxt(self.dep_export_ctxt),
)),
init: Some(Box::new(Expr::Ident(n.orig.clone()))),
definite: false,
})
}
}
},
}

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,8 @@
use super::{load::TransformedModule, Bundler};
use crate::{id::ModuleId, load::Load, resolve::Resolve, util::IntoParallelIterator, Bundle};
use crate::{
bundler::chunk::merge::Ctx, id::ModuleId, load::Load, resolve::Resolve,
util::IntoParallelIterator, Bundle,
};
use anyhow::{Context, Error};
#[cfg(feature = "rayon")]
use rayon::iter::ParallelIterator;
@ -11,7 +14,7 @@ mod computed_key;
mod export;
mod merge;
mod plan;
mod remark;
mod sort;
#[derive(Debug)]
struct InternalEntry {
@ -43,13 +46,19 @@ where
entries: HashMap<String, TransformedModule>,
) -> Result<Vec<Bundle>, Error> {
let plan = self.determine_entries(entries).context("failed to plan")?;
let merged = Default::default();
let ctx = Ctx {
plan,
merged: Default::default(),
transitive_remap: Default::default(),
export_stars_in_wrapped: Default::default(),
};
Ok((&*plan.entries)
Ok((&*ctx.plan.entries)
.into_par_iter()
.map(|&entry| {
self.run(|| {
let kind = plan
let kind = ctx
.plan
.bundle_kinds
.get(&entry)
.unwrap_or_else(|| {
@ -58,7 +67,7 @@ where
.clone();
let module = self
.merge_modules(&plan, entry, true, false, &merged)
.merge_modules(&ctx, entry, true, true)
.context("failed to merge module")
.unwrap(); // TODO

View File

@ -107,29 +107,29 @@ pub(super) struct Plan {
pub bundle_kinds: HashMap<ModuleId, BundleKind>,
}
impl Plan {
pub fn entry_as_circular(&self, entry: ModuleId) -> Option<&CircularPlan> {
let plan = self.circular.get(&entry)?;
if plan.chunks.is_empty() {
return None;
}
Some(plan)
}
}
#[derive(Debug, Default)]
pub(super) struct NormalPlan {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum DepType {
/// Direct dependencies
pub chunks: Vec<ModuleId>,
Direct,
/// Used to handle
///
/// - a -> b
/// - a -> c
/// - b -> d
/// - c -> d
pub transitive_chunks: Vec<ModuleId>,
Transitive,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct Dependancy {
pub ty: DepType,
pub id: ModuleId,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(test, derive(PartialEq, Eq))]
pub(super) struct NormalPlan {
pub chunks: Vec<Dependancy>,
}
#[derive(Debug, Default)]
@ -150,17 +150,10 @@ where
entries: HashMap<String, TransformedModule>,
) -> Result<Plan, Error> {
let plan = self.calculate_plan(entries)?;
let plan = self.handle_duplicates(plan);
Ok(plan)
}
/// 1. For entry -> a -> b -> a, entry -> a->c, entry -> b -> c,
/// we change c as transitive dependancy of entry.
fn handle_duplicates(&self, plan: Plan) -> Plan {
plan
}
fn calculate_plan(&self, entries: HashMap<String, TransformedModule>) -> Result<Plan, Error> {
let mut builder = PlanBuilder::default();
@ -211,7 +204,7 @@ where
Ok(self.build_plan(&metadata, builder))
}
fn build_plan(&self, metadata: &HashMap<ModuleId, Metadata>, builder: PlanBuilder) -> Plan {
fn build_plan(&self, _metadata: &HashMap<ModuleId, Metadata>, builder: PlanBuilder) -> Plan {
let mut plans = Plan::default();
for (id, kind) in builder.kinds.iter() {
@ -233,6 +226,14 @@ where
.collect();
deps.sort();
let should_be_reexport = deps.iter().any(|&dep| {
builder
.all_deps
.get(&(entry, dep))
.copied()
.unwrap_or(false)
});
for &dep in &deps {
if let Some(circular_members) = builder.circular.get(entry) {
if circular_members.contains(&dep) {
@ -243,7 +244,15 @@ where
);
if entry != root_entry && dep != root_entry {
// done.insert(dep);
plans.normal.entry(entry).or_default().chunks.push(dep);
plans
.normal
.entry(entry)
.or_default()
.chunks
.push(Dependancy {
id: dep,
ty: DepType::Direct,
});
}
continue;
}
@ -260,132 +269,182 @@ where
.collect::<Vec<_>>();
dependants.sort();
if metadata.get(&dep).map(|md| md.bundle_cnt).unwrap_or(0) == 1 {
log::debug!("{:?} depends on {:?}", entry, dep);
log::debug!("{:?} depends on {:?}", entry, dep);
let is_reexport = builder
.all_deps
.get(&(entry, dep))
.copied()
.unwrap_or(false);
// Common js support.
if !is_es6 {
// Dependancy of
//
// a -> b
// b -> c
//
// results in
//
// a <- b
// b <- c
//
if dependants.len() <= 1 {
plans.normal.entry(entry).or_default().chunks.push(dep);
continue;
}
// We now have a module depended by multiple modules. Let's say
//
// a -> b
// a -> c
// b -> c
//
// results in
//
// a <- b
// a <- c
let module = least_common_ancestor(&builder, &dependants);
let normal_plan = plans.normal.entry(module).or_default();
for &dep in &deps {
if !normal_plan.chunks.contains(&dep)
&& !normal_plan.transitive_chunks.contains(&dep)
{
if dependants.contains(&module) {
log::trace!("Normal: {:?} => {:?}", module, dep);
// `entry` depends on `module` directly
normal_plan.chunks.push(dep);
} else {
log::trace!("Transitive: {:?} => {:?}", module, dep);
normal_plan.transitive_chunks.push(dep);
}
}
}
let is_reexport = builder
.all_deps
.get(&(entry, dep))
.copied()
.unwrap_or(false);
// Common js support.
if !is_es6 {
// Dependancy of
//
// a -> b
// b -> c
//
// results in
//
// a <- b
// b <- c
//
if dependants.len() <= 1 {
plans
.normal
.entry(entry)
.or_default()
.chunks
.push(Dependancy {
id: dep,
ty: DepType::Direct,
});
continue;
}
if is_reexport {
let normal_plan = plans.normal.entry(entry).or_default();
if !normal_plan.chunks.contains(&dep) {
done.insert(dep);
// We now have a module depended by multiple modules. Let's say
//
// a -> b
// a -> c
// b -> c
//
// results in
//
// a <- b
// a <- c
let module = least_common_ancestor(&builder, &dependants);
let normal_plan = plans.normal.entry(module).or_default();
log::trace!("Normal: {:?} => {:?}", entry, dep);
normal_plan.chunks.push(dep);
}
continue;
}
for &dep in &deps {
let contains = normal_plan.chunks.iter().any(|d| d.id == dep);
if 2 <= dependants.len() {
// Should be merged as a transitive dependency.
let higher_module = if plans.entries.contains(&dependants[0]) {
dependants[0]
} else if dependants.len() == 2
&& plans.entries.contains(&dependants[1])
{
dependants[1]
} else {
least_common_ancestor(&builder, &dependants)
};
if dependants.len() == 2 && dependants.contains(&higher_module) {
let mut entry = if is_reexport {
higher_module
if !contains {
if dependants.contains(&module) {
log::trace!("Normal (non-es6): {:?} => {:?}", module, dep);
// `entry` depends on `module` directly
normal_plan.chunks.push(Dependancy {
id: dep,
ty: DepType::Direct,
});
} else {
*dependants.iter().find(|&&v| v != higher_module).unwrap()
};
// We choose higher node if import is circular
if builder.is_circular(entry) {
entry = higher_module;
}
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 {
let t = &mut plans
.normal
.entry(higher_module)
.or_default()
.transitive_chunks;
if !t.contains(&dep) {
log::trace!("Transitive: {:?} => {:?}", entry, dep);
done.insert(dep);
t.push(dep)
log::trace!("Transitive (non-es6): {:?} => {:?}", module, dep);
normal_plan.chunks.push(Dependancy {
id: dep,
ty: DepType::Transitive,
});
}
}
} else {
// Direct dependency.
log::trace!("Normal: {:?} => {:?}", entry, dep);
done.insert(dep);
plans.normal.entry(entry).or_default().chunks.push(dep);
}
continue;
}
if is_reexport {
let normal_plan = plans.normal.entry(entry).or_default();
if normal_plan
.chunks
.iter()
.all(|dependancy| dependancy.id != dep)
{
done.insert(dep);
log::trace!("Normal: {:?} => {:?}", entry, dep);
normal_plan.chunks.push(Dependancy {
id: dep,
ty: DepType::Direct,
});
}
continue;
}
if 2 <= dependants.len() {
// Should be merged as a transitive dependency.
let higher_module = if plans.entries.contains(&dependants[0]) {
dependants[0]
} else if dependants.len() == 2 && plans.entries.contains(&dependants[1]) {
dependants[1]
} else {
least_common_ancestor(&builder, &dependants)
};
if dependants.len() == 2 && dependants.contains(&higher_module) {
let mut entry = if should_be_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) {
entry = higher_module;
}
let normal_plan = plans.normal.entry(entry).or_default();
if normal_plan
.chunks
.iter()
.all(|dependancy| dependancy.id != dep)
{
log::trace!("Normal: {:?} => {:?}", entry, dep);
done.insert(dep);
normal_plan.chunks.push(Dependancy {
id: dep,
ty: DepType::Direct,
});
}
} else {
if self.scope.should_be_wrapped_with_a_fn(dep) {
let normal_entry = &mut plans.normal.entry(entry).or_default();
let t = &mut normal_entry.chunks;
if t.iter().all(|dependancy| dependancy.id != dep) {
log::info!("Normal, esm: {:?} => {:?}", entry, dep);
done.insert(dep);
t.push(Dependancy {
id: dep,
ty: DepType::Direct,
})
}
} else {
let normal_entry =
&mut plans.normal.entry(higher_module).or_default();
let t = &mut normal_entry.chunks;
if t.iter().all(|dependancy| dependancy.id != dep) {
log::trace!("Transitive: {:?} => {:?}", entry, dep);
done.insert(dep);
t.push(Dependancy {
id: dep,
ty: if should_be_reexport {
DepType::Direct
} else {
DepType::Transitive
},
})
}
}
}
} else {
// Direct dependency.
log::trace!("Normal: {:?} => {:?}", entry, dep);
done.insert(dep);
plans
.normal
.entry(entry)
.or_default()
.chunks
.push(Dependancy {
id: dep,
ty: DepType::Direct,
});
}
}
}
}
// Sort transitive chunks topologically.
for (_, normal_plan) in &mut plans.normal {
toposort(&builder, &mut normal_plan.transitive_chunks);
toposort(&builder, &mut normal_plan.chunks);
}
// Handle circular imports
@ -416,7 +475,8 @@ where
{
let c = &mut plans.normal.entry(dep).or_default().chunks;
if let Some(pos) = c.iter().position(|&v| v == circular_member)
if let Some(pos) =
c.iter().position(|&v| v.id == circular_member)
{
c.remove(pos);
}
@ -428,7 +488,7 @@ where
.entry(circular_member)
.or_default()
.chunks;
if let Some(pos) = c.iter().position(|&v| v == dep) {
if let Some(pos) = c.iter().position(|&v| v.id == dep) {
c.remove(pos);
}
}
@ -550,24 +610,51 @@ where
}
}
fn toposort(b: &PlanBuilder, module_ids: &mut Vec<ModuleId>) {
let len = module_ids.len();
fn toposort(b: &PlanBuilder, module_ids: &mut Vec<Dependancy>) {
if module_ids.len() <= 1 {
return;
}
for i in 0..len {
for j in i..len {
let mi = module_ids[i];
let mj = module_ids[j];
if mi == mj {
let mut graph = b.direct_deps.clone();
let len = module_ids.len();
let mut orders: Vec<usize> = vec![];
loop {
if graph.all_edges().count() == 0 {
break;
}
let mut did_work = false;
// Add nodes which does not have any dependencies.
for i in 0..len {
let m = module_ids[i].id;
if graph.neighbors_directed(m, Incoming).count() != 0 {
continue;
}
if b.direct_deps.contains_edge(mj, mi) {
module_ids.swap(i, j);
}
did_work = true;
orders.push(i);
// Remove dependency
graph.remove_node(m);
}
if !did_work {
break;
}
}
for i in 0..len {
if orders.contains(&i) {
continue;
}
orders.push(i);
}
let mut buf = Vec::with_capacity(module_ids.len());
for order in orders {
let item = module_ids[order];
buf.push(item)
}
*module_ids = buf;
}

View File

@ -1,5 +1,8 @@
use super::Plan;
use crate::bundler::tests::{suite, Tester};
use crate::bundler::{
chunk::plan::DepType,
tests::{suite, Tester},
};
use std::collections::{HashMap, HashSet};
use swc_common::FileName;
@ -18,18 +21,33 @@ fn assert_normal_transitive(
) {
if deps.is_empty() {
if let Some(v) = p.normal.get(&t.id(&format!("{}.js", entry))) {
assert_eq!(v.chunks, vec![], "Should be empty");
let actual = v
.chunks
.iter()
.filter(|dep| match dep.ty {
DepType::Direct => true,
_ => false,
})
.copied()
.collect::<Vec<_>>();
assert_eq!(actual, vec![], "Should be empty");
}
return;
}
let actual = p.normal[&t.id(&format!("{}.js", entry))]
.chunks
.iter()
.filter(|dep| match dep.ty {
DepType::Direct => true,
_ => false,
})
.map(|dep| dep.id)
.collect::<HashSet<_>>();
assert_eq!(
p.normal[&t.id(&format!("{}.js", entry))]
.chunks
.iter()
.cloned()
.collect::<HashSet<_>>(),
actual,
deps.into_iter()
.map(|s| format!("{}.js", s))
.map(|s| t.id(&s))
@ -40,9 +58,13 @@ fn assert_normal_transitive(
assert_eq!(
p.normal[&t.id(&format!("{}.js", entry))]
.transitive_chunks
.chunks
.iter()
.cloned()
.filter(|dep| match dep.ty {
DepType::Transitive => true,
_ => false,
})
.map(|dep| dep.id)
.collect::<HashSet<_>>(),
transitive_deps
.into_iter()
@ -958,3 +980,55 @@ fn deno_003() {
Ok(())
});
}
#[test]
fn deno_8302_3() {
suite()
.file(
"main.js",
"
import * as a from './a';
",
)
.file(
"a.js",
"
import * as b from './b';
import * as lib from './lib';
",
)
.file(
"b.js",
"
import * as c from './c';
import * as lib from './lib';
",
)
.file(
"c.js",
"
import * as a from './a';
import * as lib from './lib';
",
)
.file("lib.js", "")
.run(|t| {
let module = t
.bundler
.load_transformed(&FileName::Real("main.js".into()))?
.unwrap();
let mut entries = HashMap::default();
entries.insert("main.js".to_string(), module.clone());
let p = t.bundler.calculate_plan(entries)?;
dbg!(&p);
assert_normal(t, &p, "main", &["a"]);
assert_normal(t, &p, "a", &["lib"]);
assert_circular(t, &p, "a", &["b", "c"]);
Ok(())
});
}

View File

@ -1,571 +0,0 @@
use crate::{bundler::load::Specifier, load::Load, resolve::Resolve, Bundler};
use std::collections::HashMap;
use swc_atoms::{js_word, JsWord};
use swc_common::{Mark, Spanned, SyntaxContext, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_utils::{ident::IdentLike, Id, StmtLike};
use swc_ecma_visit::{noop_fold_type, noop_visit_mut_type, Fold, FoldWith, VisitMut, VisitMutWith};
pub(super) type RemarkMap = HashMap<Id, SyntaxContext>;
impl<L, R> Bundler<'_, L, R>
where
L: Load,
R: Resolve,
{
/// Applied to dependencies
///
///
/// If `imports` is [None], it means all specifier can be used.
pub(super) fn remark_exports(
&self,
mut dep: Module,
dep_ctxt: SyntaxContext,
imports: Option<&[Specifier]>,
unexport: bool,
) -> Module {
let mut v = ExportRenamer {
dep_ctxt,
imports,
extras: vec![],
remark_map: Default::default(),
unexport,
};
dep = dep.fold_with(&mut v);
if !v.remark_map.is_empty() {
log::debug!("Remark map: {:?}", v.remark_map);
// Swap syntax context. Although name is remark, it's actually
// swapping because ExportRenamer inserts two-side conversion
// rule.
self.remark(&mut dep, &v.remark_map);
}
dep
}
pub(super) fn remark(&self, module: &mut Module, remark_map: &RemarkMap) {
module.visit_mut_with(&mut RemarkIdents { map: remark_map });
}
}
/// Applied to dependency modules.
struct ExportRenamer<'a> {
/// The mark applied to identifiers exported to dependant modules.
dep_ctxt: SyntaxContext,
remark_map: RemarkMap,
/// Dependant module's import
imports: Option<&'a [Specifier]>,
extras: Vec<Stmt>,
unexport: bool,
}
impl ExportRenamer<'_> {
/// Returns [SyntaxContext] for the name of variable.
fn mark_as_remarking_required(&mut self, exported: Id, orig: Id) -> SyntaxContext {
log::debug!("Remarking required: {:?} -> {:?}", exported, orig);
let ctxt = SyntaxContext::empty().apply_mark(Mark::fresh(Mark::root()));
self.remark_map
.insert((exported.0.clone(), ctxt), exported.1);
self.remark_map.insert(orig, ctxt);
ctxt
}
fn aliased_import(&self, sym: &JsWord) -> Option<Id> {
if self.imports.is_none() {
return Some((sym.clone(), self.dep_ctxt));
}
self.imports
.as_ref()
.unwrap()
.iter()
.find_map(|s| match s {
Specifier::Specific {
ref local,
alias: Some(ref alias),
..
} if *alias == *sym => Some(local.clone()),
Specifier::Specific {
ref local,
alias: None,
..
} if *local == *sym => Some(local.clone()),
_ => None,
})
.map(|v| v.to_id())
}
}
impl ExportRenamer<'_> {
fn fold_stmt_like<T>(&mut self, items: Vec<T>) -> Vec<T>
where
T: FoldWith<Self> + StmtLike,
{
let mut buf = Vec::with_capacity(items.len() + 4);
for item in items {
let item = item.fold_with(self);
buf.push(item);
buf.extend(self.extras.drain(..).map(|v| T::from_stmt(v)))
}
buf
}
}
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 span = item.span();
let item: ModuleItem = item.fold_children_with(self);
match item {
ModuleItem::Stmt(..) => return item,
ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(export)) => {
let ident = match export.decl {
DefaultDecl::Class(ClassExpr {
ident: Some(ident),
class,
}) => {
return ModuleItem::Stmt(Stmt::Decl(Decl::Class(ClassDecl {
ident,
declare: false,
class,
})))
}
DefaultDecl::Fn(FnExpr {
ident: Some(ident),
function,
}) => {
return ModuleItem::Stmt(Stmt::Decl(Decl::Fn(FnDecl {
ident,
declare: false,
function,
})))
}
_ => self.aliased_import(&js_word!("default")),
};
let ident = if let Some(id) = ident {
id
} else {
log::debug!("Dropping export default declaration because it's not used");
return Stmt::Empty(EmptyStmt { span: DUMMY_SP }).into();
};
match export.decl {
DefaultDecl::Class(c) => {
return ModuleItem::Stmt(Stmt::Decl(Decl::Var(VarDecl {
span: export.span,
kind: VarDeclKind::Const,
declare: false,
decls: vec![VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(Ident::new(
ident.0,
DUMMY_SP.with_ctxt(self.dep_ctxt),
)),
init: Some(Box::new(Expr::Class(c))),
definite: false,
}],
})))
}
DefaultDecl::Fn(f) => {
return ModuleItem::Stmt(Stmt::Decl(Decl::Var(VarDecl {
span: export.span,
kind: VarDeclKind::Const,
declare: false,
decls: vec![VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(Ident::new(
ident.0,
DUMMY_SP.with_ctxt(self.dep_ctxt),
)),
init: Some(Box::new(Expr::Fn(f))),
definite: false,
}],
})))
}
DefaultDecl::TsInterfaceDecl(_) => {
log::debug!(
"Dropping export default declaration because ts interface declaration \
is not supported yet"
);
return Stmt::Empty(EmptyStmt { span: DUMMY_SP }).into();
}
}
}
ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(e)) => {
let ident = self.aliased_import(&js_word!("default"));
// TODO: Optimize if type of expression is identifier.
return if let Some(ident) = ident {
let ctxt = self.mark_as_remarking_required(
(ident.0.clone(), self.dep_ctxt),
ident.clone(),
);
ModuleItem::Stmt(Stmt::Decl(Decl::Var(VarDecl {
span: e.span,
kind: VarDeclKind::Const,
declare: false,
decls: vec![VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(Ident::new(ident.0, DUMMY_SP.with_ctxt(ctxt))),
init: Some(e.expr),
definite: false,
}],
})))
} else {
log::trace!("Removing default export expression as it's not imported");
// Expression statement cannot start with function
ModuleItem::Stmt(Stmt::Expr(ExprStmt {
span: e.span,
expr: Box::new(Expr::Paren(ParenExpr {
span: DUMMY_SP,
expr: e.expr,
})),
}))
};
}
ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(e)) if e.src.is_none() => {
if self.unexport {
let mut var_decls = Vec::with_capacity(e.specifiers.len());
e.specifiers.into_iter().for_each(|specifier| {
let span = specifier.span();
let ident = match &specifier {
// TODO
ExportSpecifier::Namespace(s) => self.aliased_import(&s.name.sym),
ExportSpecifier::Default(..) => {
self.aliased_import(&js_word!("default"))
}
ExportSpecifier::Named(s) => {
if let Some(exported) = &s.exported {
// We need remarking
match self.aliased_import(&exported.sym) {
Some(v) => {
let ctxt = self.mark_as_remarking_required(
v.clone(),
s.orig.to_id(),
);
log::trace!(
"exported = {}{:?}",
exported.sym,
exported.span.ctxt
);
log::trace!("id = {:?}", v);
log::trace!(
"orig = {}{:?}",
s.orig.sym,
s.orig.span.ctxt()
);
Some((v.0, ctxt))
}
None => None,
}
} else {
match self.aliased_import(&s.orig.sym) {
Some(id) => {
let ctxt = self.mark_as_remarking_required(
(s.orig.sym.clone(), self.dep_ctxt),
s.orig.to_id(),
);
self.remark_map
.insert((id.0.clone(), ctxt), self.dep_ctxt);
Some((id.0, ctxt))
}
None => None,
}
}
}
};
if let Some(i) = ident {
let orig = match specifier {
// TODO
ExportSpecifier::Namespace(s) => s.name,
ExportSpecifier::Default(..) => {
Ident::new(js_word!("default"), span)
}
ExportSpecifier::Named(s) => s.orig,
};
var_decls.push(VarDeclarator {
span,
name: Pat::Ident(Ident::new(i.0, DUMMY_SP.with_ctxt(i.1))),
init: Some(Box::new(Expr::Ident(orig))),
definite: false,
})
} else {
log::trace!(
"Removing export specifier {:?} as it's not imported",
specifier
);
}
});
if !var_decls.is_empty() {
self.extras.push(Stmt::Decl(Decl::Var(VarDecl {
span,
kind: VarDeclKind::Const,
declare: false,
decls: var_decls,
})))
}
return Stmt::Empty(EmptyStmt { span }).into();
} else {
let mut export_specifiers = Vec::with_capacity(e.specifiers.len());
e.specifiers.into_iter().for_each(|specifier| {
let span = specifier.span();
let ident = match &specifier {
// TODO
ExportSpecifier::Namespace(s) => self.aliased_import(&s.name.sym),
ExportSpecifier::Default(..) => {
self.aliased_import(&js_word!("default"))
}
ExportSpecifier::Named(s) => {
if let Some(exported) = &s.exported {
// We need remarking
match self.aliased_import(&exported.sym) {
Some(v) => {
let ctxt = self.mark_as_remarking_required(
v.clone(),
s.orig.to_id(),
);
log::trace!(
"exported = {}{:?}",
exported.sym,
exported.span.ctxt
);
log::trace!("id = {:?}", v);
log::trace!(
"orig = {}{:?}",
s.orig.sym,
s.orig.span.ctxt()
);
Some((v.0, ctxt))
}
None => None,
}
} else {
match self.aliased_import(&s.orig.sym) {
Some(id) => {
let ctxt = self.mark_as_remarking_required(
(s.orig.sym.clone(), self.dep_ctxt),
s.orig.to_id(),
);
Some((id.0, ctxt))
}
None => None,
}
}
}
};
if let Some(i) = ident {
let orig = match specifier {
// TODO
ExportSpecifier::Namespace(s) => s.name,
ExportSpecifier::Default(..) => {
Ident::new(js_word!("default"), span)
}
ExportSpecifier::Named(s) => s.orig,
};
export_specifiers.push(ExportSpecifier::Named(ExportNamedSpecifier {
span,
orig,
exported: Some(Ident::new(i.0, DUMMY_SP.with_ctxt(i.1))),
}));
// export_specifiers.push(VarDeclarator {
// span,
// name: Pat::Ident(Ident::new(i.0,
// DUMMY_SP.with_ctxt(i.1))),
// init: Some(Box::new(Expr::Ident(orig))),
// definite: false,
// })
} else {
log::trace!(
"Removing export specifier {:?} as it's not imported (`unexport` \
is false, but it's not used)",
specifier
);
}
});
if !export_specifiers.is_empty() {
return ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(NamedExport {
type_only: false,
span: e.span,
specifiers: export_specifiers,
src: None,
}));
}
return Stmt::Empty(EmptyStmt { span }).into();
}
}
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(mut decl)) => {
//
return match decl.decl {
Decl::TsInterface(_)
| Decl::TsTypeAlias(_)
| Decl::TsEnum(_)
| Decl::TsModule(_) => ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(decl)),
Decl::Class(mut c) => {
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()
} else {
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
decl: Decl::Class(c),
..decl
}))
}
}
Decl::Fn(mut f) => {
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 {
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
decl: Decl::Fn(f),
..decl
}))
}
}
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))
}
};
}
_ => {}
}
item
}
fn fold_module_items(&mut self, items: Vec<ModuleItem>) -> Vec<ModuleItem> {
self.fold_stmt_like(items)
}
fn fold_stmts(&mut self, items: Vec<Stmt>) -> Vec<Stmt> {
self.fold_stmt_like(items)
}
}
struct ActualMarker<'a, 'b> {
dep_ctxt: SyntaxContext,
/// Dependant module's import
imports: Option<&'a [Specifier]>,
remarks: &'b mut RemarkMap,
}
impl ActualMarker<'_, '_> {
fn rename(&mut self, ident: &mut Ident) {
if self.imports.is_none() {
return;
}
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((local.sym().clone(), ident.span.ctxt)),
Specifier::Specific { alias: None, local } if *local == ident.sym => {
Some(local.to_id())
}
_ => None,
}) {
ident.sym = id.0.clone();
self.remarks.insert(id, self.dep_ctxt);
}
}
}
impl VisitMut for ActualMarker<'_, '_> {
noop_visit_mut_type!();
fn visit_mut_expr(&mut self, _: &mut Expr) {}
fn visit_mut_ident(&mut self, ident: &mut Ident) {
self.rename(ident)
}
fn visit_mut_private_name(&mut self, _: &mut PrivateName) {}
fn visit_mut_export_named_specifier(&mut self, s: &mut ExportNamedSpecifier) {
s.orig.visit_mut_with(self);
}
}
struct RemarkIdents<'a> {
map: &'a RemarkMap,
}
impl VisitMut for RemarkIdents<'_> {
noop_visit_mut_type!();
fn visit_mut_ident(&mut self, n: &mut Ident) {
let id = (*n).to_id();
if let Some(&ctxt) = self.map.get(&id) {
n.span = n.span.with_ctxt(ctxt);
log::debug!("Remark: {:?} -> {:?}", id, ctxt)
}
}
fn visit_mut_private_name(&mut self, _: &mut PrivateName) {}
}

View File

@ -0,0 +1,369 @@
use crate::{id::Id, util::MapWithMut};
use indexmap::IndexSet;
use petgraph::{
graphmap::DiGraphMap,
EdgeDirection::{Incoming, Outgoing},
};
use std::collections::HashMap;
use swc_common::DUMMY_SP;
use swc_ecma_ast::*;
use swc_ecma_utils::find_ids;
use swc_ecma_visit::{noop_visit_type, Node, Visit, VisitWith};
type StmtDepGraph = DiGraphMap<usize, Required>;
/// Is dependancy between nodes hard?
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum Required {
/// Required to evaluate
Always,
/// Maybe required to evaluate
Maybe,
}
pub(super) fn sort(new: &mut Vec<ModuleItem>) {
new.retain(|item| match item {
ModuleItem::Stmt(Stmt::Empty(..)) => false,
_ => true,
});
let mut graph = StmtDepGraph::default();
let mut declared_by = HashMap::<Id, Vec<usize>>::default();
let mut uninitialized_ids = HashMap::<Id, usize>::new();
for (idx, item) in new.iter().enumerate() {
graph.add_node(idx);
// We start by calculating ids created by statements. Note that we don't need to
// analyze bodies of functions nor members of classes, because it's not
// evaludated until they are called.
match item {
// We only check declarations because ids are created by declarations.
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { decl, .. }))
| ModuleItem::Stmt(Stmt::Decl(decl)) => {
//
match decl {
Decl::Class(ClassDecl { ident, .. }) | Decl::Fn(FnDecl { ident, .. }) => {
declared_by.entry(Id::from(ident)).or_default().push(idx);
}
Decl::Var(vars) => {
for var in &vars.decls {
//
let ids: Vec<Id> = find_ids(&var.name);
for id in ids {
if var.init.is_none() {
uninitialized_ids.insert(id.clone(), idx);
}
declared_by.entry(id).or_default().push(idx);
}
}
}
_ => {}
}
}
_ => {}
}
}
// Handle uninitialized variables
//
// Compiled typescript enum is not initialized by declaration
//
// var Status;
// (function(Status){})(Status)
for (uninit_id, start_idx) in uninitialized_ids {
for (idx, item) in new.iter().enumerate().filter(|(idx, _)| *idx > start_idx) {
let mut finder = InitializerFinder {
ident: uninit_id.clone(),
found: false,
in_complex: false,
};
item.visit_with(&Invalid { span: DUMMY_SP }, &mut finder);
if finder.found {
declared_by.entry(uninit_id).or_default().push(idx);
break;
}
}
}
for (idx, item) in new.iter().enumerate() {
// We then calculate which ids a statement require to be executed.
// Again, we don't need to analyze non-top-level idents because they
// are not evaluated while lpoading module.
let mut visitor = RequirementCalculartor::default();
item.visit_with(&Invalid { span: DUMMY_SP }, &mut visitor);
for (id, kind) in visitor.required_ids {
if let Some(declarator_indexes) = declared_by.get(&id) {
for &declarator_index in declarator_indexes {
if declarator_index != idx {
graph.add_edge(declarator_index, idx, kind);
}
}
}
}
}
// Now graph contains enough information to sort statements.
let len = new.len();
let mut orders: Vec<usize> = vec![];
// No dependencies
loop {
if graph.all_edges().count() == 0 {
break;
}
let mut did_work = false;
// Add nodes which does not have any dependencies.
for i in 0..len {
if orders.contains(&i) {
continue;
}
let dependants = graph.neighbors_directed(i, Incoming);
if dependants.count() != 0 {
continue;
}
did_work = true;
orders.push(i);
// Remove dependencies to other node.
graph.remove_node(i);
}
if !did_work {
break;
}
}
// Strong dependencies
loop {
if graph.all_edges().count() == 0 {
break;
}
let mut did_work = false;
// Add nodes which does not have any dependencies.
for i in 0..len {
if orders.contains(&i) {
continue;
}
let dependants = graph
.neighbors_directed(i, Incoming)
.filter(|&entry| graph.edge_weight(entry, i) == Some(&Required::Always));
if dependants.count() != 0 {
continue;
}
did_work = true;
orders.push(i);
// Remove strong dependency
for dependancy in graph
.neighbors_directed(i, Outgoing)
.filter(|&dep| graph.edge_weight(i, dep) == Some(&Required::Always))
.collect::<Vec<_>>()
{
graph.remove_edge(i, dependancy).unwrap();
}
}
if !did_work {
break;
}
}
// Weak dependencies
loop {
if graph.all_edges().count() == 0 {
break;
}
let mut did_work = false;
// Add nodes which does not have any dependencies.
for i in 0..len {
let dependants = graph.neighbors_directed(i, Incoming);
if orders.contains(&i) || dependants.count() != 0 {
continue;
}
did_work = true;
orders.push(i);
// Remove dependency
graph.remove_node(i);
}
if !did_work {
break;
}
}
// Now all dependencies are merged.
for i in 0..len {
if orders.contains(&i) {
continue;
}
orders.push(i);
}
assert_eq!(orders.len(), new.len());
let mut buf = Vec::with_capacity(new.len());
for order in orders {
let stmt = new[order].take();
buf.push(stmt)
}
*new = buf;
}
/// Finds usage of `ident`
struct InitializerFinder {
ident: Id,
found: bool,
in_complex: bool,
}
impl Visit for InitializerFinder {
noop_visit_type!();
fn visit_ident(&mut self, i: &Ident, _: &dyn Node) {
if self.in_complex && self.ident == *i {
self.found = true;
}
}
fn visit_expr_or_spread(&mut self, node: &ExprOrSpread, _: &dyn Node) {
let in_complex = self.in_complex;
self.in_complex = true;
node.visit_children_with(self);
self.in_complex = in_complex;
}
fn visit_member_expr(&mut self, e: &MemberExpr, _: &dyn Node) {
{
let in_complex = self.in_complex;
self.in_complex = true;
e.obj.visit_children_with(self);
self.in_complex = in_complex;
}
if e.computed {
e.prop.visit_with(e as _, self);
}
}
}
/// We do not care about variables created by current statement.
/// But we care about modifications.
#[derive(Default)]
struct RequirementCalculartor {
required_ids: IndexSet<(Id, Required)>,
in_weak: bool,
in_var_decl: bool,
}
macro_rules! weak {
($name:ident, $T:ty) => {
fn $name(&mut self, f: &$T, _: &dyn Node) {
let in_weak = self.in_weak;
self.in_weak = true;
f.visit_children_with(self);
self.in_weak = in_weak;
}
};
}
impl RequirementCalculartor {
fn insert(&mut self, i: Id) {
self.required_ids.insert((
i,
if self.in_weak {
Required::Maybe
} else {
Required::Always
},
));
}
}
impl Visit for RequirementCalculartor {
noop_visit_type!();
weak!(visit_arrow_expr, ArrowExpr);
weak!(visit_function, Function);
weak!(visit_class_method, ClassMethod);
weak!(visit_private_method, PrivateMethod);
weak!(visit_method_prop, MethodProp);
fn visit_export_named_specifier(&mut self, n: &ExportNamedSpecifier, _: &dyn Node) {
self.insert(n.orig.clone().into());
}
fn visit_pat(&mut self, pat: &Pat, _: &dyn Node) {
match pat {
Pat::Ident(i) => {
// We do not care about variables created by current statement.
if self.in_var_decl {
return;
}
self.insert(i.into());
}
_ => {}
}
}
fn visit_var_declarator(&mut self, var: &VarDeclarator, _: &dyn Node) {
let in_var_decl = self.in_var_decl;
self.in_var_decl = true;
var.visit_children_with(self);
self.in_var_decl = in_var_decl;
}
fn visit_expr(&mut self, expr: &Expr, _: &dyn Node) {
let in_var_decl = self.in_var_decl;
self.in_var_decl = false;
match expr {
Expr::Ident(i) => {
self.insert(i.into());
}
_ => {
expr.visit_children_with(self);
}
}
self.in_var_decl = in_var_decl;
}
fn visit_prop(&mut self, prop: &Prop, _: &dyn Node) {
match prop {
Prop::Shorthand(i) => {
self.insert(i.into());
}
_ => prop.visit_children_with(self),
}
}
fn visit_member_expr(&mut self, e: &MemberExpr, _: &dyn Node) {
e.obj.visit_with(e as _, self);
if e.computed {
e.prop.visit_with(e as _, self);
}
}
}

View File

@ -21,12 +21,14 @@ where
&self,
file_name: &FileName,
module: &mut Module,
export_ctxt: SyntaxContext,
) -> RawExports {
self.run(|| {
let mut v = ExportFinder {
info: Default::default(),
file_name,
bundler: self,
export_ctxt,
};
module.visit_mut_with(&mut v);
@ -56,6 +58,7 @@ where
info: RawExports,
file_name: &'a FileName,
bundler: &'a Bundler<'b, L, R>,
export_ctxt: SyntaxContext,
}
impl<L, R> ExportFinder<'_, '_, L, R>
@ -63,7 +66,8 @@ where
L: Load,
R: Resolve,
{
fn ctxt_for(&self, src: &JsWord) -> Option<SyntaxContext> {
/// Returns `(local, export)`.
fn ctxt_for(&self, src: &JsWord) -> Option<(SyntaxContext, SyntaxContext)> {
// Don't apply mark if it's a core module.
if self
.bundler
@ -75,10 +79,12 @@ where
return None;
}
let path = self.bundler.resolve(self.file_name, src).ok()?;
let (_, mark) = self.bundler.scope.module_id_gen.gen(&path);
let ctxt = SyntaxContext::empty();
let (_, local_mark, export_mark) = self.bundler.scope.module_id_gen.gen(&path);
Some(ctxt.apply_mark(mark))
Some((
SyntaxContext::empty().apply_mark(local_mark),
SyntaxContext::empty().apply_mark(export_mark),
))
}
fn mark_as_wrapping_required(&self, src: &JsWord) {
@ -97,7 +103,7 @@ where
Ok(v) => v,
_ => return,
};
let (id, _) = self.bundler.scope.module_id_gen.gen(&path);
let (id, _, _) = self.bundler.scope.module_id_gen.gen(&path);
self.bundler.scope.mark_as_wrapping_required(id);
}
@ -207,6 +213,10 @@ where
for s in &mut named.specifiers {
match s {
ExportSpecifier::Namespace(n) => {
if let Some((_, export_ctxt)) = ctxt {
n.name.span.ctxt = export_ctxt;
}
need_wrapping = true;
v.push(Specifier::Namespace {
local: n.name.clone().into(),
@ -220,8 +230,19 @@ where
});
}
ExportSpecifier::Named(n) => {
if let Some(ctxt) = ctxt {
n.orig.span = n.orig.span.with_ctxt(ctxt);
if let Some((_, export_ctxt)) = ctxt {
n.orig.span.ctxt = export_ctxt;
}
match &mut n.exported {
Some(exported) => {
exported.span.ctxt = self.export_ctxt;
}
None => {
let mut exported: Ident = n.orig.clone();
exported.span.ctxt = self.export_ctxt;
n.exported = Some(exported);
}
}
if let Some(exported) = &n.exported {
@ -247,6 +268,11 @@ where
}
ModuleItem::ModuleDecl(ModuleDecl::ExportAll(all)) => {
let ctxt = self.ctxt_for(&all.src.value);
if let Some((_, export_ctxt)) = ctxt {
all.span.ctxt = export_ctxt;
}
self.info.items.entry(Some(all.src.clone())).or_default();
}
_ => {}

View File

@ -26,13 +26,13 @@ where
&self,
path: &FileName,
module: &mut Module,
module_mark: Mark,
module_local_mark: Mark,
) -> RawImports {
self.run(|| {
let body = replace(&mut module.body, vec![]);
let mut v = ImportHandler {
module_ctxt: SyntaxContext::empty().apply_mark(module_mark),
module_ctxt: SyntaxContext::empty().apply_mark(module_local_mark),
path,
bundler: self,
top_level: false,
@ -127,7 +127,8 @@ where
L: Load,
R: Resolve,
{
fn ctxt_for(&self, src: &JsWord) -> Option<SyntaxContext> {
/// Retursn (local, export)
fn ctxt_for(&self, src: &JsWord) -> Option<(SyntaxContext, SyntaxContext)> {
// Don't apply mark if it's a core module.
if self
.bundler
@ -139,10 +140,12 @@ where
return None;
}
let path = self.bundler.resolve(self.path, src).ok()?;
let (_, mark) = self.bundler.scope.module_id_gen.gen(&path);
let ctxt = SyntaxContext::empty();
let (_, local_mark, export_mark) = self.bundler.scope.module_id_gen.gen(&path);
Some(ctxt.apply_mark(mark))
Some((
SyntaxContext::empty().apply_mark(local_mark),
SyntaxContext::empty().apply_mark(export_mark),
))
}
fn mark_as_wrapping_required(&self, src: &JsWord) {
@ -161,7 +164,7 @@ where
Ok(v) => v,
Err(_) => return,
};
let (id, _) = self.bundler.scope.module_id_gen.gen(&path);
let (id, _, _) = self.bundler.scope.module_id_gen.gen(&path);
self.bundler.scope.mark_as_wrapping_required(id);
}
@ -185,22 +188,30 @@ where
{
return import;
}
if let Some(ctxt) = self.ctxt_for(&import.src.value) {
import.span = import.span.with_ctxt(ctxt);
if let Some((_, export_ctxt)) = self.ctxt_for(&import.src.value) {
import.span = import.span.with_ctxt(export_ctxt);
for specifier in &mut import.specifiers {
match specifier {
ImportSpecifier::Named(n) => {
self.imported_idents.insert(n.local.to_id(), ctxt);
n.local.span = n.local.span.with_ctxt(ctxt);
self.imported_idents.insert(n.local.to_id(), export_ctxt);
match &mut n.imported {
Some(imported) => {
imported.span.ctxt = export_ctxt;
}
None => {
let mut imported: Ident = n.local.clone();
imported.span.ctxt = export_ctxt;
n.imported = Some(imported);
}
}
}
ImportSpecifier::Default(n) => {
self.imported_idents.insert(n.local.to_id(), ctxt);
n.local.span = n.local.span.with_ctxt(ctxt);
self.imported_idents
.insert(n.local.to_id(), n.local.span.ctxt);
}
ImportSpecifier::Namespace(n) => {
self.imported_idents.insert(n.local.to_id(), ctxt);
n.local.span = n.local.span.with_ctxt(ctxt);
self.imported_idents.insert(n.local.to_id(), export_ctxt);
}
}
}
@ -329,41 +340,26 @@ where
}
fn fold_export_named_specifier(&mut self, mut s: ExportNamedSpecifier) -> ExportNamedSpecifier {
if let Some(&ctxt) = self.imported_idents.get(&s.orig.to_id()) {
s.orig.span = s.orig.span.with_ctxt(ctxt);
match &s.exported {
Some(exported) => {
debug_assert_eq!(
exported.span.ctxt, self.module_ctxt,
"Exported names should have same (local) context as top-level module items"
);
}
None => {
let exported =
Ident::new(s.orig.sym.clone(), s.orig.span.with_ctxt(self.module_ctxt));
s.exported = Some(exported);
}
}
s
}
fn fold_prop(&mut self, mut prop: Prop) -> Prop {
prop = prop.fold_children_with(self);
match prop {
Prop::Shorthand(mut i) => {
if let Some(&ctxt) = self.imported_idents.get(&i.to_id()) {
let local = i.clone();
i.span = i.span.with_ctxt(ctxt);
return Prop::KeyValue(KeyValueProp {
key: PropName::Ident(local),
value: Box::new(Expr::Ident(i)),
});
}
Prop::Shorthand(i)
}
_ => prop,
}
}
fn fold_expr(&mut self, e: Expr) -> Expr {
match e {
Expr::Ident(mut i) if self.deglob_phase => {
if let Some(&ctxt) = self.imported_idents.get(&i.to_id()) {
i.span = i.span.with_ctxt(ctxt);
}
Expr::Ident(i) if self.deglob_phase => {
return Expr::Ident(i);
}
@ -408,9 +404,9 @@ where
false
}) {
let mark = self.ctxt_for(&import.src.value);
let ctxt = match mark {
let exported_ctxt = match mark {
None => return e.into(),
Some(mark) => mark,
Some(ctxts) => ctxts.1,
};
if self.deglob_phase {
if self.info.forced_ns.contains(&import.src.value) {
@ -421,7 +417,7 @@ where
let i = match &*e.prop {
Expr::Ident(i) => {
let mut i = i.clone();
i.span = i.span.with_ctxt(ctxt);
i.span = i.span.with_ctxt(exported_ctxt);
i
}
_ => unreachable!(
@ -438,7 +434,7 @@ where
let i = match &*e.prop {
Expr::Ident(i) => {
let mut i = i.clone();
i.span = i.span.with_ctxt(ctxt);
i.span = i.span.with_ctxt(exported_ctxt);
i
}
_ => unreachable!(
@ -496,8 +492,8 @@ where
{
match &mut **callee {
Expr::Ident(i) => {
if let Some(ctxt) = self.ctxt_for(&src.value) {
i.span = i.span.with_ctxt(ctxt);
if let Some((_, export_ctxt)) = self.ctxt_for(&src.value) {
i.span = i.span.with_ctxt(export_ctxt);
}
}
_ => {}
@ -586,8 +582,8 @@ where
match &mut **callee {
Expr::Ident(i) => {
if let Some(mark) = self.ctxt_for(&src.value) {
i.span = i.span.with_ctxt(mark);
if let Some((_, export_ctxt)) = self.ctxt_for(&src.value) {
i.span = i.span.with_ctxt(export_ctxt);
}
}
_ => {}

View File

@ -12,7 +12,7 @@ use is_macro::Is;
#[cfg(feature = "rayon")]
use rayon::iter::ParallelIterator;
use swc_atoms::js_word;
use swc_common::{sync::Lrc, FileName, Mark, SourceFile, SyntaxContext, DUMMY_SP};
use swc_common::{sync::Lrc, FileName, SourceFile, SyntaxContext, DUMMY_SP};
use swc_ecma_ast::{
CallExpr, Expr, ExprOrSuper, Ident, ImportDecl, ImportSpecifier, Invalid, MemberExpr, Module,
ModuleDecl, Str,
@ -36,17 +36,19 @@ pub(super) struct TransformedModule {
pub swc_helpers: Lrc<swc_ecma_transforms::helpers::Helpers>,
mark: Mark,
local_ctxt: SyntaxContext,
export_ctxt: SyntaxContext,
}
impl TransformedModule {
/// THe marker for the module's top-level identifiers.
pub fn mark(&self) -> Mark {
self.mark
/// [SyntaxContext] for exported items.
pub fn export_ctxt(&self) -> SyntaxContext {
self.export_ctxt
}
pub fn ctxt(&self) -> SyntaxContext {
SyntaxContext::empty().apply_mark(self.mark)
/// Top level contexts.
pub fn local_ctxt(&self) -> SyntaxContext {
self.local_ctxt
}
}
@ -78,7 +80,13 @@ where
.context("failed to analyze module")?;
files.dedup_by_key(|v| v.1.clone());
log::debug!("({}, {:?}) Storing module: {}", v.id, v.ctxt(), file_name);
log::debug!(
"({:?}, {:?}, {:?}) Storing module: {}",
v.id,
v.local_ctxt(),
v.export_ctxt(),
file_name
);
self.scope.store_module(v.clone());
// Load dependencies and store them in the `Scope`
@ -101,7 +109,7 @@ where
fn load(&self, file_name: &FileName) -> Result<(ModuleId, ModuleData), Error> {
self.run(|| {
let (module_id, _) = self.scope.module_id_gen.gen(file_name);
let (module_id, _, _) = self.scope.module_id_gen.gen(file_name);
let data = self
.loader
@ -120,9 +128,9 @@ where
) -> Result<(TransformedModule, Vec<(Source, Lrc<FileName>)>), Error> {
self.run(|| {
log::trace!("transform_module({})", data.fm.name);
let (id, mark) = self.scope.module_id_gen.gen(file_name);
let (id, local_mark, export_mark) = self.scope.module_id_gen.gen(file_name);
let mut module = data.module.fold_with(&mut resolver_with_mark(mark));
let mut module = data.module.fold_with(&mut resolver_with_mark(local_mark));
// {
// let code = self
@ -139,7 +147,7 @@ where
// println!("Resolved:\n{}\n\n", code);
// }
let imports = self.extract_import_info(file_name, &mut module, mark);
let imports = self.extract_import_info(file_name, &mut module, local_mark);
// {
// let code = self
@ -156,7 +164,11 @@ where
// println!("After imports:\n{}\n", code,);
// }
let exports = self.extract_export_info(file_name, &mut module);
let exports = self.extract_export_info(
file_name,
&mut module,
SyntaxContext::empty().apply_mark(export_mark),
);
let is_es6 = if !self.config.require {
true
@ -188,8 +200,9 @@ where
exports: Lrc::new(exports),
is_es6,
helpers: Default::default(),
mark,
swc_helpers: Lrc::new(data.helpers),
local_ctxt: SyntaxContext::empty().apply_mark(local_mark),
export_ctxt: SyntaxContext::empty().apply_mark(export_mark),
},
import_files,
))
@ -216,8 +229,9 @@ where
let info = match src {
Some(src) => {
let name = self.resolve(base, &src.value)?;
let (id, mark) = self.scope.module_id_gen.gen(&name);
Some((id, mark, name, src))
let (id, local_mark, export_mark) =
self.scope.module_id_gen.gen(&name);
Some((id, local_mark, export_mark, name, src))
}
None => None,
};
@ -232,13 +246,14 @@ where
match info {
None => exports.items.extend(specifiers),
Some((id, mark, name, src)) => {
Some((id, local_mark, export_mark, name, src)) => {
//
let src = Source {
is_loaded_synchronously: true,
is_unconditional: false,
module_id: id,
ctxt: SyntaxContext::empty().apply_mark(mark),
local_ctxt: SyntaxContext::empty().apply_mark(local_mark),
export_ctxt: SyntaxContext::empty().apply_mark(export_mark),
src,
};
exports.reexports.push((src.clone(), specifiers));
@ -290,22 +305,33 @@ where
self.run(|| {
//
let file_name = self.resolve(base, &decl.src.value)?;
let (id, mark) = self.scope.module_id_gen.gen(&file_name);
let (id, local_mark, export_mark) =
self.scope.module_id_gen.gen(&file_name);
Ok((id, mark, file_name, decl, dynamic, unconditional))
Ok((
id,
local_mark,
export_mark,
file_name,
decl,
dynamic,
unconditional,
))
})
})
.collect::<Vec<_>>();
for res in loaded {
// TODO: Report error and proceed instead of returning an error
let (id, mark, file_name, decl, is_dynamic, is_unconditional) = res?;
let (id, local_mark, export_mark, file_name, decl, is_dynamic, is_unconditional) =
res?;
let src = Source {
is_loaded_synchronously: !is_dynamic,
is_unconditional,
module_id: id,
ctxt: SyntaxContext::empty().apply_mark(mark),
local_ctxt: SyntaxContext::empty().apply_mark(local_mark),
export_ctxt: SyntaxContext::empty().apply_mark(export_mark),
src: decl.src,
};
files.push((src.clone(), file_name));
@ -365,7 +391,8 @@ pub(super) struct Source {
pub is_unconditional: bool,
pub module_id: ModuleId,
pub ctxt: SyntaxContext,
pub local_ctxt: SyntaxContext,
pub export_ctxt: SyntaxContext,
// Clone is relatively cheap, thanks to string_cache.
pub src: Str,

View File

@ -1,6 +1,6 @@
use crate::{Bundler, Load, Resolve};
use swc_ecma_ast::*;
use swc_ecma_transforms::optimization::simplify::{dce, inlining};
use swc_ecma_transforms::optimization::simplify::{const_propgation::constant_propagation, dce};
use swc_ecma_visit::FoldWith;
impl<L, R> Bundler<'_, L, R>
@ -15,7 +15,7 @@ where
pub(super) fn optimize(&self, mut node: Module) -> Module {
self.run(|| {
if !self.config.disable_inliner {
node = node.fold_with(&mut inlining::inlining(inlining::Config {}))
node = node.fold_with(&mut constant_propagation())
}
node = node.fold_with(&mut dce::dce(dce::Config {

View File

@ -1,6 +1,6 @@
use super::load::TransformedModule;
use crate::{
id::{ModuleId, ModuleIdGenerator},
id::{Id, ModuleId, ModuleIdGenerator},
util::CloneMap,
};
use std::sync::atomic::{AtomicBool, Ordering};
@ -36,7 +36,7 @@ impl Scope {
}
pub fn get_module_by_path(&self, file_name: &FileName) -> Option<TransformedModule> {
let (id, _) = self.module_id_gen.gen(file_name);
let (id, _, _) = self.module_id_gen.gen(file_name);
self.get_module(id)
}
@ -62,4 +62,15 @@ impl Scope {
false
}
}
/// Returns `Some(module_ident)` if the module should be wrapped
/// with a function.
pub fn wrapped_esm_id(&self, id: ModuleId) -> Option<Id> {
if !self.should_be_wrapped_with_a_fn(id) {
return None;
}
let info = self.get_module(id)?;
Some(Id::new("mod".into(), info.export_ctxt()))
}
}

View File

@ -5,7 +5,7 @@ use std::{
};
use swc_atoms::JsWord;
use swc_common::{sync::Lock, FileName, Mark, SyntaxContext, DUMMY_SP};
use swc_ecma_ast::Ident;
use swc_ecma_ast::{Expr, Ident};
use swc_ecma_utils::ident::IdentLike;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
@ -27,20 +27,23 @@ impl fmt::Debug for ModuleId {
#[derive(Debug, Default)]
pub(crate) struct ModuleIdGenerator {
v: AtomicU32,
cache: Lock<HashMap<FileName, (ModuleId, Mark)>>,
/// `(module_id, local_mark, export_mark)`
cache: Lock<HashMap<FileName, (ModuleId, Mark, Mark)>>,
}
impl ModuleIdGenerator {
pub fn gen(&self, file_name: &FileName) -> (ModuleId, Mark) {
pub fn gen(&self, file_name: &FileName) -> (ModuleId, Mark, Mark) {
let mut w = self.cache.lock();
if let Some(v) = w.get(file_name) {
return v.clone();
}
let id = ModuleId(self.v.fetch_add(1, SeqCst));
let mark = Mark::fresh(Mark::root());
w.insert(file_name.clone(), (id, mark));
(id, mark)
let local_mark = Mark::fresh(Mark::root());
let export_mark = Mark::fresh(Mark::root());
let v = (id, local_mark, export_mark);
w.insert(file_name.clone(), v);
(id, local_mark, export_mark)
}
}
@ -62,12 +65,16 @@ impl Id {
&self.0
}
pub fn ctxt(&self) -> SyntaxContext {
self.1
}
pub fn into_ident(self) -> Ident {
Ident::new(self.0, DUMMY_SP.with_ctxt(self.1))
}
pub fn replace_mark(mut self, mark: Mark) -> Self {
self.1 = SyntaxContext::empty().apply_mark(mark);
pub fn with_ctxt(mut self, ctxt: SyntaxContext) -> Self {
self.1 = ctxt;
self
}
}
@ -109,3 +116,17 @@ impl PartialEq<JsWord> for Id {
self.0 == *other
}
}
impl From<Id> for Ident {
#[inline]
fn from(id: Id) -> Self {
id.into_ident()
}
}
impl From<Id> for Expr {
#[inline]
fn from(id: Id) -> Self {
Expr::Ident(id.into_ident())
}
}

View File

@ -1,9 +1,56 @@
use std::{hash::Hash, mem::replace};
use std::{clone::Clone, cmp::Eq, hash::Hash, mem::replace};
use swc_atoms::js_word;
use swc_common::{Span, SyntaxContext, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_utils::ident::IdentLike;
use swc_ecma_visit::{noop_visit_mut_type, VisitMut};
pub(crate) trait VarDeclaratorExt: Into<VarDeclarator> {
fn into_module_item(self, _name: &'static str) -> ModuleItem {
ModuleItem::Stmt(Stmt::Decl(Decl::Var(VarDecl {
span: DUMMY_SP,
kind: VarDeclKind::Const,
declare: false,
decls: vec![
self.into(),
/* Ident::new(name.into(), DUMMY_SP)
* .assign_to(Ident::new("INJECTED_FROM".into(), DUMMY_SP)), */
],
})))
}
}
impl<T> VarDeclaratorExt for T where T: Into<VarDeclarator> {}
pub(crate) trait ExprExt: Into<Expr> {
#[track_caller]
fn assign_to<T>(self, lhs: T) -> VarDeclarator
where
T: IdentLike,
{
let init = self.into();
let lhs = lhs.into_id();
if cfg!(debug_assertions) {
match &init {
Expr::Ident(rhs) => {
debug_assert_ne!(lhs, rhs.to_id());
}
_ => {}
}
}
VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(Ident::new(lhs.0, DUMMY_SP.with_ctxt(lhs.1))),
init: Some(Box::new(init)),
definite: false,
}
}
}
impl<T> ExprExt for T where T: Into<Expr> {}
/// Helper for migration from [Fold] to [VisitMut]
pub(crate) trait MapWithMut: Sized {
fn dummy() -> Self;
@ -98,6 +145,54 @@ impl MapWithMut for PatOrExpr {
}
}
impl MapWithMut for ClassExpr {
fn dummy() -> Self {
ClassExpr {
ident: None,
class: MapWithMut::dummy(),
}
}
}
impl MapWithMut for FnExpr {
fn dummy() -> Self {
FnExpr {
ident: None,
function: MapWithMut::dummy(),
}
}
}
impl MapWithMut for Class {
fn dummy() -> Self {
Class {
span: Default::default(),
decorators: Default::default(),
body: Default::default(),
super_class: Default::default(),
is_abstract: Default::default(),
type_params: Default::default(),
super_type_params: Default::default(),
implements: Default::default(),
}
}
}
impl MapWithMut for Function {
fn dummy() -> Self {
Function {
params: Default::default(),
decorators: Default::default(),
span: Default::default(),
body: Default::default(),
is_generator: Default::default(),
is_async: Default::default(),
type_params: Default::default(),
return_type: Default::default(),
}
}
}
#[derive(Debug)]
pub(crate) struct CHashSet<V>
where

View File

@ -5,7 +5,7 @@
use anyhow::{Context, Error};
use sha1::{Digest, Sha1};
use std::{
collections::HashMap,
collections::{HashMap, HashSet},
env,
fs::{create_dir_all, read_to_string, write},
path::PathBuf,
@ -13,81 +13,379 @@ use std::{
};
use swc_atoms::js_word;
use swc_bundler::{Bundler, Load, ModuleData, ModuleRecord, Resolve};
use swc_common::{sync::Lrc, FileName, SourceMap, Span, GLOBALS};
use swc_ecma_ast::{
Bool, Expr, ExprOrSuper, Ident, KeyValueProp, Lit, MemberExpr, MetaPropExpr, PropName, Str,
};
use swc_common::{comments::SingleThreadedComments, sync::Lrc, FileName, SourceMap, Span, GLOBALS};
use swc_ecma_ast::*;
use swc_ecma_codegen::{text_writer::JsWriter, Emitter};
use swc_ecma_parser::{lexer::Lexer, JscTarget, Parser, StringInput, Syntax, TsConfig};
use swc_ecma_transforms::{proposals::decorators, typescript::strip};
use swc_ecma_visit::FoldWith;
use swc_ecma_transforms::{proposals::decorators, react, typescript::strip};
use swc_ecma_utils::{find_ids, Id};
use swc_ecma_visit::{FoldWith, Node, Visit, VisitWith};
use testing::assert_eq;
use url::Url;
#[test]
fn oak_6_3_1_application() {
run("https://deno.land/x/oak@v6.3.1/application.ts", None);
run(
"https://deno.land/x/oak@v6.3.1/application.ts",
&[
"ApplicationErrorEvent",
"ApplicationListenEvent",
"Application",
],
);
}
#[test]
fn oak_6_3_1_mod() {
run("https://deno.land/x/oak@v6.3.1/mod.ts", None);
run(
"https://deno.land/x/oak@v6.3.1/mod.ts",
&[
"Application",
"Context",
"helpers",
"Cookies",
"HttpError",
"httpErrors",
"isHttpError",
"composeMiddleware",
"FormDataReader",
"Request",
"REDIRECT_BACK",
"Response",
"Router",
"send",
"ServerSentEvent",
"ServerSentEventTarget",
"isErrorStatus",
"isRedirectStatus",
"Status",
"STATUS_TEXT",
],
);
}
#[test]
fn std_0_74_9_http_server() {
run("https://deno.land/std@0.74.0/http/server.ts", None);
fn std_0_74_0_http_server() {
run(
"https://deno.land/std@0.74.0/http/server.ts",
&[
"ServerRequest",
"Server",
"_parseAddrFromStr",
"serve",
"listenAndServe",
"serveTLS",
"listenAndServeTLS",
],
);
}
#[test]
#[ignore = "Does not finish by default"]
fn oak_6_3_1_example_server() {
run("https://deno.land/x/oak@v6.3.1/examples/server.ts", None);
run("https://deno.land/x/oak@v6.3.1/examples/server.ts", &[]);
}
#[test]
#[ignore = "Does not finish by default"]
fn oak_6_3_1_example_sse_server() {
run("https://deno.land/x/oak@v6.3.1/examples/sseServer.ts", None);
run("https://deno.land/x/oak@v6.3.1/examples/sseServer.ts", &[]);
}
#[test]
fn std_0_75_0_http_server() {
run("https://deno.land/std@0.75.0/http/server.ts", None);
}
#[test]
fn deno_8188() {
fn deno_8188_full() {
run(
"https://raw.githubusercontent.com/nats-io/nats.ws/master/src/mod.ts",
None,
&[
"connect",
"NatsConnectionImpl",
"Nuid",
"nuid",
"ErrorCode",
"NatsError",
"DebugEvents",
"Empty",
"Events",
"MsgImpl",
"SubscriptionImpl",
"Subscriptions",
"setTransportFactory",
"setUrlParseFn",
"Connect",
"createInbox",
"INFO",
"ProtocolHandler",
"deferred",
"delay",
"extractProtocolMessage",
"render",
"timeout",
"headers",
"MsgHdrsImpl",
"Heartbeat",
"MuxSubscription",
"DataBuffer",
"checkOptions",
"Request",
"credsAuthenticator",
"jwtAuthenticator",
"nkeyAuthenticator",
"JSONCodec",
"StringCodec",
"QueuedIterator",
"Kind",
"Parser",
"State",
"DenoBuffer",
"MAX_SIZE",
"readAll",
"writeAll",
"Bench",
"Metric",
"TD",
"TE",
"isIP",
"parseIP",
"nkeys",
],
);
}
#[test]
fn deno_8188_01() {
run(
"https://raw.githubusercontent.com/nats-io/nats.deno/v1.0.0-12/nats-base-client/nkeys.ts",
&["nkeys"],
);
}
#[test]
fn deno_8188_02() {
run(
"https://raw.githubusercontent.com/nats-io/nkeys.js/v1.0.0-7/modules/esm/mod.ts",
&[
"NKeysError",
"NKeysErrorCode",
"createAccount",
"createOperator",
"createPair",
"createUser",
"decode",
"encode",
"fromPublic",
"fromSeed",
],
);
}
#[test]
fn deno_8188_03() {
run(
"https://raw.githubusercontent.com/nats-io/nkeys.js/v1.0.0-7/modules/esm/deps.ts",
&["denoHelper"],
);
}
#[test]
fn deno_8189() {
run("https://deno.land/x/lz4/mod.ts", None);
run(
"https://deno.land/x/lz4/mod.ts",
&["compress", "decompress"],
);
}
#[test]
fn deno_8211() {
run("https://unpkg.com/luxon@1.25.0/src/luxon.js", None);
run(
"https://unpkg.com/luxon@1.25.0/src/luxon.js",
&[
"DateTime",
"Duration",
"Interval",
"Info",
"Zone",
"FixedOffsetZone",
"IANAZone",
"InvalidZone",
"LocalZone",
"Settings",
],
);
}
#[test]
fn deno_8246() {
run("https://raw.githubusercontent.com/nats-io/nats.deno/v1.0.0-11/nats-base-client/internal_mod.ts",None);
run("https://raw.githubusercontent.com/nats-io/nats.deno/v1.0.0-11/nats-base-client/internal_mod.ts",&[
"NatsConnectionImpl",
"Nuid",
"nuid",
"ErrorCode",
"NatsError",
"DebugEvents",
"Empty",
"Events",
"MsgImpl",
"SubscriptionImpl",
"Subscriptions",
"setTransportFactory",
"setUrlParseFn",
"Connect",
"createInbox",
"INFO",
"ProtocolHandler",
"deferred",
"delay",
"extractProtocolMessage",
"render",
"timeout",
"headers",
"MsgHdrsImpl",
"Heartbeat",
"MuxSubscription",
"DataBuffer",
"checkOptions",
"Request",
"credsAuthenticator",
"jwtAuthenticator",
"nkeyAuthenticator",
"JSONCodec",
"StringCodec",
"QueuedIterator",
"Kind",
"Parser",
"State",
"DenoBuffer",
"MAX_SIZE",
"readAll",
"writeAll",
"Bench",
"Metric",
"TD",
"TE",
"isIP",
"parseIP",
"nkeys",
]);
}
fn run(url: &str, expeceted_bytes: Option<usize>) {
#[test]
#[ignore = "document is not defined when I use deno run"]
fn deno_6802() {
run("tests/deno/issue-6802/input.tsx", &[]);
}
#[test]
fn deno_8314_1() {
run("tests/deno/issue-8314/input.ts", &[]);
}
#[test]
fn deno_8314_2() {
run("https://dev.jspm.io/ngraph.graph", &["default"]);
}
#[test]
fn deno_8302() {
run("tests/deno/issue-8302/input.ts", &["DB", "Empty", "Status"]);
}
#[test]
fn deno_8399_1() {
run("tests/deno/issue-8399-1/input.ts", &[]);
}
#[test]
fn deno_8399_2() {
run("tests/deno/issue-8399-2/input.ts", &[]);
}
#[test]
fn merging_order_01() {
run(
"https://deno.land/x/oak@v6.3.1/multipart.ts",
&["FormDataReader"],
);
}
#[test]
fn reexport_01() {
run(
"https://raw.githubusercontent.com/aricart/tweetnacl-deno/import-type-fixes/src/nacl.ts",
&[
"AuthLength",
"ByteArray",
"HalfArray",
"HashLength",
"IntArray",
"NumArray",
"SealedBoxLength",
"SignLength",
"WordArray",
"_hash",
"_verify_16",
"_verify_32",
"auth",
"auth_full",
"blake2b",
"blake2b_final",
"blake2b_init",
"blake2b_update",
"blake2s",
"blake2s_final",
"blake2s_init",
"blake2s_update",
"decodeBase64",
"decodeHex",
"decodeUTF8",
"encodeBase64",
"encodeHex",
"encodeUTF8",
"hash",
"randomBytes",
"scalarbase",
"scalarmult",
"sealedbox",
"sealedbox_open",
"sign",
"sign_detached",
"sign_detached_verify",
"sign_keyPair",
"sign_keyPair_fromSecretKey",
"sign_keyPair_fromSeed",
"sign_open",
"validateBase64",
"validateHex",
"verify",
],
);
}
fn run(url: &str, exports: &[&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();
if let Some(expected) = expeceted_bytes {
assert_eq!(src.len(), expected);
}
::testing::run_test2(false, |cm, _| {
let fm = cm.load_file(&path).unwrap();
let loader = Loader { cm: cm.clone() };
let module = loader.load(&fm.name).unwrap().module;
let mut actual_exports = collect_exports(&module).into_iter().collect::<Vec<_>>();
actual_exports.sort();
let mut expected_exports = exports
.into_iter()
.map(|s| s.to_string())
.collect::<Vec<_>>();
expected_exports.sort();
assert_eq!(expected_exports, actual_exports);
Ok(())
})
.unwrap();
if env::var("CI").is_ok() {
return;
@ -125,7 +423,14 @@ fn bundle(url: &str) -> String {
Box::new(Hook),
);
let mut entries = HashMap::new();
entries.insert("main".to_string(), FileName::Custom(url.to_string()));
entries.insert(
"main".to_string(),
if url.starts_with("http") {
FileName::Custom(url.to_string())
} else {
FileName::Real(url.to_string().into())
},
);
let output = bundler.bundle(entries).unwrap();
let module = output.into_iter().next().unwrap().module;
@ -192,21 +497,30 @@ impl Load for Loader {
fn load(&self, file: &FileName) -> Result<ModuleData, Error> {
eprintln!("{}", file);
let url = match file {
FileName::Custom(v) => v,
let tsx;
let fm = match file {
FileName::Real(path) => {
tsx = path.to_string_lossy().ends_with(".tsx");
self.cm.load_file(path)?
}
FileName::Custom(url) => {
tsx = url.ends_with(".tsx");
let url = Url::parse(&url).context("failed to parse url")?;
let src = load_url(url.clone())?;
self.cm
.new_source_file(FileName::Custom(url.to_string()), src.to_string())
}
_ => unreachable!("this test only uses url"),
};
let url = Url::parse(&url).context("failed to parse url")?;
let src = load_url(url.clone())?;
let fm = self
.cm
.new_source_file(FileName::Custom(url.to_string()), src.to_string());
let lexer = Lexer::new(
Syntax::Typescript(TsConfig {
decorators: true,
tsx,
..Default::default()
}),
JscTarget::Es2020,
@ -215,12 +529,18 @@ impl Load for Loader {
);
let mut parser = Parser::new_from(lexer);
let module = parser.parse_typescript_module().unwrap();
let module = module.fold_with(&mut decorators::decorators(decorators::Config {
legacy: true,
emit_metadata: false,
}));
let module = module.fold_with(&mut strip());
let module = parser.parse_module().unwrap();
let module = module
.fold_with(&mut decorators::decorators(decorators::Config {
legacy: true,
emit_metadata: false,
}))
.fold_with(&mut react::react::<SingleThreadedComments>(
self.cm.clone(),
None,
Default::default(),
))
.fold_with(&mut strip());
Ok(ModuleData {
fm,
@ -235,6 +555,11 @@ struct Resolver;
impl Resolve for Resolver {
fn resolve(&self, base: &FileName, module_specifier: &str) -> Result<FileName, Error> {
match Url::parse(module_specifier) {
Ok(v) => return Ok(FileName::Custom(v.to_string())),
Err(_) => {}
}
let base_url = match base {
FileName::Custom(v) => v,
_ => unreachable!("this test only uses url"),
@ -287,3 +612,61 @@ impl swc_bundler::Hook for Hook {
])
}
}
fn collect_exports(module: &Module) -> HashSet<String> {
let mut v = ExportCollector::default();
module.visit_with(module, &mut v);
v.exports
}
#[derive(Default)]
struct ExportCollector {
exports: HashSet<String>,
}
impl Visit for ExportCollector {
fn visit_export_specifier(&mut self, s: &ExportSpecifier, _: &dyn Node) {
match s {
ExportSpecifier::Namespace(ns) => {
self.exports.insert(ns.name.sym.to_string());
}
ExportSpecifier::Default(_) => {
self.exports.insert("default".into());
}
ExportSpecifier::Named(named) => {
self.exports.insert(
named
.exported
.as_ref()
.unwrap_or(&named.orig)
.sym
.to_string(),
);
}
}
}
fn visit_export_default_decl(&mut self, _: &ExportDefaultDecl, _: &dyn Node) {
self.exports.insert("default".into());
}
fn visit_export_default_expr(&mut self, _: &ExportDefaultExpr, _: &dyn Node) {
self.exports.insert("default".into());
}
fn visit_export_decl(&mut self, export: &ExportDecl, _: &dyn Node) {
match &export.decl {
swc_ecma_ast::Decl::Class(ClassDecl { ident, .. })
| swc_ecma_ast::Decl::Fn(FnDecl { ident, .. }) => {
self.exports.insert(ident.sym.to_string());
}
swc_ecma_ast::Decl::Var(var) => {
let ids: Vec<Id> = find_ids(var);
self.exports
.extend(ids.into_iter().map(|v| v.0.to_string()));
}
_ => {}
}
}
}

View File

@ -0,0 +1,4 @@
import React from "https://cdn.pika.dev/@pika/react@^16.13.1";
import ReactDOM from "https://cdn.pika.dev/@pika/react-dom@^16.13.1";
ReactDOM.render(<div>Hi, React!</div>, document.getElementById("root"));

View File

@ -0,0 +1 @@
export { DB, Empty, Status } from 'https://deno.land/x/sqlite@v2.3.1/mod.ts';

View File

@ -0,0 +1,3 @@
import createGraph from "https://dev.jspm.io/ngraph.graph";
console.log(createGraph);

View File

@ -0,0 +1,2 @@
import * as yaml from "https://deno.land/std@0.78.0/encoding/yaml.ts"
console.log(yaml && 1)

View File

@ -0,0 +1,2 @@
import { stringify } from "https://deno.land/std@0.78.0/encoding/yaml.ts";
console.log(stringify({ a: 1 }));

View File

@ -0,0 +1,4 @@
import { b } from './b';
export const a = 'a';
export { b }

View File

@ -0,0 +1,4 @@
import { c } from './c';
export const b = 'b';
export { c }

View File

@ -0,0 +1,4 @@
import { a } from './a';
export const c = 'c';
export { a }

View File

@ -0,0 +1,2 @@
export { a } from './a';
export { c } from './c';

View File

@ -0,0 +1,6 @@
const a2 = 'a';
const a1 = a2;
const c2 = 'c';
const c1 = c2;
export { a1 as a };
export { c1 as c };

View File

@ -0,0 +1,4 @@
import { b } from './b';
export const a = 'a';
export { b }

View File

@ -0,0 +1,4 @@
import { c } from './c';
export const b = 'b';
export { c }

View File

@ -0,0 +1,4 @@
import { a } from './a';
export const c = 'c';
export { a }

View File

@ -0,0 +1,4 @@
import { a } from './a';
import { c } from './c';
console.log(a, c)

View File

@ -0,0 +1,7 @@
const a = 'a';
const a1 = a;
const c = 'c';
const c1 = c;
const a2 = a1;
const c2 = c1;
console.log(a2, c2);

View File

@ -0,0 +1,5 @@
import { b } from './b';
export { b };
export const a = 1;

View File

@ -0,0 +1,5 @@
import { a } from './a';
export { a };
export const b = 2;

View File

@ -0,0 +1,4 @@
import { a } from './a';
import { b } from './b';
console.log(a, b);

View File

@ -0,0 +1,7 @@
const a = 1;
const a1 = a;
const b = 2;
const b1 = b;
const a2 = a1;
const b2 = b1;
console.log(a2, b2);

View File

@ -1,8 +1,10 @@
const foo = function() {
const mod = function() {
class A {
}
const A1 = A;
return {
A
};
}();
const foo = mod;
export { foo };

View File

@ -1,8 +1,10 @@
const foo = function() {
async function foo1() {
const mod = function() {
async function foo() {
}
const foo1 = foo;
return {
foo: foo1
foo
};
}();
const foo = mod;
export { foo };

View File

@ -1,13 +1,17 @@
const foo = function() {
const mod = function() {
const [a, b, c] = [
1,
2,
3
];
const a1 = a;
const b1 = b;
const c1 = c;
return {
a,
b,
c
};
}();
const foo = mod;
export { foo };

View File

@ -1,12 +1,14 @@
const foo = {
const __default = {
"a": "a",
"b": "b",
"c": "c"
};
const foo1 = {
const foo = __default;
const __default1 = {
"a": "a1",
"b": "b1",
"c": "c1"
};
const foo1 = __default1;
console.log(foo);
console.log(foo1);

View File

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

View File

@ -0,0 +1,2 @@
import { a } from './a';
export const b = a + 1;

View File

@ -0,0 +1,2 @@
import { a } from './a';
export const c = a + 2;

View File

@ -0,0 +1,2 @@
import { a, b, c } from './lib';
console.log(a, b, c)

View File

@ -0,0 +1,3 @@
export * from './a';
export * from './b';
export * from './c';

View File

@ -0,0 +1,13 @@
const a = 1;
const a1 = a;
const a2 = a1;
const b = a2 + 1;
const a3 = a1;
const c = a3 + 2;
const a4 = a;
const b1 = b;
const c1 = c;
const a5 = a4;
const b2 = b1;
const c2 = c1;
console.log(a5, b2, c2);

View File

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

View File

@ -0,0 +1,2 @@
import { a } from './a';
export const b = a + 1;

View File

@ -0,0 +1,2 @@
import { a } from './a';
export const c = a + 2;

View File

@ -0,0 +1,5 @@
import { a, b, c } from './lib';
import { user1 } from './user1'
import { user2 } from './user2'
console.log(a, b, c)
console.log(user1, user2)

View File

@ -0,0 +1,3 @@
export * from './a';
export * from './b';
export * from './c';

View File

@ -0,0 +1,3 @@
import { a } from './a';
export const user1 = a + 1;
console.log('user 1', user1)

View File

@ -0,0 +1,5 @@
import { a } from './a';
import { b } from './b';
import { user1 } from './user1';
export const user2 = user1 + a + b;
console.log('user 2', user2)

View File

@ -0,0 +1,27 @@
const a = 1;
const a1 = a;
const a2 = a1;
const b = a2 + 1;
const b1 = b;
const a3 = a1;
const c = a3 + 2;
const a4 = a;
const b2 = b;
const c1 = c;
const a5 = a4;
const b3 = b2;
const c2 = c1;
const a6 = a1;
const b4 = b1;
const a7 = a1;
const user1 = a7 + 1;
console.log('user 1', user1);
const user11 = user1;
const user12 = user11;
const user2 = user12 + a6 + b4;
console.log('user 2', user2);
const user21 = user2;
const user22 = user21;
console.log(a5, b3, c2);
const user13 = user11;
console.log(user13, user22);

View File

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

View File

@ -0,0 +1 @@
export { a as b } from './a';

View File

@ -0,0 +1,2 @@
import { a, b } from './lib'
console.log(a, b)

View File

@ -0,0 +1,2 @@
export * from './a';
export * from './b';

View File

@ -0,0 +1,7 @@
const a = 1;
const a1 = a;
const a2 = a;
const b = a1;
const a3 = a2;
const b1 = b;
console.log(a3, b1);

View File

@ -1,5 +1,10 @@
class Zone {
class Zone3 {
}
class FixedOffsetZone extends Zone {
const __default = Zone3;
const Zone1 = __default;
class FixedOffsetZone2 extends Zone1 {
}
export { Zone, FixedOffsetZone };
const __default1 = FixedOffsetZone2;
const FixedOffsetZone1 = __default1;
const Zone2 = __default;
export { Zone2 as Zone, FixedOffsetZone1 as FixedOffsetZone };

View File

@ -1,10 +1,18 @@
class Zone {
class Zone3 {
}
class FixedOffsetZone extends Zone {
const __default = Zone3;
const Zone1 = __default;
class FixedOffsetZone3 extends Zone1 {
}
class Info {
const __default1 = FixedOffsetZone3;
const FixedOffsetZone1 = __default1;
class Info2 {
use() {
console.log(FixedOffsetZone);
console.log(FixedOffsetZone1);
}
}
export { Zone, Info, FixedOffsetZone };
const __default2 = Info2;
const Info1 = __default2;
const Zone2 = __default;
const FixedOffsetZone2 = __default1;
export { Zone2 as Zone, Info1 as Info, FixedOffsetZone2 as FixedOffsetZone };

View File

@ -0,0 +1,6 @@
import { b } from './b';
import { A } from './lib';
console.log(b, A);
export const a = 1;

View File

@ -0,0 +1,6 @@
import { c } from './c';
import { A } from './lib';
console.log(c, A);
export const b = 2

View File

@ -0,0 +1,6 @@
import { a } from './a';
import { A } from './lib';
console.log(a, A);
export const c = 3;

View File

@ -0,0 +1,3 @@
import { a } from './a';
console.log(a);

View File

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

View File

@ -0,0 +1,20 @@
const a = 1;
const a1 = a;
const b = 2;
const b1 = b;
const a2 = a1;
const c = 3;
class A {
}
const A1 = A;
const c1 = c;
const b2 = b1;
const A2 = A1;
console.log(b2, A2);
const c2 = c1;
const A3 = A1;
console.log(c2, A3);
const A4 = A1;
console.log(a2, A4);
const a3 = a1;
console.log(a3);

View File

@ -0,0 +1,3 @@
export default function square(a) {
return a * a;
}

View File

@ -0,0 +1,3 @@
export default function square(a) {
return a * a;
};

View File

@ -0,0 +1,3 @@
import entry from "./lib"
console.log(entry());

View File

@ -0,0 +1,3 @@
export default function square(a) {
return a * a;
}

View File

@ -0,0 +1,6 @@
function square(a) {
return a * a;
}
const __default = square;
const entry = __default;
console.log(entry());

View File

@ -0,0 +1,3 @@
import { u } from "./v";
console.log(u("a"));

View File

@ -0,0 +1,7 @@
function u(str) {
return str;
}
export { u };
export default null;
export const __esModule = true;

View File

@ -0,0 +1,2 @@
export * from "./u";
export { default } from "./u";

View File

@ -0,0 +1,6 @@
function u(str) {
return str;
}
const u1 = u;
const u2 = u1;
console.log(u2("a"));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,13 +8,15 @@ function d() {
});
return Object.assign(promise, methods);
}
const d1 = d;
const d2 = d1;
class A {
s = d();
s = d2();
a() {
this.s.resolve();
}
b() {
this.s = d();
this.s = d2();
}
}
new A();

View File

@ -8,8 +8,10 @@ function d() {
});
return Object.assign(promise, methods);
}
const d1 = d;
const d2 = d1;
class A {
s = d();
s = d2();
a() {
this.s.resolve();
}

View File

@ -0,0 +1,3 @@
export class A { }
export function utilForA() { }

View File

@ -0,0 +1,3 @@
export class B { }
export function utilForB() { }

View File

@ -0,0 +1,3 @@
export class C { }
export function utilForC() { }

View File

@ -0,0 +1,3 @@
import { A } from './lib';
console.log(A)

View File

@ -0,0 +1,3 @@
export * from './a';
export * from './b';
export * from './c';

View File

@ -0,0 +1,5 @@
class A {
}
const A1 = A;
const A2 = A1;
console.log(A2);

View File

@ -0,0 +1 @@
export * from './b';

View File

@ -0,0 +1 @@
export * from './c';

View File

@ -0,0 +1,5 @@
export const a = 1;
export function foo() { }
export class Class { }

View File

@ -0,0 +1 @@
export * from './a';

View File

@ -0,0 +1,5 @@
export const a = 1;
export function foo() {
}
export class Class {
}

Some files were not shown because too many files have changed in this diff Show More