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

View File

@ -37,7 +37,7 @@ jobs:
- name: Set platform name - name: Set platform name
run: | run: |
export NODE_PLATFORM_NAME=$(node -e "console.log(require('os').platform())") 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 shell: bash
- name: Install llvm - name: Install llvm
@ -129,7 +129,7 @@ jobs:
node-version: 12 node-version: 12
- name: Set release name - 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 # Do not cache node_modules, or yarn workspace links broken
- name: Install dependencies - name: Install dependencies

View File

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

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::{ use super::plan::CircularPlan;
merge::{ImportDropper, Unexporter}, use crate::{
plan::{CircularPlan, Plan}, 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 anyhow::{Context, Error};
use std::borrow::Borrow;
use swc_common::DUMMY_SP; use swc_common::DUMMY_SP;
use swc_ecma_ast::*; use swc_ecma_ast::*;
use swc_ecma_visit::{noop_visit_type, FoldWith, Node, Visit, VisitMutWith, VisitWith}; use swc_ecma_utils::find_ids;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
@ -20,309 +23,189 @@ where
L: Load, L: Load,
R: Resolve, R: Resolve,
{ {
pub(super) fn merge_circular_modules( pub(super) fn merge_circular(
&self, &self,
plan: &Plan, ctx: &Ctx,
circular_plan: &CircularPlan, plan: &CircularPlan,
entry_id: ModuleId, entry_id: ModuleId,
merged: &CHashSet<ModuleId>,
) -> Result<Module, Error> { ) -> Result<Module, Error> {
assert!( assert!(
circular_plan.chunks.len() >= 1, plan.chunks.len() >= 1,
"# of circular modules should be 2 or greater than 2 including entry. Got {:?}", "# 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 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 .chunks
.iter() .iter()
.map(|&id| self.scope.get_module(id).unwrap()) .map(|&id| self.scope.get_module(id).unwrap())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
merged.insert(entry_id);
let mut entry = self 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")?; .context("failed to merge dependency of a cyclic module")?;
entry.visit_mut_with(&mut ImportDropper { if !ctx.merged.insert(entry_id) {
imports: &entry_module.imports, log::debug!("[circular] skip: {:?}", entry_id);
return Ok(Module {
span: DUMMY_SP,
body: Default::default(),
shebang: Default::default(),
}); });
// 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));
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 { if dep == entry_id {
continue; return false;
} }
if !merged.insert(dep) {
log::debug!("[circular merge] Already merged: {:?}", dep); if !ctx.merged.insert(dep) {
continue; log::debug!("[circular] skip: {:?}", dep);
return false;
} }
log::debug!("Circular merge: {:?}", dep); log::debug!("Circular merge: {:?}", dep);
let new_module = self.merge_two_circular_modules(plan, &modules, entry, dep, merged)?; true
});
deps.sort();
let new_module = self.merge_circular_modules(ctx, &modules, entry, deps)?;
entry = new_module; entry = new_module;
// print_hygiene( if !exports.is_empty() {
// "[circular] entry:merge_two_circular_modules", entry
// &self.cm, .body
// &entry, .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) Ok(entry)
} }
/// Merges `a` and `b` into one module. /// Merges `a` and `b` into one module.
fn merge_two_circular_modules( fn merge_circular_modules(
&self, &self,
plan: &Plan, ctx: &Ctx,
_circular_modules: &[TransformedModule], _circular_modules: &[TransformedModule],
mut entry: Module, mut entry: Module,
dep: ModuleId, deps: Vec<ModuleId>,
merged: &CHashSet<ModuleId>,
) -> Result<Module, Error> { ) -> Result<Module, Error> {
self.run(|| { self.run(|| {
let mut dep_body = vec![];
for dep in deps {
let dep_info = self.scope.get_module(dep).unwrap();
let mut dep = self let mut dep = self
.merge_modules(plan, dep, false, true, merged) .merge_modules(ctx, dep, false, false)
.context("failed to merge dependency of a cyclic module")?; .context("failed to merge dependency of a cyclic module")?;
// print_hygiene("[circular] dep:init", &self.cm, &dep); // print_hygiene("[circular] dep:init 1", &self.cm, &dep);
dep = dep.fold_with(&mut Unexporter); 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 // 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) Ok(entry)
}) })
} }
} }
/// Originally, this method should create a dependency graph, but fn merge_respecting_order(dep: Vec<ModuleItem>, entry: Vec<ModuleItem>) -> Vec<ModuleItem> {
fn merge_respecting_order(mut dep: Vec<ModuleItem>, mut entry: Vec<ModuleItem>) -> Vec<ModuleItem> {
let mut new = Vec::with_capacity(dep.len() + entry.len()); let mut new = Vec::with_capacity(dep.len() + entry.len());
// While looping over items from entry, we check for dependency. new.extend(entry);
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(dep); new.extend(dep);
// Append remaining statements. sort(&mut new);
new.extend(entry);
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 super::*;
use crate::debug::print_hygiene;
use swc_common::{sync::Lrc, FileName, SourceMap}; use swc_common::{sync::Lrc, FileName, SourceMap};
use swc_ecma_parser::{lexer::Lexer, JscTarget, Parser, StringInput, Syntax}; use swc_ecma_parser::{lexer::Lexer, JscTarget, Parser, StringInput, Syntax};
use swc_ecma_utils::drop_span; use swc_ecma_utils::drop_span;
use testing::assert_eq;
fn parse(cm: Lrc<SourceMap>, name: &str, src: &str) -> Module { fn parse(cm: Lrc<SourceMap>, name: &str, src: &str) -> Module {
let fm = cm.new_source_file(FileName::Custom(name.into()), src.into()); 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] #[track_caller]
fn assert_merge_respecting_order(modules: &[&str], output: &str) { 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| { ::testing::run_test2(false, |cm, _handler| {
let mut entry = parse(cm.clone(), &format!("entry-{}", i), modules[i]).body; let mut entry = parse(cm.clone(), &format!("entry"), modules[0]);
for j in 0..modules.len() { for i in 1..modules.len() {
if i == j { let dep = parse(cm.clone(), &format!("deps-{}", i), modules[i]);
continue; entry.body = merge_respecting_order(entry.body, dep.body);
}
let dep = parse(cm.clone(), &format!("deps-{}-{}", i, j), modules[j]); print_hygiene("merge", &cm, &entry);
entry = merge_respecting_order(entry, dep.body);
} }
let output = parse(cm.clone(), "output", output); let output = parse(cm.clone(), "output", output);
assert_eq!(entry, output.body, "[{}]", i); if entry.body != output.body {
panic!()
log::info!("[{}] Success", i); }
Ok(()) Ok(())
}) })
.unwrap() .unwrap()
}
} }
#[test] #[test]
@ -57,27 +52,59 @@ fn simple_two() {
); );
} }
#[track_caller] #[test]
fn assert_dependency_index(entry: &str, dep: &str, expected: usize) { fn many_vars_1() {
::testing::run_test2(false, |cm, _handler| { assert_merge_respecting_order(
let entry = parse(cm.clone(), "entry", entry); &[
let dep = parse(cm.clone(), "dep", dep); "
const A6 = A5;
class B6 extends A6 {
}
const B7 = B6;
let calculated = dependency_index(&entry.body[0], &dep.body); ",
"
assert_eq!(calculated, Some(expected)); const B4 = B7;
class A4 {
Ok(()) method() {
}) return new B4();
.unwrap(); }
}
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] #[test]
fn dep_index_class() { fn no_dep_first_01() {
assert_dependency_index("class A extends B {}", "class B {}", 0); assert_merge_respecting_order(
} &[
"
#[test] const b1 = b2;
fn dep_index_export_class() { ",
assert_dependency_index("class A extends B {}", "export class B {}", 0); "
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 super::merge::Unexporter;
use crate::{bundler::load::TransformedModule, Bundler, Load, ModuleId, Resolve}; use crate::{
bundler::{
chunk::{merge::Ctx, plan::Dependancy},
load::TransformedModule,
},
Bundler, Load, Resolve,
};
use anyhow::Error; use anyhow::Error;
use std::{borrow::Cow, sync::atomic::Ordering}; 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_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}; use swc_ecma_visit::{noop_visit_mut_type, FoldWith, VisitMut, VisitMutWith};
impl<L, R> Bundler<'_, L, R> impl<L, R> Bundler<'_, L, R>
@ -35,26 +42,30 @@ where
/// modules. /// modules.
pub(super) fn merge_cjs( pub(super) fn merge_cjs(
&self, &self,
plan: &Plan, ctx: &Ctx,
is_entry: bool, is_entry: bool,
entry: &mut Module, entry: &mut Module,
info: &TransformedModule, info: &TransformedModule,
dep: Cow<Module>, dep: Cow<Module>,
dep_info: &TransformedModule, dep_info: &TransformedModule,
targets: &mut Vec<ModuleId>, targets: &mut Vec<Dependancy>,
) -> Result<(), Error> { ) -> Result<(), Error> {
if info.is_es6 && dep_info.is_es6 {
return Ok(());
}
log::debug!("Merging as a common js module: {}", info.fm.name); log::debug!("Merging as a common js module: {}", info.fm.name);
// If src is none, all requires are transpiled // If src is none, all requires are transpiled
let mut v = RequireReplacer { let mut v = RequireReplacer {
is_entry, is_entry,
ctxt: dep_info.ctxt(), ctxt: dep_info.export_ctxt(),
load_var: Ident::new("load".into(), DUMMY_SP.with_ctxt(dep_info.ctxt())), load_var: Ident::new("load".into(), DUMMY_SP.with_ctxt(dep_info.export_ctxt())),
replaced: false, replaced: false,
}; };
entry.body.visit_mut_with(&mut v); entry.body.visit_mut_with(&mut v);
if v.replaced { 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); targets.remove(idx);
} }
@ -65,12 +76,15 @@ where
let mut dep = dep.into_owned().fold_with(&mut Unexporter); let mut dep = dep.into_owned().fold_with(&mut Unexporter);
dep.visit_mut_with(&mut ImportDropper); dep.visit_mut_with(&mut ImportDropper);
dep.visit_mut_with(&mut DefaultHandler {
local_ctxt: dep_info.local_ctxt(),
});
prepend( prepend(
&mut entry.body, &mut entry.body,
ModuleItem::Stmt(wrap_module( ModuleItem::Stmt(wrap_module(
SyntaxContext::empty(), SyntaxContext::empty(),
dep_info.mark(), dep_info.local_ctxt(),
load_var, load_var,
dep, dep,
)), )),
@ -79,15 +93,15 @@ where
log::warn!("Injecting load"); log::warn!("Injecting load");
} }
if let Some(normal_plan) = plan.normal.get(&dep_info.id) { if let Some(normal_plan) = ctx.plan.normal.get(&dep_info.id) {
for &dep_id in &normal_plan.chunks { for dep in normal_plan.chunks.iter() {
if !targets.contains(&dep_id) { if !targets.contains(&dep) {
continue; continue;
} }
let dep_info = self.scope.get_module(dep_id).unwrap(); let dep_info = self.scope.get_module(dep.id).unwrap();
self.merge_cjs( self.merge_cjs(
plan, ctx,
false, false,
entry, entry,
info, info,
@ -105,7 +119,7 @@ where
fn wrap_module( fn wrap_module(
helper_ctxt: SyntaxContext, helper_ctxt: SyntaxContext,
top_level_mark: Mark, local_ctxt: SyntaxContext,
load_var: Ident, load_var: Ident,
dep: Module, dep: Module,
) -> Stmt { ) -> Stmt {
@ -118,19 +132,13 @@ fn wrap_module(
Param { Param {
span: DUMMY_SP, span: DUMMY_SP,
decorators: Default::default(), decorators: Default::default(),
pat: Pat::Ident(Ident::new( pat: Pat::Ident(Ident::new("module".into(), DUMMY_SP.with_ctxt(local_ctxt))),
"module".into(),
DUMMY_SP.apply_mark(top_level_mark),
)),
}, },
// exports // exports
Param { Param {
span: DUMMY_SP, span: DUMMY_SP,
decorators: Default::default(), decorators: Default::default(),
pat: Pat::Ident(Ident::new( pat: Pat::Ident(Ident::new("exports".into(), DUMMY_SP.with_ctxt(local_ctxt))),
"exports".into(),
DUMMY_SP.apply_mark(top_level_mark),
)),
}, },
], ],
decorators: vec![], 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::chunk::merge::Ctx, Bundler, Load, ModuleId, Resolve};
use crate::{bundler::load::TransformedModule, util::CHashSet, Bundler, Load, ModuleId, Resolve}; use anyhow::{bail, Error};
use anyhow::Error;
use std::mem::take; use std::mem::take;
use swc_atoms::js_word; use swc_atoms::js_word;
use swc_common::DUMMY_SP; use swc_common::DUMMY_SP;
@ -29,15 +28,17 @@ where
/// }; /// };
/// })(); /// })();
/// ``` /// ```
pub(super) fn wrap_esm_as_a_var( pub(super) fn wrap_esm(
&self, &self,
plan: &Plan, ctx: &Ctx,
id: ModuleId,
module: Module, module: Module,
info: &TransformedModule,
merged: &CHashSet<ModuleId>,
id: Ident,
) -> Result<Module, Error> { ) -> Result<Module, Error> {
let span = module.span; 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![]; let mut module_items = vec![];
@ -48,6 +49,13 @@ where
.into_iter() .into_iter()
.filter_map(|v| match v { .filter_map(|v| match v {
ModuleItem::Stmt(s) => Some(s), 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); module_items.push(v);
None None
@ -87,31 +95,18 @@ where
decls: vec![VarDeclarator { decls: vec![VarDeclarator {
span: DUMMY_SP, span: DUMMY_SP,
definite: false, definite: false,
name: Pat::Ident(id.clone()), name: Pat::Ident(var_name.into_ident()),
init: Some(Box::new(module_expr)), init: Some(Box::new(module_expr)),
}], }],
}; };
module_items.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(var_decl)))); module_items.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(var_decl))));
let module = Module { Ok(Module {
span: DUMMY_SP, span: DUMMY_SP,
shebang: None, shebang: None,
body: module_items, 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, DefaultDecl::TsInterfaceDecl(_) => None,
}, },
ModuleDecl::ExportDefaultExpr(_) => None, ModuleDecl::ExportDefaultExpr(_) => None,
ModuleDecl::ExportAll(_) => { ModuleDecl::ExportAll(export) => {
unimplemented!("export * from 'foo' inside a module loaded with computed key") return ModuleItem::ModuleDecl(ModuleDecl::ExportAll(export))
} }
ModuleDecl::ExportNamed(_) => { ModuleDecl::ExportNamed(named) => {
unimplemented!("export {{ a }} from 'foo' inside a module loaded with computed key") 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::TsImportEquals(_) => None,
ModuleDecl::TsExportAssignment(_) => None, ModuleDecl::TsExportAssignment(_) => None,
@ -232,7 +246,6 @@ impl Fold for ExportToReturn {
_ => true, _ => true,
}); });
if !self.exports.is_empty() {
new.push(ModuleItem::Stmt(Stmt::Return(ReturnStmt { new.push(ModuleItem::Stmt(Stmt::Return(ReturnStmt {
span: DUMMY_SP, span: DUMMY_SP,
arg: Some(Box::new(Expr::Object(ObjectLit { arg: Some(Box::new(Expr::Object(ObjectLit {
@ -240,7 +253,6 @@ impl Fold for ExportToReturn {
props: take(&mut self.exports), props: take(&mut self.exports),
}))), }))),
}))); })));
}
new new
} }

View File

@ -1,19 +1,18 @@
use super::plan::{NormalPlan, Plan};
use crate::{ use crate::{
bundler::load::{Source, Specifier, TransformedModule}, bundler::{
util::{CHashSet, IntoParallelIterator}, chunk::merge::Ctx,
load::{Source, Specifier, TransformedModule},
},
util::{ExprExt, MapWithMut, VarDeclaratorExt},
Bundler, Load, ModuleId, Resolve, Bundler, Load, ModuleId, Resolve,
}; };
use anyhow::{Context, Error}; use anyhow::{Context, Error};
#[cfg(feature = "concurrent")] #[cfg(feature = "concurrent")]
use rayon::iter::ParallelIterator; use rayon::iter::ParallelIterator;
use std::{ use std::mem::{replace, take};
collections::HashMap,
mem::{replace, take},
};
use swc_common::{Spanned, SyntaxContext, DUMMY_SP}; use swc_common::{Spanned, SyntaxContext, DUMMY_SP};
use swc_ecma_ast::*; 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}; use swc_ecma_visit::{noop_fold_type, noop_visit_mut_type, Fold, FoldWith, VisitMut, VisitMutWith};
impl<L, R> Bundler<'_, L, R> impl<L, R> Bundler<'_, L, R>
@ -52,149 +51,99 @@ where
/// ///
/// console.log(bar, baz); /// console.log(bar, baz);
/// ``` /// ```
pub(super) fn merge_reexports( pub(super) fn merge_export(
&self, &self,
plan: &Plan, ctx: &Ctx,
nomral_plan: &NormalPlan, dep_id: ModuleId,
entry: &mut Module, specifiers: &[Specifier],
info: &TransformedModule, ) -> Result<Module, Error> {
merged: &CHashSet<ModuleId>, self.run(|| {
) -> Result<(), Error> { log::debug!("Reexporting {:?}", dep_id);
log::trace!("merge_reexports: {}", info.fm.name); let dep_info = self.scope.get_module(dep_id).unwrap();
// Transitive dependencies
let mut additional_modules = vec![];
let mut reexports = vec![];
let mut decls_for_reexport: HashMap<_, Vec<VarDeclarator>> = HashMap::new();
// 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());
}
reexports.push(v);
} else {
additional_modules.push(v);
}
}
for (src, specifiers) in info.exports.reexports.iter() {
let imported = self.scope.get_module(src.module_id).unwrap();
// export * from './foo';
if specifiers.is_empty() {
let vars = decls_for_reexport.entry(src.module_id).or_default();
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(),
)));
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)
}
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 let mut dep = self
.merge_modules(plan, src.module_id, false, false, merged) .merge_modules(ctx, dep_id, false, true)
.with_context(|| { .context("failed to get module for merging")?;
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); // print_hygiene(
// &format!(
// "reexport: load dep: {} ({:?}, {:?})",
// dep_info.fm.name,
// dep_info.local_ctxt(),
// dep_info.export_ctxt()
// ),
// &self.cm,
// &dep,
// );
let id_of_export_namespace_from = specifiers.iter().find_map(|s| match s { self.handle_reexport(&dep_info, &mut dep);
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 { // print_hygiene(&format!("dep: handle reexport"), &self.cm, &dep);
dep = self.wrap_esm_as_a_var(plan, dep, &imported, merged, id)?;
} else { // for stmt in &mut dep.body {
dep = self.remark_exports(dep, src.ctxt, None, false); // let decl = match stmt {
// ModuleItem::ModuleDecl(decl) => decl,
// ModuleItem::Stmt(_) => continue,
// };
// 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;
// }
// exported.span =
//
// exported.span.with_ctxt(dep_info.export_ctxt());
// } None => {
// if named.orig.span.ctxt != dep_info.local_ctxt()
// { continue;
// }
// named.exported = Some(Ident::new(
// named.orig.sym.clone(),
//
// named.orig.span.with_ctxt(dep_info.export_ctxt()),
// )); }
// },
// }
// }
// }
// ModuleDecl::ExportDefaultDecl(_) => {}
// ModuleDecl::ExportDefaultExpr(_) => {}
// ModuleDecl::ExportAll(_) => {}
// _ => {}
// }
// }
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 {
match specifier {
Specifier::Namespace { local, .. } => {
dep.body.push(
module_name
.assign_to(local.clone())
.into_module_item("merge_export"),
);
break;
}
_ => {}
}
}
} }
// print_hygiene(&format!("dep: remark exports"), &self.cm, &dep);
if !specifiers.is_empty() { if !specifiers.is_empty() {
dep.visit_mut_with(&mut UnexportAsVar { dep.visit_mut_with(&mut UnexportAsVar {
dep_ctxt: src.ctxt, dep_export_ctxt: dep_info.export_ctxt(),
_entry_ctxt: info.ctxt(), _specifiers: &specifiers,
_exports: &specifiers,
}); });
// print_hygiene(&format!("dep: unexport as var"), &self.cm, &dep); // print_hygiene(&format!("dep: unexport as var"), &self.cm, &dep);
@ -206,164 +155,92 @@ where
// print_hygiene(&format!("dep: unexport"), &self.cm, &dep); // print_hygiene(&format!("dep: unexport"), &self.cm, &dep);
} }
Ok(Some((src, dep))) // TODO: Add varaible based on specifers
Ok(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. /// # ExportDecl
let is_indirect = !nomral_plan.chunks.contains(&src.module_id); ///
/// 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);
let add_to = if specifiers.is_empty() && is_indirect { for stmt in &mut module.body {
// User provided code like `export * from './foo';`, but planner decide to merge let mut vars = vec![];
// it within dependency module. So we reexport them using a named export. let mut stmt = stmt.take();
&mut star_reexports
} else {
&mut normal_reexports
};
for specifier in specifiers { match &mut stmt {
let (imported, exported) = match specifier { ModuleItem::ModuleDecl(ModuleDecl::Import(import)) => {
Specifier::Specific { local, alias } => { for specifier in &import.specifiers {
let alias = alias.unwrap_or_else(|| local.clone()); match specifier {
let local = local.replace_mark(info.mark()); ImportSpecifier::Named(named) => match &named.imported {
(local.into_ident(), alias.into_ident()) Some(imported) => {
vars.push(imported.clone().assign_to(named.local.clone()));
} }
Specifier::Namespace { .. } => continue, None => {}
};
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,
}, },
))); _ => {}
} }
} }
import.specifiers.clear();
}
for dep in deps { ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export)) => {
let dep = dep?; match &export.decl {
let dep = match dep { Decl::Class(ClassDecl { ident, .. }) | Decl::Fn(FnDecl { ident, .. }) => {
Some(v) => v, let mut exported = ident.clone();
None => continue, 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),
);
}
_ => {}
}; };
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 new_body.push(stmt);
let mut injector = ExportInjector { for var in vars {
imported: dep.body, new_body.push(var.into_module_item("from_export_rs"))
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( module.body = new_body;
// &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(())
} }
} }
struct ExportInjector { pub(super) struct ExportInjector {
imported: Vec<ModuleItem>, pub imported: Vec<ModuleItem>,
source: Source, pub source: Source,
} }
impl VisitMut for ExportInjector { impl VisitMut for ExportInjector {
@ -400,7 +277,7 @@ impl VisitMut for ExportInjector {
.. ..
}) => { }) => {
let mut imported = imported.clone(); 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 { Some(VarDeclarator {
span: DUMMY_SP, span: DUMMY_SP,
@ -413,13 +290,8 @@ impl VisitMut for ExportInjector {
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if !decls.is_empty() { for var in decls {
buf.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(VarDecl { buf.push(var.into_module_item("ExportInjector"));
span: DUMMY_SP,
kind: VarDeclKind::Const,
declare: false,
decls,
}))));
} }
} }
@ -492,12 +364,10 @@ impl VisitMut for ExportInjector {
/// export { foo#7 as foo#5 } where #5 is mark of current entry. /// export { foo#7 as foo#5 } where #5 is mark of current entry.
struct UnexportAsVar<'a> { struct UnexportAsVar<'a> {
/// Syntax context for the generated variables. /// Syntax context for the generated variables.
dep_ctxt: SyntaxContext, dep_export_ctxt: SyntaxContext,
_entry_ctxt: SyntaxContext,
/// Exports to preserve /// Exports to preserve
_exports: &'a [Specifier], _specifiers: &'a [Specifier],
} }
impl VisitMut for UnexportAsVar<'_> { impl VisitMut for UnexportAsVar<'_> {
@ -513,20 +383,16 @@ impl VisitMut for UnexportAsVar<'_> {
Box::new(Expr::Invalid(Invalid { span: DUMMY_SP })), Box::new(Expr::Invalid(Invalid { span: DUMMY_SP })),
); );
*n = ModuleItem::Stmt(Stmt::Decl(Decl::Var(VarDecl { *n = VarDeclarator {
span: export.span,
kind: VarDeclKind::Const,
declare: false,
decls: vec![VarDeclarator {
span: DUMMY_SP, span: DUMMY_SP,
name: Pat::Ident(Ident::new( name: Pat::Ident(Ident::new(
"__default".into(), "__default".into(),
expr.span().with_ctxt(self.dep_ctxt), expr.span().with_ctxt(self.dep_export_ctxt),
)), )),
init: Some(expr), init: Some(expr),
definite: false, definite: false,
}], }
}))); .into_module_item("UnexportAsVar");
} }
ModuleItem::ModuleDecl(ModuleDecl::ExportNamed( ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
ref export @ NamedExport { src: None, .. }, ref export @ NamedExport { src: None, .. },
@ -554,18 +420,24 @@ impl VisitMut for UnexportAsVar<'_> {
} }
} }
None => { 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 { decls.push(VarDeclarator {
span: n.span, span: n.span,
name: Pat::Ident(Ident::new( name: Pat::Ident(Ident::new(
n.orig.sym.clone(), n.orig.sym.clone(),
n.orig.span.with_ctxt(self.dep_ctxt), n.orig.span.with_ctxt(self.dep_export_ctxt),
)), )),
init: Some(Box::new(Expr::Ident(n.orig.clone()))), init: Some(Box::new(Expr::Ident(n.orig.clone()))),
definite: false, definite: false,
}) })
} }
}
}, },
} }
} }

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -107,29 +107,29 @@ pub(super) struct Plan {
pub bundle_kinds: HashMap<ModuleId, BundleKind>, pub bundle_kinds: HashMap<ModuleId, BundleKind>,
} }
impl Plan { #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub fn entry_as_circular(&self, entry: ModuleId) -> Option<&CircularPlan> { pub(super) enum DepType {
let plan = self.circular.get(&entry)?;
if plan.chunks.is_empty() {
return None;
}
Some(plan)
}
}
#[derive(Debug, Default)]
pub(super) struct NormalPlan {
/// Direct dependencies /// Direct dependencies
pub chunks: Vec<ModuleId>, Direct,
/// Used to handle /// Used to handle
/// ///
/// - a -> b /// - a -> b
/// - a -> c /// - a -> c
/// - b -> d /// - b -> d
/// - c -> 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)] #[derive(Debug, Default)]
@ -150,17 +150,10 @@ where
entries: HashMap<String, TransformedModule>, entries: HashMap<String, TransformedModule>,
) -> Result<Plan, Error> { ) -> Result<Plan, Error> {
let plan = self.calculate_plan(entries)?; let plan = self.calculate_plan(entries)?;
let plan = self.handle_duplicates(plan);
Ok(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> { fn calculate_plan(&self, entries: HashMap<String, TransformedModule>) -> Result<Plan, Error> {
let mut builder = PlanBuilder::default(); let mut builder = PlanBuilder::default();
@ -211,7 +204,7 @@ where
Ok(self.build_plan(&metadata, builder)) 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(); let mut plans = Plan::default();
for (id, kind) in builder.kinds.iter() { for (id, kind) in builder.kinds.iter() {
@ -233,6 +226,14 @@ where
.collect(); .collect();
deps.sort(); deps.sort();
let should_be_reexport = deps.iter().any(|&dep| {
builder
.all_deps
.get(&(entry, dep))
.copied()
.unwrap_or(false)
});
for &dep in &deps { for &dep in &deps {
if let Some(circular_members) = builder.circular.get(entry) { if let Some(circular_members) = builder.circular.get(entry) {
if circular_members.contains(&dep) { if circular_members.contains(&dep) {
@ -243,7 +244,15 @@ where
); );
if entry != root_entry && dep != root_entry { if entry != root_entry && dep != root_entry {
// done.insert(dep); // 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; continue;
} }
@ -260,7 +269,6 @@ where
.collect::<Vec<_>>(); .collect::<Vec<_>>();
dependants.sort(); 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 let is_reexport = builder
@ -282,7 +290,15 @@ where
// b <- c // b <- c
// //
if dependants.len() <= 1 { if dependants.len() <= 1 {
plans.normal.entry(entry).or_default().chunks.push(dep); plans
.normal
.entry(entry)
.or_default()
.chunks
.push(Dependancy {
id: dep,
ty: DepType::Direct,
});
continue; continue;
} }
@ -300,16 +316,22 @@ where
let normal_plan = plans.normal.entry(module).or_default(); let normal_plan = plans.normal.entry(module).or_default();
for &dep in &deps { for &dep in &deps {
if !normal_plan.chunks.contains(&dep) let contains = normal_plan.chunks.iter().any(|d| d.id == dep);
&& !normal_plan.transitive_chunks.contains(&dep)
{ if !contains {
if dependants.contains(&module) { if dependants.contains(&module) {
log::trace!("Normal: {:?} => {:?}", module, dep); log::trace!("Normal (non-es6): {:?} => {:?}", module, dep);
// `entry` depends on `module` directly // `entry` depends on `module` directly
normal_plan.chunks.push(dep); normal_plan.chunks.push(Dependancy {
id: dep,
ty: DepType::Direct,
});
} else { } else {
log::trace!("Transitive: {:?} => {:?}", module, dep); log::trace!("Transitive (non-es6): {:?} => {:?}", module, dep);
normal_plan.transitive_chunks.push(dep); normal_plan.chunks.push(Dependancy {
id: dep,
ty: DepType::Transitive,
});
} }
} }
} }
@ -319,11 +341,18 @@ where
if is_reexport { if is_reexport {
let normal_plan = plans.normal.entry(entry).or_default(); let normal_plan = plans.normal.entry(entry).or_default();
if !normal_plan.chunks.contains(&dep) { if normal_plan
.chunks
.iter()
.all(|dependancy| dependancy.id != dep)
{
done.insert(dep); done.insert(dep);
log::trace!("Normal: {:?} => {:?}", entry, dep); log::trace!("Normal: {:?} => {:?}", entry, dep);
normal_plan.chunks.push(dep); normal_plan.chunks.push(Dependancy {
id: dep,
ty: DepType::Direct,
});
} }
continue; continue;
} }
@ -332,16 +361,14 @@ where
// Should be merged as a transitive dependency. // Should be merged as a transitive dependency.
let higher_module = if plans.entries.contains(&dependants[0]) { let higher_module = if plans.entries.contains(&dependants[0]) {
dependants[0] dependants[0]
} else if dependants.len() == 2 } else if dependants.len() == 2 && plans.entries.contains(&dependants[1]) {
&& plans.entries.contains(&dependants[1])
{
dependants[1] dependants[1]
} else { } else {
least_common_ancestor(&builder, &dependants) least_common_ancestor(&builder, &dependants)
}; };
if dependants.len() == 2 && dependants.contains(&higher_module) { if dependants.len() == 2 && dependants.contains(&higher_module) {
let mut entry = if is_reexport { let mut entry = if should_be_reexport {
higher_module higher_module
} else { } else {
*dependants.iter().find(|&&v| v != higher_module).unwrap() *dependants.iter().find(|&&v| v != higher_module).unwrap()
@ -353,31 +380,63 @@ where
} }
let normal_plan = plans.normal.entry(entry).or_default(); let normal_plan = plans.normal.entry(entry).or_default();
if !normal_plan.chunks.contains(&dep) { if normal_plan
.chunks
.iter()
.all(|dependancy| dependancy.id != dep)
{
log::trace!("Normal: {:?} => {:?}", entry, dep); log::trace!("Normal: {:?} => {:?}", entry, dep);
done.insert(dep); done.insert(dep);
normal_plan.chunks.push(dep); normal_plan.chunks.push(Dependancy {
id: dep,
ty: DepType::Direct,
});
} }
} else { } else {
let t = &mut plans if self.scope.should_be_wrapped_with_a_fn(dep) {
.normal let normal_entry = &mut plans.normal.entry(entry).or_default();
.entry(higher_module)
.or_default() let t = &mut normal_entry.chunks;
.transitive_chunks; if t.iter().all(|dependancy| dependancy.id != dep) {
if !t.contains(&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); log::trace!("Transitive: {:?} => {:?}", entry, dep);
done.insert(dep); done.insert(dep);
t.push(dep) t.push(Dependancy {
id: dep,
ty: if should_be_reexport {
DepType::Direct
} else {
DepType::Transitive
},
})
}
} }
} }
} else { } else {
// Direct dependency. // Direct dependency.
log::trace!("Normal: {:?} => {:?}", entry, dep); log::trace!("Normal: {:?} => {:?}", entry, dep);
done.insert(dep); done.insert(dep);
plans.normal.entry(entry).or_default().chunks.push(dep); plans
} .normal
.entry(entry)
continue; .or_default()
.chunks
.push(Dependancy {
id: dep,
ty: DepType::Direct,
});
} }
} }
} }
@ -385,7 +444,7 @@ where
// Sort transitive chunks topologically. // Sort transitive chunks topologically.
for (_, normal_plan) in &mut plans.normal { for (_, normal_plan) in &mut plans.normal {
toposort(&builder, &mut normal_plan.transitive_chunks); toposort(&builder, &mut normal_plan.chunks);
} }
// Handle circular imports // Handle circular imports
@ -416,7 +475,8 @@ where
{ {
let c = &mut plans.normal.entry(dep).or_default().chunks; 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); c.remove(pos);
} }
@ -428,7 +488,7 @@ where
.entry(circular_member) .entry(circular_member)
.or_default() .or_default()
.chunks; .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); c.remove(pos);
} }
} }
@ -550,24 +610,51 @@ where
} }
} }
fn toposort(b: &PlanBuilder, module_ids: &mut Vec<ModuleId>) { fn toposort(b: &PlanBuilder, module_ids: &mut Vec<Dependancy>) {
let len = module_ids.len();
if module_ids.len() <= 1 { if module_ids.len() <= 1 {
return; return;
} }
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 { for i in 0..len {
for j in i..len { let m = module_ids[i].id;
let mi = module_ids[i]; if graph.neighbors_directed(m, Incoming).count() != 0 {
let mj = module_ids[j];
if mi == mj {
continue; continue;
} }
if b.direct_deps.contains_edge(mj, mi) { did_work = true;
module_ids.swap(i, j); 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 super::Plan;
use crate::bundler::tests::{suite, Tester}; use crate::bundler::{
chunk::plan::DepType,
tests::{suite, Tester},
};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use swc_common::FileName; use swc_common::FileName;
@ -18,18 +21,33 @@ fn assert_normal_transitive(
) { ) {
if deps.is_empty() { if deps.is_empty() {
if let Some(v) = p.normal.get(&t.id(&format!("{}.js", entry))) { 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; return;
} }
assert_eq!( let actual = p.normal[&t.id(&format!("{}.js", entry))]
p.normal[&t.id(&format!("{}.js", entry))]
.chunks .chunks
.iter() .iter()
.cloned() .filter(|dep| match dep.ty {
.collect::<HashSet<_>>(), DepType::Direct => true,
_ => false,
})
.map(|dep| dep.id)
.collect::<HashSet<_>>();
assert_eq!(
actual,
deps.into_iter() deps.into_iter()
.map(|s| format!("{}.js", s)) .map(|s| format!("{}.js", s))
.map(|s| t.id(&s)) .map(|s| t.id(&s))
@ -40,9 +58,13 @@ fn assert_normal_transitive(
assert_eq!( assert_eq!(
p.normal[&t.id(&format!("{}.js", entry))] p.normal[&t.id(&format!("{}.js", entry))]
.transitive_chunks .chunks
.iter() .iter()
.cloned() .filter(|dep| match dep.ty {
DepType::Transitive => true,
_ => false,
})
.map(|dep| dep.id)
.collect::<HashSet<_>>(), .collect::<HashSet<_>>(),
transitive_deps transitive_deps
.into_iter() .into_iter()
@ -958,3 +980,55 @@ fn deno_003() {
Ok(()) 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, &self,
file_name: &FileName, file_name: &FileName,
module: &mut Module, module: &mut Module,
export_ctxt: SyntaxContext,
) -> RawExports { ) -> RawExports {
self.run(|| { self.run(|| {
let mut v = ExportFinder { let mut v = ExportFinder {
info: Default::default(), info: Default::default(),
file_name, file_name,
bundler: self, bundler: self,
export_ctxt,
}; };
module.visit_mut_with(&mut v); module.visit_mut_with(&mut v);
@ -56,6 +58,7 @@ where
info: RawExports, info: RawExports,
file_name: &'a FileName, file_name: &'a FileName,
bundler: &'a Bundler<'b, L, R>, bundler: &'a Bundler<'b, L, R>,
export_ctxt: SyntaxContext,
} }
impl<L, R> ExportFinder<'_, '_, L, R> impl<L, R> ExportFinder<'_, '_, L, R>
@ -63,7 +66,8 @@ where
L: Load, L: Load,
R: Resolve, 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. // Don't apply mark if it's a core module.
if self if self
.bundler .bundler
@ -75,10 +79,12 @@ where
return None; return None;
} }
let path = self.bundler.resolve(self.file_name, src).ok()?; let path = self.bundler.resolve(self.file_name, src).ok()?;
let (_, mark) = self.bundler.scope.module_id_gen.gen(&path); let (_, local_mark, export_mark) = self.bundler.scope.module_id_gen.gen(&path);
let ctxt = SyntaxContext::empty();
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) { fn mark_as_wrapping_required(&self, src: &JsWord) {
@ -97,7 +103,7 @@ where
Ok(v) => v, Ok(v) => v,
_ => return, _ => 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); self.bundler.scope.mark_as_wrapping_required(id);
} }
@ -207,6 +213,10 @@ where
for s in &mut named.specifiers { for s in &mut named.specifiers {
match s { match s {
ExportSpecifier::Namespace(n) => { ExportSpecifier::Namespace(n) => {
if let Some((_, export_ctxt)) = ctxt {
n.name.span.ctxt = export_ctxt;
}
need_wrapping = true; need_wrapping = true;
v.push(Specifier::Namespace { v.push(Specifier::Namespace {
local: n.name.clone().into(), local: n.name.clone().into(),
@ -220,8 +230,19 @@ where
}); });
} }
ExportSpecifier::Named(n) => { ExportSpecifier::Named(n) => {
if let Some(ctxt) = ctxt { if let Some((_, export_ctxt)) = ctxt {
n.orig.span = n.orig.span.with_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 { if let Some(exported) = &n.exported {
@ -247,6 +268,11 @@ where
} }
ModuleItem::ModuleDecl(ModuleDecl::ExportAll(all)) => { 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(); self.info.items.entry(Some(all.src.clone())).or_default();
} }
_ => {} _ => {}

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
use super::load::TransformedModule; use super::load::TransformedModule;
use crate::{ use crate::{
id::{ModuleId, ModuleIdGenerator}, id::{Id, ModuleId, ModuleIdGenerator},
util::CloneMap, util::CloneMap,
}; };
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
@ -36,7 +36,7 @@ impl Scope {
} }
pub fn get_module_by_path(&self, file_name: &FileName) -> Option<TransformedModule> { 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) self.get_module(id)
} }
@ -62,4 +62,15 @@ impl Scope {
false 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_atoms::JsWord;
use swc_common::{sync::Lock, FileName, Mark, SyntaxContext, DUMMY_SP}; 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; use swc_ecma_utils::ident::IdentLike;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
@ -27,20 +27,23 @@ impl fmt::Debug for ModuleId {
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub(crate) struct ModuleIdGenerator { pub(crate) struct ModuleIdGenerator {
v: AtomicU32, v: AtomicU32,
cache: Lock<HashMap<FileName, (ModuleId, Mark)>>, /// `(module_id, local_mark, export_mark)`
cache: Lock<HashMap<FileName, (ModuleId, Mark, Mark)>>,
} }
impl ModuleIdGenerator { 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(); let mut w = self.cache.lock();
if let Some(v) = w.get(file_name) { if let Some(v) = w.get(file_name) {
return v.clone(); return v.clone();
} }
let id = ModuleId(self.v.fetch_add(1, SeqCst)); let id = ModuleId(self.v.fetch_add(1, SeqCst));
let mark = Mark::fresh(Mark::root()); let local_mark = Mark::fresh(Mark::root());
w.insert(file_name.clone(), (id, mark)); let export_mark = Mark::fresh(Mark::root());
(id, mark) 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 &self.0
} }
pub fn ctxt(&self) -> SyntaxContext {
self.1
}
pub fn into_ident(self) -> Ident { pub fn into_ident(self) -> Ident {
Ident::new(self.0, DUMMY_SP.with_ctxt(self.1)) Ident::new(self.0, DUMMY_SP.with_ctxt(self.1))
} }
pub fn replace_mark(mut self, mark: Mark) -> Self { pub fn with_ctxt(mut self, ctxt: SyntaxContext) -> Self {
self.1 = SyntaxContext::empty().apply_mark(mark); self.1 = ctxt;
self self
} }
} }
@ -109,3 +116,17 @@ impl PartialEq<JsWord> for Id {
self.0 == *other 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_atoms::js_word;
use swc_common::{Span, SyntaxContext, DUMMY_SP}; use swc_common::{Span, SyntaxContext, DUMMY_SP};
use swc_ecma_ast::*; use swc_ecma_ast::*;
use swc_ecma_utils::ident::IdentLike;
use swc_ecma_visit::{noop_visit_mut_type, VisitMut}; 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] /// Helper for migration from [Fold] to [VisitMut]
pub(crate) trait MapWithMut: Sized { pub(crate) trait MapWithMut: Sized {
fn dummy() -> Self; 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)] #[derive(Debug)]
pub(crate) struct CHashSet<V> pub(crate) struct CHashSet<V>
where where

View File

@ -5,7 +5,7 @@
use anyhow::{Context, Error}; use anyhow::{Context, Error};
use sha1::{Digest, Sha1}; use sha1::{Digest, Sha1};
use std::{ use std::{
collections::HashMap, collections::{HashMap, HashSet},
env, env,
fs::{create_dir_all, read_to_string, write}, fs::{create_dir_all, read_to_string, write},
path::PathBuf, path::PathBuf,
@ -13,81 +13,379 @@ use std::{
}; };
use swc_atoms::js_word; use swc_atoms::js_word;
use swc_bundler::{Bundler, Load, ModuleData, ModuleRecord, Resolve}; use swc_bundler::{Bundler, Load, ModuleData, ModuleRecord, Resolve};
use swc_common::{sync::Lrc, FileName, SourceMap, Span, GLOBALS}; use swc_common::{comments::SingleThreadedComments, sync::Lrc, FileName, SourceMap, Span, GLOBALS};
use swc_ecma_ast::{ use swc_ecma_ast::*;
Bool, Expr, ExprOrSuper, Ident, KeyValueProp, Lit, MemberExpr, MetaPropExpr, PropName, Str,
};
use swc_ecma_codegen::{text_writer::JsWriter, Emitter}; use swc_ecma_codegen::{text_writer::JsWriter, Emitter};
use swc_ecma_parser::{lexer::Lexer, JscTarget, Parser, StringInput, Syntax, TsConfig}; use swc_ecma_parser::{lexer::Lexer, JscTarget, Parser, StringInput, Syntax, TsConfig};
use swc_ecma_transforms::{proposals::decorators, typescript::strip}; use swc_ecma_transforms::{proposals::decorators, react, typescript::strip};
use swc_ecma_visit::FoldWith; use swc_ecma_utils::{find_ids, Id};
use swc_ecma_visit::{FoldWith, Node, Visit, VisitWith};
use testing::assert_eq;
use url::Url; use url::Url;
#[test] #[test]
fn oak_6_3_1_application() { 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] #[test]
fn oak_6_3_1_mod() { 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] #[test]
fn std_0_74_9_http_server() { fn std_0_74_0_http_server() {
run("https://deno.land/std@0.74.0/http/server.ts", None); run(
"https://deno.land/std@0.74.0/http/server.ts",
&[
"ServerRequest",
"Server",
"_parseAddrFromStr",
"serve",
"listenAndServe",
"serveTLS",
"listenAndServeTLS",
],
);
} }
#[test] #[test]
#[ignore = "Does not finish by default"] #[ignore = "Does not finish by default"]
fn oak_6_3_1_example_server() { 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] #[test]
#[ignore = "Does not finish by default"] #[ignore = "Does not finish by default"]
fn oak_6_3_1_example_sse_server() { 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] #[test]
fn std_0_75_0_http_server() { fn deno_8188_full() {
run("https://deno.land/std@0.75.0/http/server.ts", None);
}
#[test]
fn deno_8188() {
run( run(
"https://raw.githubusercontent.com/nats-io/nats.ws/master/src/mod.ts", "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] #[test]
fn deno_8189() { fn deno_8189() {
run("https://deno.land/x/lz4/mod.ts", None); run(
"https://deno.land/x/lz4/mod.ts",
&["compress", "decompress"],
);
} }
#[test] #[test]
fn deno_8211() { 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] #[test]
fn deno_8246() { 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 dir = tempfile::tempdir().expect("failed to crate temp file");
let path = dir.path().join("main.js"); let path = dir.path().join("main.js");
println!("{}", path.display()); println!("{}", path.display());
let src = bundle(url); let src = bundle(url);
write(&path, &src).unwrap(); 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() { if env::var("CI").is_ok() {
return; return;
@ -125,7 +423,14 @@ fn bundle(url: &str) -> String {
Box::new(Hook), Box::new(Hook),
); );
let mut entries = HashMap::new(); let mut entries = HashMap::new();
entries.insert("main".to_string(), FileName::Custom(url.to_string())); entries.insert(
"main".to_string(),
if url.starts_with("http") {
FileName::Custom(url.to_string())
} else {
FileName::Real(url.to_string().into())
},
);
let output = bundler.bundle(entries).unwrap(); let output = bundler.bundle(entries).unwrap();
let module = output.into_iter().next().unwrap().module; let module = output.into_iter().next().unwrap().module;
@ -192,21 +497,30 @@ impl Load for Loader {
fn load(&self, file: &FileName) -> Result<ModuleData, Error> { fn load(&self, file: &FileName) -> Result<ModuleData, Error> {
eprintln!("{}", file); eprintln!("{}", file);
let url = match file { let tsx;
FileName::Custom(v) => v,
_ => unreachable!("this test only uses url"), 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 url = Url::parse(&url).context("failed to parse url")?;
let src = load_url(url.clone())?; let src = load_url(url.clone())?;
let fm = self
.cm self.cm
.new_source_file(FileName::Custom(url.to_string()), src.to_string()); .new_source_file(FileName::Custom(url.to_string()), src.to_string())
}
_ => unreachable!("this test only uses url"),
};
let lexer = Lexer::new( let lexer = Lexer::new(
Syntax::Typescript(TsConfig { Syntax::Typescript(TsConfig {
decorators: true, decorators: true,
tsx,
..Default::default() ..Default::default()
}), }),
JscTarget::Es2020, JscTarget::Es2020,
@ -215,12 +529,18 @@ impl Load for Loader {
); );
let mut parser = Parser::new_from(lexer); let mut parser = Parser::new_from(lexer);
let module = parser.parse_typescript_module().unwrap(); let module = parser.parse_module().unwrap();
let module = module.fold_with(&mut decorators::decorators(decorators::Config { let module = module
.fold_with(&mut decorators::decorators(decorators::Config {
legacy: true, legacy: true,
emit_metadata: false, emit_metadata: false,
})); }))
let module = module.fold_with(&mut strip()); .fold_with(&mut react::react::<SingleThreadedComments>(
self.cm.clone(),
None,
Default::default(),
))
.fold_with(&mut strip());
Ok(ModuleData { Ok(ModuleData {
fm, fm,
@ -235,6 +555,11 @@ struct Resolver;
impl Resolve for Resolver { impl Resolve for Resolver {
fn resolve(&self, base: &FileName, module_specifier: &str) -> Result<FileName, Error> { 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 { let base_url = match base {
FileName::Custom(v) => v, FileName::Custom(v) => v,
_ => unreachable!("this test only uses url"), _ => 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 { class A {
} }
const A1 = A;
return { return {
A A
}; };
}(); }();
const foo = mod;
export { foo }; export { foo };

View File

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

View File

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

View File

@ -1,12 +1,14 @@
const foo = { const __default = {
"a": "a", "a": "a",
"b": "b", "b": "b",
"c": "c" "c": "c"
}; };
const foo1 = { const foo = __default;
const __default1 = {
"a": "a1", "a": "a1",
"b": "b1", "b": "b1",
"c": "c1" "c": "c1"
}; };
const foo1 = __default1;
console.log(foo); console.log(foo);
console.log(foo1); 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() { 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"); console.log("a");
} }
const a1 = a; const a1 = a;
var O; const a2 = a1;
var O3;
(function(O1) { (function(O1) {
O1[O1["A"] = 0] = "A"; O1[O1["A"] = 0] = "A";
O1[O1["B"] = 1] = "B"; O1[O1["B"] = 1] = "B";
O1[O1["C"] = 2] = "C"; O1[O1["C"] = 2] = "C";
})(O || (O = { })(O3 || (O3 = {
})); }));
const O1 = O; const defaultA = a2;
const defaultA = a1;
export { O1 as O };
class A { class A {
#a; #a;
#c; #c;
constructor(o = { constructor(o = {
}){ }){
const { a: a2 = defaultA , c , } = o; const { a: a3 = defaultA , c , } = o;
this.#a = a2; this.#a = a3;
this.#c = c; this.#c = c;
} }
a() { a() {
@ -28,6 +27,9 @@ class A {
console.log(this.#c); console.log(this.#c);
} }
} }
let a3 = new A(); let a4 = new A();
a3.a(); a4.a();
a3.c(); a4.c();
const O1 = O3;
const O2 = O1;
export { O2 as O };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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