mirror of
https://github.com/swc-project/swc.git
synced 2024-12-18 03:01:48 +03:00
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:
parent
723970db1f
commit
4294b5e7ba
3
.github/workflows/integration.yml
vendored
3
.github/workflows/integration.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
||||
- name: Set platform name
|
||||
run: |
|
||||
export NODE_PLATFORM_NAME=$(node -e "console.log(require('os').platform())")
|
||||
echo "::set-env name=PLATFORM_NAME::$NODE_PLATFORM_NAME"
|
||||
echo "name=PLATFORM_NAME::$NODE_PLATFORM_NAME" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- name: Prepare
|
||||
@ -56,7 +56,6 @@ jobs:
|
||||
|
||||
(cd integration-tests/three-js/build/test && qunit -r failonlyreporter unit/three.source.unit.js)
|
||||
|
||||
|
||||
# terser: contains with statement in test
|
||||
# Rome.js: I forgot the cause, but it didn't work.
|
||||
# jQuery: browser only (window.document is required)
|
||||
|
4
.github/workflows/publish-node.yml
vendored
4
.github/workflows/publish-node.yml
vendored
@ -37,7 +37,7 @@ jobs:
|
||||
- name: Set platform name
|
||||
run: |
|
||||
export NODE_PLATFORM_NAME=$(node -e "console.log(require('os').platform())")
|
||||
echo "::set-env name=PLATFORM_NAME::$NODE_PLATFORM_NAME"
|
||||
echo "name=PLATFORM_NAME::$NODE_PLATFORM_NAME" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- name: Install llvm
|
||||
@ -129,7 +129,7 @@ jobs:
|
||||
node-version: 12
|
||||
|
||||
- name: Set release name
|
||||
run: echo ::set-env name=RELEASE_VERSION::${GITHUB_REF#refs/*/}
|
||||
run: echo name=RELEASE_VERSION::${GITHUB_REF#refs/*/} >> $GITHUB_ENV
|
||||
|
||||
# Do not cache node_modules, or yarn workspace links broken
|
||||
- name: Install dependencies
|
||||
|
@ -8,7 +8,7 @@ edition = "2018"
|
||||
license = "Apache-2.0/MIT"
|
||||
name = "swc_bundler"
|
||||
repository = "https://github.com/swc-project/swc.git"
|
||||
version = "0.17.0"
|
||||
version = "0.17.1"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[features]
|
||||
@ -33,7 +33,7 @@ swc_common = {version = "0.10.0", path = "../common"}
|
||||
swc_ecma_ast = {version = "0.35.0", path = "../ecmascript/ast"}
|
||||
swc_ecma_codegen = {version = "0.41.0", path = "../ecmascript/codegen"}
|
||||
swc_ecma_parser = {version = "0.43.0", path = "../ecmascript/parser"}
|
||||
swc_ecma_transforms = {version = "0.30.0", path = "../ecmascript/transforms"}
|
||||
swc_ecma_transforms = {version = "0.30.1", path = "../ecmascript/transforms"}
|
||||
swc_ecma_utils = {version = "0.25.0", path = "../ecmascript/utils"}
|
||||
swc_ecma_visit = {version = "0.21.0", path = "../ecmascript/visit"}
|
||||
|
||||
@ -41,6 +41,7 @@ swc_ecma_visit = {version = "0.21.0", path = "../ecmascript/visit"}
|
||||
hex = "0.4"
|
||||
reqwest = {version = "0.10.8", features = ["blocking"]}
|
||||
sha-1 = "0.9"
|
||||
swc_ecma_transforms = {version = "0.30.1", path = "../ecmascript/transforms", features = ["react"]}
|
||||
tempfile = "3.1.0"
|
||||
testing = {version = "0.10.0", path = "../testing"}
|
||||
url = "2.1.1"
|
||||
|
5
bundler/scripts/test.sh
Executable file
5
bundler/scripts/test.sh
Executable file
@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eux
|
||||
|
||||
cargo test --test fixture
|
||||
cargo test --test deno $@
|
@ -1,13 +1,16 @@
|
||||
use super::{
|
||||
merge::{ImportDropper, Unexporter},
|
||||
plan::{CircularPlan, Plan},
|
||||
use super::plan::CircularPlan;
|
||||
use crate::{
|
||||
bundler::{
|
||||
chunk::{merge::Ctx, sort::sort},
|
||||
load::TransformedModule,
|
||||
},
|
||||
id::Id,
|
||||
Bundler, Load, ModuleId, Resolve,
|
||||
};
|
||||
use crate::{bundler::load::TransformedModule, util::CHashSet, Bundler, Load, ModuleId, Resolve};
|
||||
use anyhow::{Context, Error};
|
||||
use std::borrow::Borrow;
|
||||
use swc_common::DUMMY_SP;
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_visit::{noop_visit_type, FoldWith, Node, Visit, VisitMutWith, VisitWith};
|
||||
use swc_ecma_utils::find_ids;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@ -20,309 +23,189 @@ where
|
||||
L: Load,
|
||||
R: Resolve,
|
||||
{
|
||||
pub(super) fn merge_circular_modules(
|
||||
pub(super) fn merge_circular(
|
||||
&self,
|
||||
plan: &Plan,
|
||||
circular_plan: &CircularPlan,
|
||||
ctx: &Ctx,
|
||||
plan: &CircularPlan,
|
||||
entry_id: ModuleId,
|
||||
merged: &CHashSet<ModuleId>,
|
||||
) -> Result<Module, Error> {
|
||||
assert!(
|
||||
circular_plan.chunks.len() >= 1,
|
||||
plan.chunks.len() >= 1,
|
||||
"# of circular modules should be 2 or greater than 2 including entry. Got {:?}",
|
||||
circular_plan
|
||||
plan
|
||||
);
|
||||
let entry_module = self.scope.get_module(entry_id).unwrap();
|
||||
let direct_deps = entry_module
|
||||
.imports
|
||||
.specifiers
|
||||
.iter()
|
||||
.map(|v| v.0.module_id)
|
||||
.chain(entry_module.exports.reexports.iter().map(|v| v.0.module_id))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let modules = circular_plan
|
||||
let modules = plan
|
||||
.chunks
|
||||
.iter()
|
||||
.map(|&id| self.scope.get_module(id).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
merged.insert(entry_id);
|
||||
let mut entry = self
|
||||
.merge_modules(plan, entry_id, false, true, merged)
|
||||
.merge_modules(ctx, entry_id, false, false)
|
||||
.context("failed to merge dependency of a cyclic module")?;
|
||||
|
||||
entry.visit_mut_with(&mut ImportDropper {
|
||||
imports: &entry_module.imports,
|
||||
if !ctx.merged.insert(entry_id) {
|
||||
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 {
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
if !merged.insert(dep) {
|
||||
log::debug!("[circular merge] Already merged: {:?}", dep);
|
||||
continue;
|
||||
|
||||
if !ctx.merged.insert(dep) {
|
||||
log::debug!("[circular] skip: {:?}", dep);
|
||||
return false;
|
||||
}
|
||||
|
||||
log::debug!("Circular merge: {:?}", dep);
|
||||
|
||||
let new_module = self.merge_two_circular_modules(plan, &modules, entry, dep, merged)?;
|
||||
true
|
||||
});
|
||||
deps.sort();
|
||||
|
||||
let new_module = self.merge_circular_modules(ctx, &modules, entry, deps)?;
|
||||
|
||||
entry = new_module;
|
||||
|
||||
// print_hygiene(
|
||||
// "[circular] entry:merge_two_circular_modules",
|
||||
// &self.cm,
|
||||
// &entry,
|
||||
// );
|
||||
if !exports.is_empty() {
|
||||
entry
|
||||
.body
|
||||
.push(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
|
||||
NamedExport {
|
||||
span: DUMMY_SP,
|
||||
specifiers: exports,
|
||||
src: None,
|
||||
type_only: false,
|
||||
},
|
||||
)));
|
||||
}
|
||||
|
||||
// print_hygiene("[circular] done", &self.cm, &entry);
|
||||
|
||||
Ok(entry)
|
||||
}
|
||||
|
||||
/// Merges `a` and `b` into one module.
|
||||
fn merge_two_circular_modules(
|
||||
fn merge_circular_modules(
|
||||
&self,
|
||||
plan: &Plan,
|
||||
ctx: &Ctx,
|
||||
_circular_modules: &[TransformedModule],
|
||||
mut entry: Module,
|
||||
dep: ModuleId,
|
||||
merged: &CHashSet<ModuleId>,
|
||||
deps: Vec<ModuleId>,
|
||||
) -> Result<Module, Error> {
|
||||
self.run(|| {
|
||||
let mut dep_body = vec![];
|
||||
|
||||
for dep in deps {
|
||||
let dep_info = self.scope.get_module(dep).unwrap();
|
||||
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")?;
|
||||
|
||||
// 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
|
||||
entry.body = merge_respecting_order(dep.body, entry.body);
|
||||
entry.body = merge_respecting_order(dep_body, entry.body);
|
||||
|
||||
// print_hygiene(
|
||||
// "[circular] END :merge_two_circular_modules",
|
||||
// &self.cm,
|
||||
// &entry,
|
||||
// );
|
||||
Ok(entry)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Originally, this method should create a dependency graph, but
|
||||
fn merge_respecting_order(mut dep: Vec<ModuleItem>, mut entry: Vec<ModuleItem>) -> Vec<ModuleItem> {
|
||||
fn merge_respecting_order(dep: Vec<ModuleItem>, entry: Vec<ModuleItem>) -> Vec<ModuleItem> {
|
||||
let mut new = Vec::with_capacity(dep.len() + entry.len());
|
||||
|
||||
// While looping over items from entry, we check for dependency.
|
||||
loop {
|
||||
if dep.is_empty() {
|
||||
log::trace!("dep is empty");
|
||||
break;
|
||||
}
|
||||
let item = dep.drain(..=0).next().unwrap();
|
||||
|
||||
// Everything from dep is injected
|
||||
if entry.is_empty() {
|
||||
log::trace!("dep is empty");
|
||||
new.push(item);
|
||||
new.append(&mut dep);
|
||||
break;
|
||||
}
|
||||
|
||||
// If the code of entry depends on dependency, we insert dependency source code
|
||||
// at the position.
|
||||
if let Some(pos) = dependency_index(&item, &entry) {
|
||||
log::trace!("Found depndency: {}", pos);
|
||||
|
||||
new.extend(entry.drain(..=pos));
|
||||
new.push(item);
|
||||
continue;
|
||||
}
|
||||
|
||||
// We checked the length of `dep`
|
||||
if let Some(pos) = dependency_index(&entry[0], &[&item]) {
|
||||
log::trace!("Found reverse depndency (index[0]): {}", pos);
|
||||
|
||||
new.push(item);
|
||||
new.extend(entry.drain(..=0));
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(pos) = dependency_index(&entry[0], &dep) {
|
||||
log::trace!("Found reverse depndency: {}", pos);
|
||||
|
||||
new.push(item);
|
||||
new.extend(dep.drain(..=pos));
|
||||
new.extend(entry.drain(..=0));
|
||||
continue;
|
||||
}
|
||||
|
||||
log::trace!("No dependency");
|
||||
|
||||
new.push(item);
|
||||
}
|
||||
|
||||
new.extend(entry);
|
||||
new.extend(dep);
|
||||
|
||||
// Append remaining statements.
|
||||
new.extend(entry);
|
||||
sort(&mut new);
|
||||
|
||||
new
|
||||
}
|
||||
|
||||
/// Searches for top level declaration which provides requirements for `deps`.
|
||||
fn dependency_index<T>(item: &ModuleItem, deps: &[T]) -> Option<usize>
|
||||
where
|
||||
T: Borrow<ModuleItem>,
|
||||
{
|
||||
let mut v = DepFinder {
|
||||
deps,
|
||||
idx: None,
|
||||
last_usage_idx: None,
|
||||
};
|
||||
item.visit_with(&Invalid { span: DUMMY_SP }, &mut v);
|
||||
v.idx.or(v.last_usage_idx)
|
||||
}
|
||||
|
||||
struct DepFinder<'a, T>
|
||||
where
|
||||
T: Borrow<ModuleItem>,
|
||||
{
|
||||
deps: &'a [T],
|
||||
last_usage_idx: Option<usize>,
|
||||
idx: Option<usize>,
|
||||
}
|
||||
|
||||
impl<T> Visit for DepFinder<'_, T>
|
||||
where
|
||||
T: Borrow<ModuleItem>,
|
||||
{
|
||||
noop_visit_type!();
|
||||
|
||||
fn visit_ident(&mut self, i: &Ident, _: &dyn Node) {
|
||||
if self.idx.is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
for (idx, dep) in self.deps.iter().enumerate() {
|
||||
match dep.borrow() {
|
||||
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
|
||||
decl: Decl::Class(decl),
|
||||
..
|
||||
}))
|
||||
| ModuleItem::Stmt(Stmt::Decl(Decl::Class(decl))) => {
|
||||
log::trace!(
|
||||
"Decl (from dep) = {}{:?}, Ident = {}{:?}",
|
||||
decl.ident.sym,
|
||||
decl.ident.span.ctxt,
|
||||
i.sym,
|
||||
i.span.ctxt
|
||||
);
|
||||
if decl.ident.sym == i.sym && decl.ident.span.ctxt == i.span.ctxt {
|
||||
self.idx = Some(idx);
|
||||
log::debug!("Index is {}", idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dep => {
|
||||
if DepUsageFinder::find(i, dep) {
|
||||
self.last_usage_idx = Some(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_member_expr(&mut self, e: &MemberExpr, _: &dyn Node) {
|
||||
e.obj.visit_with(e as _, self);
|
||||
|
||||
if e.computed {
|
||||
e.prop.visit_with(e as _, self)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn visit_import_decl(&mut self, _: &ImportDecl, _: &dyn Node) {}
|
||||
#[inline]
|
||||
fn visit_class_member(&mut self, _: &ClassMember, _: &dyn Node) {}
|
||||
#[inline]
|
||||
fn visit_function(&mut self, _: &Function, _: &dyn Node) {}
|
||||
#[inline]
|
||||
fn visit_arrow_expr(&mut self, _: &ArrowExpr, _: &dyn Node) {}
|
||||
|
||||
/// We only search for top-level binding
|
||||
#[inline]
|
||||
fn visit_stmts(&mut self, _: &[Stmt], _: &dyn Node) {}
|
||||
/// We only search for top-level binding
|
||||
#[inline]
|
||||
fn visit_block_stmt(&mut self, _: &BlockStmt, _: &dyn Node) {}
|
||||
}
|
||||
|
||||
/// Finds usage of `ident`
|
||||
struct DepUsageFinder<'a> {
|
||||
ident: &'a Ident,
|
||||
found: bool,
|
||||
}
|
||||
|
||||
impl<'a> Visit for DepUsageFinder<'a> {
|
||||
noop_visit_type!();
|
||||
|
||||
fn visit_call_expr(&mut self, e: &CallExpr, _: &dyn Node) {
|
||||
if self.found {
|
||||
return;
|
||||
}
|
||||
|
||||
match &e.callee {
|
||||
ExprOrSuper::Super(_) => {}
|
||||
ExprOrSuper::Expr(callee) => match &**callee {
|
||||
Expr::Ident(..) => {}
|
||||
_ => {
|
||||
callee.visit_with(e, self);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
e.args.visit_with(e, self);
|
||||
}
|
||||
|
||||
fn visit_ident(&mut self, i: &Ident, _: &dyn Node) {
|
||||
if self.found {
|
||||
return;
|
||||
}
|
||||
|
||||
if i.span.ctxt() == self.ident.span.ctxt() && i.sym == self.ident.sym {
|
||||
self.found = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_member_expr(&mut self, e: &MemberExpr, _: &dyn Node) {
|
||||
if self.found {
|
||||
return;
|
||||
}
|
||||
|
||||
e.obj.visit_with(e as _, self);
|
||||
|
||||
if e.computed {
|
||||
e.prop.visit_with(e as _, self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DepUsageFinder<'a> {
|
||||
pub fn find<N>(ident: &'a Ident, node: &N) -> bool
|
||||
where
|
||||
N: VisitWith<Self>,
|
||||
{
|
||||
let mut v = DepUsageFinder {
|
||||
ident,
|
||||
found: false,
|
||||
};
|
||||
node.visit_with(&Invalid { span: DUMMY_SP } as _, &mut v);
|
||||
v.found
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
use super::*;
|
||||
use crate::debug::print_hygiene;
|
||||
use swc_common::{sync::Lrc, FileName, SourceMap};
|
||||
use swc_ecma_parser::{lexer::Lexer, JscTarget, Parser, StringInput, Syntax};
|
||||
use swc_ecma_utils::drop_span;
|
||||
use testing::assert_eq;
|
||||
|
||||
fn parse(cm: Lrc<SourceMap>, name: &str, src: &str) -> Module {
|
||||
let fm = cm.new_source_file(FileName::Custom(name.into()), src.into());
|
||||
@ -21,29 +21,24 @@ fn parse(cm: Lrc<SourceMap>, name: &str, src: &str) -> Module {
|
||||
|
||||
#[track_caller]
|
||||
fn assert_merge_respecting_order(modules: &[&str], output: &str) {
|
||||
for i in 0..modules.len() {
|
||||
log::info!("[{}] Testing", i);
|
||||
::testing::run_test2(false, |cm, _handler| {
|
||||
let mut entry = parse(cm.clone(), &format!("entry-{}", i), modules[i]).body;
|
||||
let mut entry = parse(cm.clone(), &format!("entry"), modules[0]);
|
||||
|
||||
for j in 0..modules.len() {
|
||||
if i == j {
|
||||
continue;
|
||||
}
|
||||
for i in 1..modules.len() {
|
||||
let dep = parse(cm.clone(), &format!("deps-{}", i), modules[i]);
|
||||
entry.body = merge_respecting_order(entry.body, dep.body);
|
||||
|
||||
let dep = parse(cm.clone(), &format!("deps-{}-{}", i, j), modules[j]);
|
||||
entry = merge_respecting_order(entry, dep.body);
|
||||
print_hygiene("merge", &cm, &entry);
|
||||
}
|
||||
|
||||
let output = parse(cm.clone(), "output", output);
|
||||
assert_eq!(entry, output.body, "[{}]", i);
|
||||
|
||||
log::info!("[{}] Success", i);
|
||||
if entry.body != output.body {
|
||||
panic!()
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -57,27 +52,59 @@ fn simple_two() {
|
||||
);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_dependency_index(entry: &str, dep: &str, expected: usize) {
|
||||
::testing::run_test2(false, |cm, _handler| {
|
||||
let entry = parse(cm.clone(), "entry", entry);
|
||||
let dep = parse(cm.clone(), "dep", dep);
|
||||
#[test]
|
||||
fn many_vars_1() {
|
||||
assert_merge_respecting_order(
|
||||
&[
|
||||
"
|
||||
const A6 = A5;
|
||||
class B6 extends A6 {
|
||||
}
|
||||
const B7 = B6;
|
||||
|
||||
let calculated = dependency_index(&entry.body[0], &dep.body);
|
||||
|
||||
assert_eq!(calculated, Some(expected));
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
",
|
||||
"
|
||||
const B4 = B7;
|
||||
class A4 {
|
||||
method() {
|
||||
return new B4();
|
||||
}
|
||||
}
|
||||
const A5 = A4;
|
||||
",
|
||||
],
|
||||
"
|
||||
class A4 {
|
||||
method() {
|
||||
return new B4();
|
||||
}
|
||||
}
|
||||
const A5 = A4;
|
||||
const A6 = A5;
|
||||
class B6 extends A6 {
|
||||
}
|
||||
const B7 = B6;
|
||||
const B4 = B7;
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dep_index_class() {
|
||||
assert_dependency_index("class A extends B {}", "class B {}", 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dep_index_export_class() {
|
||||
assert_dependency_index("class A extends B {}", "export class B {}", 0);
|
||||
fn no_dep_first_01() {
|
||||
assert_merge_respecting_order(
|
||||
&[
|
||||
"
|
||||
const b1 = b2;
|
||||
",
|
||||
"
|
||||
const b2 = 2;
|
||||
const b3 = b1;
|
||||
",
|
||||
],
|
||||
"
|
||||
const b2 = 2;
|
||||
const b1 = b2;
|
||||
const b3 = b1;
|
||||
",
|
||||
);
|
||||
}
|
||||
|
@ -1,10 +1,17 @@
|
||||
use super::{merge::Unexporter, plan::Plan};
|
||||
use crate::{bundler::load::TransformedModule, Bundler, Load, ModuleId, Resolve};
|
||||
use super::merge::Unexporter;
|
||||
use crate::{
|
||||
bundler::{
|
||||
chunk::{merge::Ctx, plan::Dependancy},
|
||||
load::TransformedModule,
|
||||
},
|
||||
Bundler, Load, Resolve,
|
||||
};
|
||||
use anyhow::Error;
|
||||
use std::{borrow::Cow, sync::atomic::Ordering};
|
||||
use swc_common::{Mark, SyntaxContext, DUMMY_SP};
|
||||
use swc_atoms::js_word;
|
||||
use swc_common::{SyntaxContext, DUMMY_SP};
|
||||
use swc_ecma_ast::{ModuleItem, *};
|
||||
use swc_ecma_utils::{prepend, undefined, ExprFactory};
|
||||
use swc_ecma_utils::{prepend, quote_ident, undefined, ExprFactory};
|
||||
use swc_ecma_visit::{noop_visit_mut_type, FoldWith, VisitMut, VisitMutWith};
|
||||
|
||||
impl<L, R> Bundler<'_, L, R>
|
||||
@ -35,26 +42,30 @@ where
|
||||
/// modules.
|
||||
pub(super) fn merge_cjs(
|
||||
&self,
|
||||
plan: &Plan,
|
||||
ctx: &Ctx,
|
||||
is_entry: bool,
|
||||
entry: &mut Module,
|
||||
info: &TransformedModule,
|
||||
dep: Cow<Module>,
|
||||
dep_info: &TransformedModule,
|
||||
targets: &mut Vec<ModuleId>,
|
||||
targets: &mut Vec<Dependancy>,
|
||||
) -> Result<(), Error> {
|
||||
if info.is_es6 && dep_info.is_es6 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
log::debug!("Merging as a common js module: {}", info.fm.name);
|
||||
// If src is none, all requires are transpiled
|
||||
let mut v = RequireReplacer {
|
||||
is_entry,
|
||||
ctxt: dep_info.ctxt(),
|
||||
load_var: Ident::new("load".into(), DUMMY_SP.with_ctxt(dep_info.ctxt())),
|
||||
ctxt: dep_info.export_ctxt(),
|
||||
load_var: Ident::new("load".into(), DUMMY_SP.with_ctxt(dep_info.export_ctxt())),
|
||||
replaced: false,
|
||||
};
|
||||
entry.body.visit_mut_with(&mut v);
|
||||
|
||||
if v.replaced {
|
||||
if let Some(idx) = targets.iter().position(|v| *v == dep_info.id) {
|
||||
if let Some(idx) = targets.iter().position(|v| v.id == dep_info.id) {
|
||||
targets.remove(idx);
|
||||
}
|
||||
|
||||
@ -65,12 +76,15 @@ where
|
||||
|
||||
let mut dep = dep.into_owned().fold_with(&mut Unexporter);
|
||||
dep.visit_mut_with(&mut ImportDropper);
|
||||
dep.visit_mut_with(&mut DefaultHandler {
|
||||
local_ctxt: dep_info.local_ctxt(),
|
||||
});
|
||||
|
||||
prepend(
|
||||
&mut entry.body,
|
||||
ModuleItem::Stmt(wrap_module(
|
||||
SyntaxContext::empty(),
|
||||
dep_info.mark(),
|
||||
dep_info.local_ctxt(),
|
||||
load_var,
|
||||
dep,
|
||||
)),
|
||||
@ -79,15 +93,15 @@ where
|
||||
log::warn!("Injecting load");
|
||||
}
|
||||
|
||||
if let Some(normal_plan) = plan.normal.get(&dep_info.id) {
|
||||
for &dep_id in &normal_plan.chunks {
|
||||
if !targets.contains(&dep_id) {
|
||||
if let Some(normal_plan) = ctx.plan.normal.get(&dep_info.id) {
|
||||
for dep in normal_plan.chunks.iter() {
|
||||
if !targets.contains(&dep) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let dep_info = self.scope.get_module(dep_id).unwrap();
|
||||
let dep_info = self.scope.get_module(dep.id).unwrap();
|
||||
self.merge_cjs(
|
||||
plan,
|
||||
ctx,
|
||||
false,
|
||||
entry,
|
||||
info,
|
||||
@ -105,7 +119,7 @@ where
|
||||
|
||||
fn wrap_module(
|
||||
helper_ctxt: SyntaxContext,
|
||||
top_level_mark: Mark,
|
||||
local_ctxt: SyntaxContext,
|
||||
load_var: Ident,
|
||||
dep: Module,
|
||||
) -> Stmt {
|
||||
@ -118,19 +132,13 @@ fn wrap_module(
|
||||
Param {
|
||||
span: DUMMY_SP,
|
||||
decorators: Default::default(),
|
||||
pat: Pat::Ident(Ident::new(
|
||||
"module".into(),
|
||||
DUMMY_SP.apply_mark(top_level_mark),
|
||||
)),
|
||||
pat: Pat::Ident(Ident::new("module".into(), DUMMY_SP.with_ctxt(local_ctxt))),
|
||||
},
|
||||
// exports
|
||||
Param {
|
||||
span: DUMMY_SP,
|
||||
decorators: Default::default(),
|
||||
pat: Pat::Ident(Ident::new(
|
||||
"exports".into(),
|
||||
DUMMY_SP.apply_mark(top_level_mark),
|
||||
)),
|
||||
pat: Pat::Ident(Ident::new("exports".into(), DUMMY_SP.with_ctxt(local_ctxt))),
|
||||
},
|
||||
],
|
||||
decorators: vec![],
|
||||
@ -344,3 +352,41 @@ impl VisitMut for ImportDropper {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DefaultHandler {
|
||||
local_ctxt: SyntaxContext,
|
||||
}
|
||||
|
||||
impl VisitMut for DefaultHandler {
|
||||
noop_visit_mut_type!();
|
||||
|
||||
fn visit_mut_expr(&mut self, e: &mut Expr) {
|
||||
e.visit_mut_children_with(self);
|
||||
|
||||
match e {
|
||||
Expr::Ident(i) => {
|
||||
if i.sym == js_word!("default") {
|
||||
*e = Expr::Member(MemberExpr {
|
||||
span: i.span,
|
||||
obj: ExprOrSuper::Expr(Box::new(Expr::Ident(Ident::new(
|
||||
"module".into(),
|
||||
DUMMY_SP.with_ctxt(self.local_ctxt),
|
||||
)))),
|
||||
prop: Box::new(Expr::Ident(quote_ident!("exports"))),
|
||||
computed: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_mut_member_expr(&mut self, e: &mut MemberExpr) {
|
||||
e.obj.visit_mut_with(self);
|
||||
|
||||
if e.computed {
|
||||
e.prop.visit_mut_with(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
use super::plan::Plan;
|
||||
use crate::{bundler::load::TransformedModule, util::CHashSet, Bundler, Load, ModuleId, Resolve};
|
||||
use anyhow::Error;
|
||||
use crate::{bundler::chunk::merge::Ctx, Bundler, Load, ModuleId, Resolve};
|
||||
use anyhow::{bail, Error};
|
||||
use std::mem::take;
|
||||
use swc_atoms::js_word;
|
||||
use swc_common::DUMMY_SP;
|
||||
@ -29,15 +28,17 @@ where
|
||||
/// };
|
||||
/// })();
|
||||
/// ```
|
||||
pub(super) fn wrap_esm_as_a_var(
|
||||
pub(super) fn wrap_esm(
|
||||
&self,
|
||||
plan: &Plan,
|
||||
ctx: &Ctx,
|
||||
id: ModuleId,
|
||||
module: Module,
|
||||
info: &TransformedModule,
|
||||
merged: &CHashSet<ModuleId>,
|
||||
id: Ident,
|
||||
) -> Result<Module, Error> {
|
||||
let span = module.span;
|
||||
let var_name = match self.scope.wrapped_esm_id(id) {
|
||||
Some(v) => v,
|
||||
None => bail!("{:?} should not be wrapped with a function", id),
|
||||
};
|
||||
|
||||
let mut module_items = vec![];
|
||||
|
||||
@ -48,6 +49,13 @@ where
|
||||
.into_iter()
|
||||
.filter_map(|v| match v {
|
||||
ModuleItem::Stmt(s) => Some(s),
|
||||
ModuleItem::ModuleDecl(ModuleDecl::ExportAll(ref export)) => {
|
||||
// We handle this later.
|
||||
let mut map = ctx.export_stars_in_wrapped.lock();
|
||||
map.entry(id).or_default().push(export.span.ctxt);
|
||||
module_items.push(v);
|
||||
None
|
||||
}
|
||||
_ => {
|
||||
module_items.push(v);
|
||||
None
|
||||
@ -87,31 +95,18 @@ where
|
||||
decls: vec![VarDeclarator {
|
||||
span: DUMMY_SP,
|
||||
definite: false,
|
||||
name: Pat::Ident(id.clone()),
|
||||
name: Pat::Ident(var_name.into_ident()),
|
||||
init: Some(Box::new(module_expr)),
|
||||
}],
|
||||
};
|
||||
|
||||
module_items.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(var_decl))));
|
||||
|
||||
let module = Module {
|
||||
Ok(Module {
|
||||
span: DUMMY_SP,
|
||||
shebang: None,
|
||||
body: module_items,
|
||||
};
|
||||
|
||||
let module_plan;
|
||||
let module_plan = match plan.normal.get(&info.id) {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
module_plan = Default::default();
|
||||
&module_plan
|
||||
}
|
||||
};
|
||||
let module = self.merge_imports(&plan, module_plan, module, info, merged, false)?;
|
||||
// print_hygiene("Imports", &self.cm, &module);
|
||||
|
||||
Ok(module)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -206,11 +201,30 @@ impl Fold for ExportToReturn {
|
||||
DefaultDecl::TsInterfaceDecl(_) => None,
|
||||
},
|
||||
ModuleDecl::ExportDefaultExpr(_) => None,
|
||||
ModuleDecl::ExportAll(_) => {
|
||||
unimplemented!("export * from 'foo' inside a module loaded with computed key")
|
||||
ModuleDecl::ExportAll(export) => {
|
||||
return ModuleItem::ModuleDecl(ModuleDecl::ExportAll(export))
|
||||
}
|
||||
ModuleDecl::ExportNamed(_) => {
|
||||
unimplemented!("export {{ a }} from 'foo' inside a module loaded with computed key")
|
||||
ModuleDecl::ExportNamed(named) => {
|
||||
for specifier in &named.specifiers {
|
||||
match specifier {
|
||||
ExportSpecifier::Namespace(_) => {}
|
||||
ExportSpecifier::Default(_) => {}
|
||||
ExportSpecifier::Named(named) => match &named.exported {
|
||||
Some(exported) => {
|
||||
self.exports
|
||||
.push(PropOrSpread::Prop(Box::new(Prop::KeyValue(
|
||||
KeyValueProp {
|
||||
key: PropName::Ident(exported.clone()),
|
||||
value: Box::new(Expr::Ident(named.orig.clone())),
|
||||
},
|
||||
))))
|
||||
}
|
||||
None => {}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(named));
|
||||
}
|
||||
ModuleDecl::TsImportEquals(_) => None,
|
||||
ModuleDecl::TsExportAssignment(_) => None,
|
||||
@ -232,7 +246,6 @@ impl Fold for ExportToReturn {
|
||||
_ => true,
|
||||
});
|
||||
|
||||
if !self.exports.is_empty() {
|
||||
new.push(ModuleItem::Stmt(Stmt::Return(ReturnStmt {
|
||||
span: DUMMY_SP,
|
||||
arg: Some(Box::new(Expr::Object(ObjectLit {
|
||||
@ -240,7 +253,6 @@ impl Fold for ExportToReturn {
|
||||
props: take(&mut self.exports),
|
||||
}))),
|
||||
})));
|
||||
}
|
||||
|
||||
new
|
||||
}
|
||||
|
@ -1,19 +1,18 @@
|
||||
use super::plan::{NormalPlan, Plan};
|
||||
use crate::{
|
||||
bundler::load::{Source, Specifier, TransformedModule},
|
||||
util::{CHashSet, IntoParallelIterator},
|
||||
bundler::{
|
||||
chunk::merge::Ctx,
|
||||
load::{Source, Specifier, TransformedModule},
|
||||
},
|
||||
util::{ExprExt, MapWithMut, VarDeclaratorExt},
|
||||
Bundler, Load, ModuleId, Resolve,
|
||||
};
|
||||
use anyhow::{Context, Error};
|
||||
#[cfg(feature = "concurrent")]
|
||||
use rayon::iter::ParallelIterator;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
mem::{replace, take},
|
||||
};
|
||||
use std::mem::{replace, take};
|
||||
use swc_common::{Spanned, SyntaxContext, DUMMY_SP};
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_utils::{ident::IdentLike, Id};
|
||||
use swc_ecma_utils::{find_ids, ident::IdentLike, Id};
|
||||
use swc_ecma_visit::{noop_fold_type, noop_visit_mut_type, Fold, FoldWith, VisitMut, VisitMutWith};
|
||||
|
||||
impl<L, R> Bundler<'_, L, R>
|
||||
@ -52,149 +51,99 @@ where
|
||||
///
|
||||
/// console.log(bar, baz);
|
||||
/// ```
|
||||
pub(super) fn merge_reexports(
|
||||
pub(super) fn merge_export(
|
||||
&self,
|
||||
plan: &Plan,
|
||||
nomral_plan: &NormalPlan,
|
||||
entry: &mut Module,
|
||||
info: &TransformedModule,
|
||||
merged: &CHashSet<ModuleId>,
|
||||
) -> Result<(), Error> {
|
||||
log::trace!("merge_reexports: {}", info.fm.name);
|
||||
|
||||
// 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);
|
||||
|
||||
ctx: &Ctx,
|
||||
dep_id: ModuleId,
|
||||
specifiers: &[Specifier],
|
||||
) -> Result<Module, Error> {
|
||||
self.run(|| {
|
||||
log::debug!("Reexporting {:?}", dep_id);
|
||||
let dep_info = self.scope.get_module(dep_id).unwrap();
|
||||
let mut dep = self
|
||||
.merge_modules(plan, src.module_id, false, false, merged)
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"failed to merge for reexport: ({}):{} <= ({}):{}",
|
||||
info.id, info.fm.name, src.module_id, src.src.value
|
||||
)
|
||||
})?;
|
||||
.merge_modules(ctx, dep_id, false, true)
|
||||
.context("failed to get module for merging")?;
|
||||
|
||||
// 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 {
|
||||
Specifier::Namespace { local, all: true } => Some(Ident::new(
|
||||
local.sym().clone(),
|
||||
DUMMY_SP.with_ctxt(info.ctxt()),
|
||||
)),
|
||||
_ => None,
|
||||
});
|
||||
self.handle_reexport(&dep_info, &mut dep);
|
||||
|
||||
if let Some(id) = id_of_export_namespace_from {
|
||||
dep = self.wrap_esm_as_a_var(plan, dep, &imported, merged, id)?;
|
||||
} else {
|
||||
dep = self.remark_exports(dep, src.ctxt, None, false);
|
||||
// print_hygiene(&format!("dep: handle reexport"), &self.cm, &dep);
|
||||
|
||||
// for stmt in &mut dep.body {
|
||||
// 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() {
|
||||
dep.visit_mut_with(&mut UnexportAsVar {
|
||||
dep_ctxt: src.ctxt,
|
||||
_entry_ctxt: info.ctxt(),
|
||||
_exports: &specifiers,
|
||||
dep_export_ctxt: dep_info.export_ctxt(),
|
||||
_specifiers: &specifiers,
|
||||
});
|
||||
|
||||
// print_hygiene(&format!("dep: unexport as var"), &self.cm, &dep);
|
||||
@ -206,164 +155,92 @@ where
|
||||
// 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.
|
||||
let is_indirect = !nomral_plan.chunks.contains(&src.module_id);
|
||||
/// # ExportDecl
|
||||
///
|
||||
/// For exported declarations, We should inject named exports.
|
||||
///
|
||||
/// ```ts
|
||||
/// export const b__9 = 1;
|
||||
/// console.log(b__9);
|
||||
/// ```
|
||||
///
|
||||
/// ```ts
|
||||
/// const b__9 = 1;
|
||||
/// export { b__9 as b__10 };
|
||||
/// console.log(b__9);
|
||||
/// ```
|
||||
fn handle_reexport(&self, info: &TransformedModule, module: &mut Module) {
|
||||
let mut new_body = Vec::with_capacity(module.body.len() + 20);
|
||||
|
||||
let add_to = if specifiers.is_empty() && is_indirect {
|
||||
// User provided code like `export * from './foo';`, but planner decide to merge
|
||||
// it within dependency module. So we reexport them using a named export.
|
||||
&mut star_reexports
|
||||
} else {
|
||||
&mut normal_reexports
|
||||
};
|
||||
for stmt in &mut module.body {
|
||||
let mut vars = vec![];
|
||||
let mut stmt = stmt.take();
|
||||
|
||||
for specifier in specifiers {
|
||||
let (imported, exported) = match specifier {
|
||||
Specifier::Specific { local, alias } => {
|
||||
let alias = alias.unwrap_or_else(|| local.clone());
|
||||
let local = local.replace_mark(info.mark());
|
||||
(local.into_ident(), alias.into_ident())
|
||||
match &mut stmt {
|
||||
ModuleItem::ModuleDecl(ModuleDecl::Import(import)) => {
|
||||
for specifier in &import.specifiers {
|
||||
match specifier {
|
||||
ImportSpecifier::Named(named) => match &named.imported {
|
||||
Some(imported) => {
|
||||
vars.push(imported.clone().assign_to(named.local.clone()));
|
||||
}
|
||||
Specifier::Namespace { .. } => continue,
|
||||
};
|
||||
|
||||
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,
|
||||
None => {}
|
||||
},
|
||||
)));
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
import.specifiers.clear();
|
||||
}
|
||||
|
||||
for dep in deps {
|
||||
let dep = dep?;
|
||||
let dep = match dep {
|
||||
Some(v) => v,
|
||||
None => continue,
|
||||
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export)) => {
|
||||
match &export.decl {
|
||||
Decl::Class(ClassDecl { ident, .. }) | Decl::Fn(FnDecl { ident, .. }) => {
|
||||
let mut exported = ident.clone();
|
||||
exported.span.ctxt = info.export_ctxt();
|
||||
|
||||
vars.push(ident.clone().assign_to(exported));
|
||||
}
|
||||
Decl::Var(var) => {
|
||||
//
|
||||
let ids: Vec<Ident> = find_ids(&var.decls);
|
||||
|
||||
vars.extend(
|
||||
ids.into_iter()
|
||||
.map(|i| {
|
||||
let mut exported = i.clone();
|
||||
exported.span.ctxt = info.export_ctxt();
|
||||
|
||||
i.assign_to(exported)
|
||||
})
|
||||
.map(From::from),
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
let (src, dep) = dep;
|
||||
}
|
||||
|
||||
// print_hygiene(
|
||||
// &format!(
|
||||
// "entry: before reexport injection {:?} <- {:?}",
|
||||
// info.ctxt(),
|
||||
// src.ctxt,
|
||||
// ),
|
||||
// &self.cm,
|
||||
// &entry,
|
||||
// );
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Replace import statement / require with module body
|
||||
let mut injector = ExportInjector {
|
||||
imported: dep.body,
|
||||
source: src.clone(),
|
||||
};
|
||||
entry.body.visit_mut_with(&mut injector);
|
||||
|
||||
// Inject variables
|
||||
if let Some(decls) = decls_for_reexport.remove(&src.module_id) {
|
||||
if !decls.is_empty() {
|
||||
entry
|
||||
.body
|
||||
.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(VarDecl {
|
||||
span: DUMMY_SP,
|
||||
kind: VarDeclKind::Const,
|
||||
declare: false,
|
||||
decls,
|
||||
}))));
|
||||
new_body.push(stmt);
|
||||
for var in vars {
|
||||
new_body.push(var.into_module_item("from_export_rs"))
|
||||
}
|
||||
}
|
||||
|
||||
// print_hygiene(
|
||||
// &format!(
|
||||
// "entry:reexport injection {:?} <- {:?}",
|
||||
// info.ctxt(),
|
||||
// src.ctxt,
|
||||
// ),
|
||||
// &self.cm,
|
||||
// &entry,
|
||||
// );
|
||||
assert_eq!(injector.imported, vec![]);
|
||||
}
|
||||
|
||||
let decls_for_reexport: Vec<_> = decls_for_reexport
|
||||
.into_iter()
|
||||
.map(|(_, decls)| decls)
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
if !decls_for_reexport.is_empty() {
|
||||
entry
|
||||
.body
|
||||
.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(VarDecl {
|
||||
span: DUMMY_SP,
|
||||
kind: VarDeclKind::Const,
|
||||
declare: false,
|
||||
decls: decls_for_reexport,
|
||||
}))));
|
||||
}
|
||||
Ok(())
|
||||
module.body = new_body;
|
||||
}
|
||||
}
|
||||
|
||||
struct ExportInjector {
|
||||
imported: Vec<ModuleItem>,
|
||||
source: Source,
|
||||
pub(super) struct ExportInjector {
|
||||
pub imported: Vec<ModuleItem>,
|
||||
pub source: Source,
|
||||
}
|
||||
|
||||
impl VisitMut for ExportInjector {
|
||||
@ -400,7 +277,7 @@ impl VisitMut for ExportInjector {
|
||||
..
|
||||
}) => {
|
||||
let mut imported = imported.clone();
|
||||
imported.span = imported.span.with_ctxt(self.source.ctxt);
|
||||
imported.span = imported.span.with_ctxt(self.source.export_ctxt);
|
||||
|
||||
Some(VarDeclarator {
|
||||
span: DUMMY_SP,
|
||||
@ -413,13 +290,8 @@ impl VisitMut for ExportInjector {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !decls.is_empty() {
|
||||
buf.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(VarDecl {
|
||||
span: DUMMY_SP,
|
||||
kind: VarDeclKind::Const,
|
||||
declare: false,
|
||||
decls,
|
||||
}))));
|
||||
for var in decls {
|
||||
buf.push(var.into_module_item("ExportInjector"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -492,12 +364,10 @@ impl VisitMut for ExportInjector {
|
||||
/// export { foo#7 as foo#5 } where #5 is mark of current entry.
|
||||
struct UnexportAsVar<'a> {
|
||||
/// Syntax context for the generated variables.
|
||||
dep_ctxt: SyntaxContext,
|
||||
|
||||
_entry_ctxt: SyntaxContext,
|
||||
dep_export_ctxt: SyntaxContext,
|
||||
|
||||
/// Exports to preserve
|
||||
_exports: &'a [Specifier],
|
||||
_specifiers: &'a [Specifier],
|
||||
}
|
||||
|
||||
impl VisitMut for UnexportAsVar<'_> {
|
||||
@ -513,20 +383,16 @@ impl VisitMut for UnexportAsVar<'_> {
|
||||
Box::new(Expr::Invalid(Invalid { span: DUMMY_SP })),
|
||||
);
|
||||
|
||||
*n = ModuleItem::Stmt(Stmt::Decl(Decl::Var(VarDecl {
|
||||
span: export.span,
|
||||
kind: VarDeclKind::Const,
|
||||
declare: false,
|
||||
decls: vec![VarDeclarator {
|
||||
*n = VarDeclarator {
|
||||
span: DUMMY_SP,
|
||||
name: Pat::Ident(Ident::new(
|
||||
"__default".into(),
|
||||
expr.span().with_ctxt(self.dep_ctxt),
|
||||
expr.span().with_ctxt(self.dep_export_ctxt),
|
||||
)),
|
||||
init: Some(expr),
|
||||
definite: false,
|
||||
}],
|
||||
})));
|
||||
}
|
||||
.into_module_item("UnexportAsVar");
|
||||
}
|
||||
ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
|
||||
ref export @ NamedExport { src: None, .. },
|
||||
@ -554,18 +420,24 @@ impl VisitMut for UnexportAsVar<'_> {
|
||||
}
|
||||
}
|
||||
None => {
|
||||
log::trace!("Alias: {:?} -> {:?}", n.orig, self.dep_ctxt);
|
||||
if n.orig.span.ctxt != self.dep_export_ctxt {
|
||||
log::trace!(
|
||||
"Alias: {:?} -> {:?}",
|
||||
n.orig,
|
||||
self.dep_export_ctxt
|
||||
);
|
||||
|
||||
decls.push(VarDeclarator {
|
||||
span: n.span,
|
||||
name: Pat::Ident(Ident::new(
|
||||
n.orig.sym.clone(),
|
||||
n.orig.span.with_ctxt(self.dep_ctxt),
|
||||
n.orig.span.with_ctxt(self.dep_export_ctxt),
|
||||
)),
|
||||
init: Some(Box::new(Expr::Ident(n.orig.clone()))),
|
||||
definite: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,8 @@
|
||||
use super::{load::TransformedModule, Bundler};
|
||||
use crate::{id::ModuleId, load::Load, resolve::Resolve, util::IntoParallelIterator, Bundle};
|
||||
use crate::{
|
||||
bundler::chunk::merge::Ctx, id::ModuleId, load::Load, resolve::Resolve,
|
||||
util::IntoParallelIterator, Bundle,
|
||||
};
|
||||
use anyhow::{Context, Error};
|
||||
#[cfg(feature = "rayon")]
|
||||
use rayon::iter::ParallelIterator;
|
||||
@ -11,7 +14,7 @@ mod computed_key;
|
||||
mod export;
|
||||
mod merge;
|
||||
mod plan;
|
||||
mod remark;
|
||||
mod sort;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct InternalEntry {
|
||||
@ -43,13 +46,19 @@ where
|
||||
entries: HashMap<String, TransformedModule>,
|
||||
) -> Result<Vec<Bundle>, Error> {
|
||||
let plan = self.determine_entries(entries).context("failed to plan")?;
|
||||
let merged = Default::default();
|
||||
let ctx = Ctx {
|
||||
plan,
|
||||
merged: Default::default(),
|
||||
transitive_remap: Default::default(),
|
||||
export_stars_in_wrapped: Default::default(),
|
||||
};
|
||||
|
||||
Ok((&*plan.entries)
|
||||
Ok((&*ctx.plan.entries)
|
||||
.into_par_iter()
|
||||
.map(|&entry| {
|
||||
self.run(|| {
|
||||
let kind = plan
|
||||
let kind = ctx
|
||||
.plan
|
||||
.bundle_kinds
|
||||
.get(&entry)
|
||||
.unwrap_or_else(|| {
|
||||
@ -58,7 +67,7 @@ where
|
||||
.clone();
|
||||
|
||||
let module = self
|
||||
.merge_modules(&plan, entry, true, false, &merged)
|
||||
.merge_modules(&ctx, entry, true, true)
|
||||
.context("failed to merge module")
|
||||
.unwrap(); // TODO
|
||||
|
||||
|
@ -107,29 +107,29 @@ pub(super) struct Plan {
|
||||
pub bundle_kinds: HashMap<ModuleId, BundleKind>,
|
||||
}
|
||||
|
||||
impl Plan {
|
||||
pub fn entry_as_circular(&self, entry: ModuleId) -> Option<&CircularPlan> {
|
||||
let plan = self.circular.get(&entry)?;
|
||||
if plan.chunks.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(plan)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(super) struct NormalPlan {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(super) enum DepType {
|
||||
/// Direct dependencies
|
||||
pub chunks: Vec<ModuleId>,
|
||||
|
||||
Direct,
|
||||
/// Used to handle
|
||||
///
|
||||
/// - a -> b
|
||||
/// - a -> c
|
||||
/// - b -> d
|
||||
/// - c -> d
|
||||
pub transitive_chunks: Vec<ModuleId>,
|
||||
Transitive,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(super) struct Dependancy {
|
||||
pub ty: DepType,
|
||||
pub id: ModuleId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub(super) struct NormalPlan {
|
||||
pub chunks: Vec<Dependancy>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@ -150,17 +150,10 @@ where
|
||||
entries: HashMap<String, TransformedModule>,
|
||||
) -> Result<Plan, Error> {
|
||||
let plan = self.calculate_plan(entries)?;
|
||||
let plan = self.handle_duplicates(plan);
|
||||
|
||||
Ok(plan)
|
||||
}
|
||||
|
||||
/// 1. For entry -> a -> b -> a, entry -> a->c, entry -> b -> c,
|
||||
/// we change c as transitive dependancy of entry.
|
||||
fn handle_duplicates(&self, plan: Plan) -> Plan {
|
||||
plan
|
||||
}
|
||||
|
||||
fn calculate_plan(&self, entries: HashMap<String, TransformedModule>) -> Result<Plan, Error> {
|
||||
let mut builder = PlanBuilder::default();
|
||||
|
||||
@ -211,7 +204,7 @@ where
|
||||
Ok(self.build_plan(&metadata, builder))
|
||||
}
|
||||
|
||||
fn build_plan(&self, metadata: &HashMap<ModuleId, Metadata>, builder: PlanBuilder) -> Plan {
|
||||
fn build_plan(&self, _metadata: &HashMap<ModuleId, Metadata>, builder: PlanBuilder) -> Plan {
|
||||
let mut plans = Plan::default();
|
||||
|
||||
for (id, kind) in builder.kinds.iter() {
|
||||
@ -233,6 +226,14 @@ where
|
||||
.collect();
|
||||
deps.sort();
|
||||
|
||||
let should_be_reexport = deps.iter().any(|&dep| {
|
||||
builder
|
||||
.all_deps
|
||||
.get(&(entry, dep))
|
||||
.copied()
|
||||
.unwrap_or(false)
|
||||
});
|
||||
|
||||
for &dep in &deps {
|
||||
if let Some(circular_members) = builder.circular.get(entry) {
|
||||
if circular_members.contains(&dep) {
|
||||
@ -243,7 +244,15 @@ where
|
||||
);
|
||||
if entry != root_entry && dep != root_entry {
|
||||
// done.insert(dep);
|
||||
plans.normal.entry(entry).or_default().chunks.push(dep);
|
||||
plans
|
||||
.normal
|
||||
.entry(entry)
|
||||
.or_default()
|
||||
.chunks
|
||||
.push(Dependancy {
|
||||
id: dep,
|
||||
ty: DepType::Direct,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@ -260,7 +269,6 @@ where
|
||||
.collect::<Vec<_>>();
|
||||
dependants.sort();
|
||||
|
||||
if metadata.get(&dep).map(|md| md.bundle_cnt).unwrap_or(0) == 1 {
|
||||
log::debug!("{:?} depends on {:?}", entry, dep);
|
||||
|
||||
let is_reexport = builder
|
||||
@ -282,7 +290,15 @@ where
|
||||
// b <- c
|
||||
//
|
||||
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;
|
||||
}
|
||||
|
||||
@ -300,16 +316,22 @@ where
|
||||
let normal_plan = plans.normal.entry(module).or_default();
|
||||
|
||||
for &dep in &deps {
|
||||
if !normal_plan.chunks.contains(&dep)
|
||||
&& !normal_plan.transitive_chunks.contains(&dep)
|
||||
{
|
||||
let contains = normal_plan.chunks.iter().any(|d| d.id == dep);
|
||||
|
||||
if !contains {
|
||||
if dependants.contains(&module) {
|
||||
log::trace!("Normal: {:?} => {:?}", module, dep);
|
||||
log::trace!("Normal (non-es6): {:?} => {:?}", module, dep);
|
||||
// `entry` depends on `module` directly
|
||||
normal_plan.chunks.push(dep);
|
||||
normal_plan.chunks.push(Dependancy {
|
||||
id: dep,
|
||||
ty: DepType::Direct,
|
||||
});
|
||||
} else {
|
||||
log::trace!("Transitive: {:?} => {:?}", module, dep);
|
||||
normal_plan.transitive_chunks.push(dep);
|
||||
log::trace!("Transitive (non-es6): {:?} => {:?}", module, dep);
|
||||
normal_plan.chunks.push(Dependancy {
|
||||
id: dep,
|
||||
ty: DepType::Transitive,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -319,11 +341,18 @@ where
|
||||
|
||||
if is_reexport {
|
||||
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);
|
||||
|
||||
log::trace!("Normal: {:?} => {:?}", entry, dep);
|
||||
normal_plan.chunks.push(dep);
|
||||
normal_plan.chunks.push(Dependancy {
|
||||
id: dep,
|
||||
ty: DepType::Direct,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@ -332,16 +361,14 @@ where
|
||||
// Should be merged as a transitive dependency.
|
||||
let higher_module = if plans.entries.contains(&dependants[0]) {
|
||||
dependants[0]
|
||||
} else if dependants.len() == 2
|
||||
&& plans.entries.contains(&dependants[1])
|
||||
{
|
||||
} else if dependants.len() == 2 && plans.entries.contains(&dependants[1]) {
|
||||
dependants[1]
|
||||
} else {
|
||||
least_common_ancestor(&builder, &dependants)
|
||||
};
|
||||
|
||||
if dependants.len() == 2 && dependants.contains(&higher_module) {
|
||||
let mut entry = if is_reexport {
|
||||
let mut entry = if should_be_reexport {
|
||||
higher_module
|
||||
} else {
|
||||
*dependants.iter().find(|&&v| v != higher_module).unwrap()
|
||||
@ -353,31 +380,63 @@ where
|
||||
}
|
||||
|
||||
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);
|
||||
done.insert(dep);
|
||||
normal_plan.chunks.push(dep);
|
||||
normal_plan.chunks.push(Dependancy {
|
||||
id: dep,
|
||||
ty: DepType::Direct,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let t = &mut plans
|
||||
.normal
|
||||
.entry(higher_module)
|
||||
.or_default()
|
||||
.transitive_chunks;
|
||||
if !t.contains(&dep) {
|
||||
if self.scope.should_be_wrapped_with_a_fn(dep) {
|
||||
let normal_entry = &mut plans.normal.entry(entry).or_default();
|
||||
|
||||
let t = &mut normal_entry.chunks;
|
||||
if t.iter().all(|dependancy| dependancy.id != dep) {
|
||||
log::info!("Normal, esm: {:?} => {:?}", entry, dep);
|
||||
done.insert(dep);
|
||||
t.push(Dependancy {
|
||||
id: dep,
|
||||
ty: DepType::Direct,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
let normal_entry =
|
||||
&mut plans.normal.entry(higher_module).or_default();
|
||||
|
||||
let t = &mut normal_entry.chunks;
|
||||
if t.iter().all(|dependancy| dependancy.id != dep) {
|
||||
log::trace!("Transitive: {:?} => {:?}", entry, dep);
|
||||
done.insert(dep);
|
||||
t.push(dep)
|
||||
t.push(Dependancy {
|
||||
id: dep,
|
||||
ty: if should_be_reexport {
|
||||
DepType::Direct
|
||||
} else {
|
||||
DepType::Transitive
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Direct dependency.
|
||||
log::trace!("Normal: {:?} => {:?}", entry, dep);
|
||||
done.insert(dep);
|
||||
plans.normal.entry(entry).or_default().chunks.push(dep);
|
||||
}
|
||||
|
||||
continue;
|
||||
plans
|
||||
.normal
|
||||
.entry(entry)
|
||||
.or_default()
|
||||
.chunks
|
||||
.push(Dependancy {
|
||||
id: dep,
|
||||
ty: DepType::Direct,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -385,7 +444,7 @@ where
|
||||
|
||||
// Sort transitive chunks topologically.
|
||||
for (_, normal_plan) in &mut plans.normal {
|
||||
toposort(&builder, &mut normal_plan.transitive_chunks);
|
||||
toposort(&builder, &mut normal_plan.chunks);
|
||||
}
|
||||
|
||||
// Handle circular imports
|
||||
@ -416,7 +475,8 @@ where
|
||||
|
||||
{
|
||||
let c = &mut plans.normal.entry(dep).or_default().chunks;
|
||||
if let Some(pos) = c.iter().position(|&v| v == circular_member)
|
||||
if let Some(pos) =
|
||||
c.iter().position(|&v| v.id == circular_member)
|
||||
{
|
||||
c.remove(pos);
|
||||
}
|
||||
@ -428,7 +488,7 @@ where
|
||||
.entry(circular_member)
|
||||
.or_default()
|
||||
.chunks;
|
||||
if let Some(pos) = c.iter().position(|&v| v == dep) {
|
||||
if let Some(pos) = c.iter().position(|&v| v.id == dep) {
|
||||
c.remove(pos);
|
||||
}
|
||||
}
|
||||
@ -550,24 +610,51 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn toposort(b: &PlanBuilder, module_ids: &mut Vec<ModuleId>) {
|
||||
let len = module_ids.len();
|
||||
|
||||
fn toposort(b: &PlanBuilder, module_ids: &mut Vec<Dependancy>) {
|
||||
if module_ids.len() <= 1 {
|
||||
return;
|
||||
}
|
||||
|
||||
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 j in i..len {
|
||||
let mi = module_ids[i];
|
||||
let mj = module_ids[j];
|
||||
if mi == mj {
|
||||
let m = module_ids[i].id;
|
||||
if graph.neighbors_directed(m, Incoming).count() != 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if b.direct_deps.contains_edge(mj, mi) {
|
||||
module_ids.swap(i, j);
|
||||
did_work = true;
|
||||
orders.push(i);
|
||||
// Remove dependency
|
||||
graph.remove_node(m);
|
||||
}
|
||||
|
||||
if !did_work {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..len {
|
||||
if orders.contains(&i) {
|
||||
continue;
|
||||
}
|
||||
orders.push(i);
|
||||
}
|
||||
|
||||
let mut buf = Vec::with_capacity(module_ids.len());
|
||||
for order in orders {
|
||||
let item = module_ids[order];
|
||||
buf.push(item)
|
||||
}
|
||||
|
||||
*module_ids = buf;
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
use super::Plan;
|
||||
use crate::bundler::tests::{suite, Tester};
|
||||
use crate::bundler::{
|
||||
chunk::plan::DepType,
|
||||
tests::{suite, Tester},
|
||||
};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use swc_common::FileName;
|
||||
|
||||
@ -18,18 +21,33 @@ fn assert_normal_transitive(
|
||||
) {
|
||||
if deps.is_empty() {
|
||||
if let Some(v) = p.normal.get(&t.id(&format!("{}.js", entry))) {
|
||||
assert_eq!(v.chunks, vec![], "Should be empty");
|
||||
let actual = v
|
||||
.chunks
|
||||
.iter()
|
||||
.filter(|dep| match dep.ty {
|
||||
DepType::Direct => true,
|
||||
_ => false,
|
||||
})
|
||||
.copied()
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(actual, vec![], "Should be empty");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
p.normal[&t.id(&format!("{}.js", entry))]
|
||||
let actual = p.normal[&t.id(&format!("{}.js", entry))]
|
||||
.chunks
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<HashSet<_>>(),
|
||||
.filter(|dep| match dep.ty {
|
||||
DepType::Direct => true,
|
||||
_ => false,
|
||||
})
|
||||
.map(|dep| dep.id)
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
assert_eq!(
|
||||
actual,
|
||||
deps.into_iter()
|
||||
.map(|s| format!("{}.js", s))
|
||||
.map(|s| t.id(&s))
|
||||
@ -40,9 +58,13 @@ fn assert_normal_transitive(
|
||||
|
||||
assert_eq!(
|
||||
p.normal[&t.id(&format!("{}.js", entry))]
|
||||
.transitive_chunks
|
||||
.chunks
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter(|dep| match dep.ty {
|
||||
DepType::Transitive => true,
|
||||
_ => false,
|
||||
})
|
||||
.map(|dep| dep.id)
|
||||
.collect::<HashSet<_>>(),
|
||||
transitive_deps
|
||||
.into_iter()
|
||||
@ -958,3 +980,55 @@ fn deno_003() {
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deno_8302_3() {
|
||||
suite()
|
||||
.file(
|
||||
"main.js",
|
||||
"
|
||||
import * as a from './a';
|
||||
",
|
||||
)
|
||||
.file(
|
||||
"a.js",
|
||||
"
|
||||
import * as b from './b';
|
||||
import * as lib from './lib';
|
||||
",
|
||||
)
|
||||
.file(
|
||||
"b.js",
|
||||
"
|
||||
import * as c from './c';
|
||||
import * as lib from './lib';
|
||||
",
|
||||
)
|
||||
.file(
|
||||
"c.js",
|
||||
"
|
||||
import * as a from './a';
|
||||
import * as lib from './lib';
|
||||
",
|
||||
)
|
||||
.file("lib.js", "")
|
||||
.run(|t| {
|
||||
let module = t
|
||||
.bundler
|
||||
.load_transformed(&FileName::Real("main.js".into()))?
|
||||
.unwrap();
|
||||
let mut entries = HashMap::default();
|
||||
entries.insert("main.js".to_string(), module.clone());
|
||||
|
||||
let p = t.bundler.calculate_plan(entries)?;
|
||||
|
||||
dbg!(&p);
|
||||
|
||||
assert_normal(t, &p, "main", &["a"]);
|
||||
|
||||
assert_normal(t, &p, "a", &["lib"]);
|
||||
assert_circular(t, &p, "a", &["b", "c"]);
|
||||
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
@ -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) {}
|
||||
}
|
369
bundler/src/bundler/chunk/sort.rs
Normal file
369
bundler/src/bundler/chunk/sort.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -21,12 +21,14 @@ where
|
||||
&self,
|
||||
file_name: &FileName,
|
||||
module: &mut Module,
|
||||
export_ctxt: SyntaxContext,
|
||||
) -> RawExports {
|
||||
self.run(|| {
|
||||
let mut v = ExportFinder {
|
||||
info: Default::default(),
|
||||
file_name,
|
||||
bundler: self,
|
||||
export_ctxt,
|
||||
};
|
||||
|
||||
module.visit_mut_with(&mut v);
|
||||
@ -56,6 +58,7 @@ where
|
||||
info: RawExports,
|
||||
file_name: &'a FileName,
|
||||
bundler: &'a Bundler<'b, L, R>,
|
||||
export_ctxt: SyntaxContext,
|
||||
}
|
||||
|
||||
impl<L, R> ExportFinder<'_, '_, L, R>
|
||||
@ -63,7 +66,8 @@ where
|
||||
L: Load,
|
||||
R: Resolve,
|
||||
{
|
||||
fn ctxt_for(&self, src: &JsWord) -> Option<SyntaxContext> {
|
||||
/// Returns `(local, export)`.
|
||||
fn ctxt_for(&self, src: &JsWord) -> Option<(SyntaxContext, SyntaxContext)> {
|
||||
// Don't apply mark if it's a core module.
|
||||
if self
|
||||
.bundler
|
||||
@ -75,10 +79,12 @@ where
|
||||
return None;
|
||||
}
|
||||
let path = self.bundler.resolve(self.file_name, src).ok()?;
|
||||
let (_, mark) = self.bundler.scope.module_id_gen.gen(&path);
|
||||
let ctxt = SyntaxContext::empty();
|
||||
let (_, local_mark, export_mark) = self.bundler.scope.module_id_gen.gen(&path);
|
||||
|
||||
Some(ctxt.apply_mark(mark))
|
||||
Some((
|
||||
SyntaxContext::empty().apply_mark(local_mark),
|
||||
SyntaxContext::empty().apply_mark(export_mark),
|
||||
))
|
||||
}
|
||||
|
||||
fn mark_as_wrapping_required(&self, src: &JsWord) {
|
||||
@ -97,7 +103,7 @@ where
|
||||
Ok(v) => v,
|
||||
_ => return,
|
||||
};
|
||||
let (id, _) = self.bundler.scope.module_id_gen.gen(&path);
|
||||
let (id, _, _) = self.bundler.scope.module_id_gen.gen(&path);
|
||||
|
||||
self.bundler.scope.mark_as_wrapping_required(id);
|
||||
}
|
||||
@ -207,6 +213,10 @@ where
|
||||
for s in &mut named.specifiers {
|
||||
match s {
|
||||
ExportSpecifier::Namespace(n) => {
|
||||
if let Some((_, export_ctxt)) = ctxt {
|
||||
n.name.span.ctxt = export_ctxt;
|
||||
}
|
||||
|
||||
need_wrapping = true;
|
||||
v.push(Specifier::Namespace {
|
||||
local: n.name.clone().into(),
|
||||
@ -220,8 +230,19 @@ where
|
||||
});
|
||||
}
|
||||
ExportSpecifier::Named(n) => {
|
||||
if let Some(ctxt) = ctxt {
|
||||
n.orig.span = n.orig.span.with_ctxt(ctxt);
|
||||
if let Some((_, export_ctxt)) = ctxt {
|
||||
n.orig.span.ctxt = export_ctxt;
|
||||
}
|
||||
|
||||
match &mut n.exported {
|
||||
Some(exported) => {
|
||||
exported.span.ctxt = self.export_ctxt;
|
||||
}
|
||||
None => {
|
||||
let mut exported: Ident = n.orig.clone();
|
||||
exported.span.ctxt = self.export_ctxt;
|
||||
n.exported = Some(exported);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(exported) = &n.exported {
|
||||
@ -247,6 +268,11 @@ where
|
||||
}
|
||||
|
||||
ModuleItem::ModuleDecl(ModuleDecl::ExportAll(all)) => {
|
||||
let ctxt = self.ctxt_for(&all.src.value);
|
||||
if let Some((_, export_ctxt)) = ctxt {
|
||||
all.span.ctxt = export_ctxt;
|
||||
}
|
||||
|
||||
self.info.items.entry(Some(all.src.clone())).or_default();
|
||||
}
|
||||
_ => {}
|
||||
|
@ -26,13 +26,13 @@ where
|
||||
&self,
|
||||
path: &FileName,
|
||||
module: &mut Module,
|
||||
module_mark: Mark,
|
||||
module_local_mark: Mark,
|
||||
) -> RawImports {
|
||||
self.run(|| {
|
||||
let body = replace(&mut module.body, vec![]);
|
||||
|
||||
let mut v = ImportHandler {
|
||||
module_ctxt: SyntaxContext::empty().apply_mark(module_mark),
|
||||
module_ctxt: SyntaxContext::empty().apply_mark(module_local_mark),
|
||||
path,
|
||||
bundler: self,
|
||||
top_level: false,
|
||||
@ -127,7 +127,8 @@ where
|
||||
L: Load,
|
||||
R: Resolve,
|
||||
{
|
||||
fn ctxt_for(&self, src: &JsWord) -> Option<SyntaxContext> {
|
||||
/// Retursn (local, export)
|
||||
fn ctxt_for(&self, src: &JsWord) -> Option<(SyntaxContext, SyntaxContext)> {
|
||||
// Don't apply mark if it's a core module.
|
||||
if self
|
||||
.bundler
|
||||
@ -139,10 +140,12 @@ where
|
||||
return None;
|
||||
}
|
||||
let path = self.bundler.resolve(self.path, src).ok()?;
|
||||
let (_, mark) = self.bundler.scope.module_id_gen.gen(&path);
|
||||
let ctxt = SyntaxContext::empty();
|
||||
let (_, local_mark, export_mark) = self.bundler.scope.module_id_gen.gen(&path);
|
||||
|
||||
Some(ctxt.apply_mark(mark))
|
||||
Some((
|
||||
SyntaxContext::empty().apply_mark(local_mark),
|
||||
SyntaxContext::empty().apply_mark(export_mark),
|
||||
))
|
||||
}
|
||||
|
||||
fn mark_as_wrapping_required(&self, src: &JsWord) {
|
||||
@ -161,7 +164,7 @@ where
|
||||
Ok(v) => v,
|
||||
Err(_) => return,
|
||||
};
|
||||
let (id, _) = self.bundler.scope.module_id_gen.gen(&path);
|
||||
let (id, _, _) = self.bundler.scope.module_id_gen.gen(&path);
|
||||
|
||||
self.bundler.scope.mark_as_wrapping_required(id);
|
||||
}
|
||||
@ -185,22 +188,30 @@ where
|
||||
{
|
||||
return import;
|
||||
}
|
||||
if let Some(ctxt) = self.ctxt_for(&import.src.value) {
|
||||
import.span = import.span.with_ctxt(ctxt);
|
||||
if let Some((_, export_ctxt)) = self.ctxt_for(&import.src.value) {
|
||||
import.span = import.span.with_ctxt(export_ctxt);
|
||||
|
||||
for specifier in &mut import.specifiers {
|
||||
match specifier {
|
||||
ImportSpecifier::Named(n) => {
|
||||
self.imported_idents.insert(n.local.to_id(), ctxt);
|
||||
n.local.span = n.local.span.with_ctxt(ctxt);
|
||||
self.imported_idents.insert(n.local.to_id(), export_ctxt);
|
||||
match &mut n.imported {
|
||||
Some(imported) => {
|
||||
imported.span.ctxt = export_ctxt;
|
||||
}
|
||||
None => {
|
||||
let mut imported: Ident = n.local.clone();
|
||||
imported.span.ctxt = export_ctxt;
|
||||
n.imported = Some(imported);
|
||||
}
|
||||
}
|
||||
}
|
||||
ImportSpecifier::Default(n) => {
|
||||
self.imported_idents.insert(n.local.to_id(), ctxt);
|
||||
n.local.span = n.local.span.with_ctxt(ctxt);
|
||||
self.imported_idents
|
||||
.insert(n.local.to_id(), n.local.span.ctxt);
|
||||
}
|
||||
ImportSpecifier::Namespace(n) => {
|
||||
self.imported_idents.insert(n.local.to_id(), ctxt);
|
||||
n.local.span = n.local.span.with_ctxt(ctxt);
|
||||
self.imported_idents.insert(n.local.to_id(), export_ctxt);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -329,41 +340,26 @@ where
|
||||
}
|
||||
|
||||
fn fold_export_named_specifier(&mut self, mut s: ExportNamedSpecifier) -> ExportNamedSpecifier {
|
||||
if let Some(&ctxt) = self.imported_idents.get(&s.orig.to_id()) {
|
||||
s.orig.span = s.orig.span.with_ctxt(ctxt);
|
||||
match &s.exported {
|
||||
Some(exported) => {
|
||||
debug_assert_eq!(
|
||||
exported.span.ctxt, self.module_ctxt,
|
||||
"Exported names should have same (local) context as top-level module items"
|
||||
);
|
||||
}
|
||||
None => {
|
||||
let exported =
|
||||
Ident::new(s.orig.sym.clone(), s.orig.span.with_ctxt(self.module_ctxt));
|
||||
s.exported = Some(exported);
|
||||
}
|
||||
}
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
fn fold_prop(&mut self, mut prop: Prop) -> Prop {
|
||||
prop = prop.fold_children_with(self);
|
||||
|
||||
match prop {
|
||||
Prop::Shorthand(mut i) => {
|
||||
if let Some(&ctxt) = self.imported_idents.get(&i.to_id()) {
|
||||
let local = i.clone();
|
||||
|
||||
i.span = i.span.with_ctxt(ctxt);
|
||||
|
||||
return Prop::KeyValue(KeyValueProp {
|
||||
key: PropName::Ident(local),
|
||||
value: Box::new(Expr::Ident(i)),
|
||||
});
|
||||
}
|
||||
|
||||
Prop::Shorthand(i)
|
||||
}
|
||||
_ => prop,
|
||||
}
|
||||
}
|
||||
|
||||
fn fold_expr(&mut self, e: Expr) -> Expr {
|
||||
match e {
|
||||
Expr::Ident(mut i) if self.deglob_phase => {
|
||||
if let Some(&ctxt) = self.imported_idents.get(&i.to_id()) {
|
||||
i.span = i.span.with_ctxt(ctxt);
|
||||
}
|
||||
Expr::Ident(i) if self.deglob_phase => {
|
||||
return Expr::Ident(i);
|
||||
}
|
||||
|
||||
@ -408,9 +404,9 @@ where
|
||||
false
|
||||
}) {
|
||||
let mark = self.ctxt_for(&import.src.value);
|
||||
let ctxt = match mark {
|
||||
let exported_ctxt = match mark {
|
||||
None => return e.into(),
|
||||
Some(mark) => mark,
|
||||
Some(ctxts) => ctxts.1,
|
||||
};
|
||||
if self.deglob_phase {
|
||||
if self.info.forced_ns.contains(&import.src.value) {
|
||||
@ -421,7 +417,7 @@ where
|
||||
let i = match &*e.prop {
|
||||
Expr::Ident(i) => {
|
||||
let mut i = i.clone();
|
||||
i.span = i.span.with_ctxt(ctxt);
|
||||
i.span = i.span.with_ctxt(exported_ctxt);
|
||||
i
|
||||
}
|
||||
_ => unreachable!(
|
||||
@ -438,7 +434,7 @@ where
|
||||
let i = match &*e.prop {
|
||||
Expr::Ident(i) => {
|
||||
let mut i = i.clone();
|
||||
i.span = i.span.with_ctxt(ctxt);
|
||||
i.span = i.span.with_ctxt(exported_ctxt);
|
||||
i
|
||||
}
|
||||
_ => unreachable!(
|
||||
@ -496,8 +492,8 @@ where
|
||||
{
|
||||
match &mut **callee {
|
||||
Expr::Ident(i) => {
|
||||
if let Some(ctxt) = self.ctxt_for(&src.value) {
|
||||
i.span = i.span.with_ctxt(ctxt);
|
||||
if let Some((_, export_ctxt)) = self.ctxt_for(&src.value) {
|
||||
i.span = i.span.with_ctxt(export_ctxt);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@ -586,8 +582,8 @@ where
|
||||
|
||||
match &mut **callee {
|
||||
Expr::Ident(i) => {
|
||||
if let Some(mark) = self.ctxt_for(&src.value) {
|
||||
i.span = i.span.with_ctxt(mark);
|
||||
if let Some((_, export_ctxt)) = self.ctxt_for(&src.value) {
|
||||
i.span = i.span.with_ctxt(export_ctxt);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
@ -12,7 +12,7 @@ use is_macro::Is;
|
||||
#[cfg(feature = "rayon")]
|
||||
use rayon::iter::ParallelIterator;
|
||||
use swc_atoms::js_word;
|
||||
use swc_common::{sync::Lrc, FileName, Mark, SourceFile, SyntaxContext, DUMMY_SP};
|
||||
use swc_common::{sync::Lrc, FileName, SourceFile, SyntaxContext, DUMMY_SP};
|
||||
use swc_ecma_ast::{
|
||||
CallExpr, Expr, ExprOrSuper, Ident, ImportDecl, ImportSpecifier, Invalid, MemberExpr, Module,
|
||||
ModuleDecl, Str,
|
||||
@ -36,17 +36,19 @@ pub(super) struct TransformedModule {
|
||||
|
||||
pub swc_helpers: Lrc<swc_ecma_transforms::helpers::Helpers>,
|
||||
|
||||
mark: Mark,
|
||||
local_ctxt: SyntaxContext,
|
||||
export_ctxt: SyntaxContext,
|
||||
}
|
||||
|
||||
impl TransformedModule {
|
||||
/// THe marker for the module's top-level identifiers.
|
||||
pub fn mark(&self) -> Mark {
|
||||
self.mark
|
||||
/// [SyntaxContext] for exported items.
|
||||
pub fn export_ctxt(&self) -> SyntaxContext {
|
||||
self.export_ctxt
|
||||
}
|
||||
|
||||
pub fn ctxt(&self) -> SyntaxContext {
|
||||
SyntaxContext::empty().apply_mark(self.mark)
|
||||
/// Top level contexts.
|
||||
pub fn local_ctxt(&self) -> SyntaxContext {
|
||||
self.local_ctxt
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,7 +80,13 @@ where
|
||||
.context("failed to analyze module")?;
|
||||
files.dedup_by_key(|v| v.1.clone());
|
||||
|
||||
log::debug!("({}, {:?}) Storing module: {}", v.id, v.ctxt(), file_name);
|
||||
log::debug!(
|
||||
"({:?}, {:?}, {:?}) Storing module: {}",
|
||||
v.id,
|
||||
v.local_ctxt(),
|
||||
v.export_ctxt(),
|
||||
file_name
|
||||
);
|
||||
self.scope.store_module(v.clone());
|
||||
|
||||
// Load dependencies and store them in the `Scope`
|
||||
@ -101,7 +109,7 @@ where
|
||||
|
||||
fn load(&self, file_name: &FileName) -> Result<(ModuleId, ModuleData), Error> {
|
||||
self.run(|| {
|
||||
let (module_id, _) = self.scope.module_id_gen.gen(file_name);
|
||||
let (module_id, _, _) = self.scope.module_id_gen.gen(file_name);
|
||||
|
||||
let data = self
|
||||
.loader
|
||||
@ -120,9 +128,9 @@ where
|
||||
) -> Result<(TransformedModule, Vec<(Source, Lrc<FileName>)>), Error> {
|
||||
self.run(|| {
|
||||
log::trace!("transform_module({})", data.fm.name);
|
||||
let (id, mark) = self.scope.module_id_gen.gen(file_name);
|
||||
let (id, local_mark, export_mark) = self.scope.module_id_gen.gen(file_name);
|
||||
|
||||
let mut module = data.module.fold_with(&mut resolver_with_mark(mark));
|
||||
let mut module = data.module.fold_with(&mut resolver_with_mark(local_mark));
|
||||
|
||||
// {
|
||||
// let code = self
|
||||
@ -139,7 +147,7 @@ where
|
||||
// println!("Resolved:\n{}\n\n", code);
|
||||
// }
|
||||
|
||||
let imports = self.extract_import_info(file_name, &mut module, mark);
|
||||
let imports = self.extract_import_info(file_name, &mut module, local_mark);
|
||||
|
||||
// {
|
||||
// let code = self
|
||||
@ -156,7 +164,11 @@ where
|
||||
// println!("After imports:\n{}\n", code,);
|
||||
// }
|
||||
|
||||
let exports = self.extract_export_info(file_name, &mut module);
|
||||
let exports = self.extract_export_info(
|
||||
file_name,
|
||||
&mut module,
|
||||
SyntaxContext::empty().apply_mark(export_mark),
|
||||
);
|
||||
|
||||
let is_es6 = if !self.config.require {
|
||||
true
|
||||
@ -188,8 +200,9 @@ where
|
||||
exports: Lrc::new(exports),
|
||||
is_es6,
|
||||
helpers: Default::default(),
|
||||
mark,
|
||||
swc_helpers: Lrc::new(data.helpers),
|
||||
local_ctxt: SyntaxContext::empty().apply_mark(local_mark),
|
||||
export_ctxt: SyntaxContext::empty().apply_mark(export_mark),
|
||||
},
|
||||
import_files,
|
||||
))
|
||||
@ -216,8 +229,9 @@ where
|
||||
let info = match src {
|
||||
Some(src) => {
|
||||
let name = self.resolve(base, &src.value)?;
|
||||
let (id, mark) = self.scope.module_id_gen.gen(&name);
|
||||
Some((id, mark, name, src))
|
||||
let (id, local_mark, export_mark) =
|
||||
self.scope.module_id_gen.gen(&name);
|
||||
Some((id, local_mark, export_mark, name, src))
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
@ -232,13 +246,14 @@ where
|
||||
|
||||
match info {
|
||||
None => exports.items.extend(specifiers),
|
||||
Some((id, mark, name, src)) => {
|
||||
Some((id, local_mark, export_mark, name, src)) => {
|
||||
//
|
||||
let src = Source {
|
||||
is_loaded_synchronously: true,
|
||||
is_unconditional: false,
|
||||
module_id: id,
|
||||
ctxt: SyntaxContext::empty().apply_mark(mark),
|
||||
local_ctxt: SyntaxContext::empty().apply_mark(local_mark),
|
||||
export_ctxt: SyntaxContext::empty().apply_mark(export_mark),
|
||||
src,
|
||||
};
|
||||
exports.reexports.push((src.clone(), specifiers));
|
||||
@ -290,22 +305,33 @@ where
|
||||
self.run(|| {
|
||||
//
|
||||
let file_name = self.resolve(base, &decl.src.value)?;
|
||||
let (id, mark) = self.scope.module_id_gen.gen(&file_name);
|
||||
let (id, local_mark, export_mark) =
|
||||
self.scope.module_id_gen.gen(&file_name);
|
||||
|
||||
Ok((id, mark, file_name, decl, dynamic, unconditional))
|
||||
Ok((
|
||||
id,
|
||||
local_mark,
|
||||
export_mark,
|
||||
file_name,
|
||||
decl,
|
||||
dynamic,
|
||||
unconditional,
|
||||
))
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for res in loaded {
|
||||
// TODO: Report error and proceed instead of returning an error
|
||||
let (id, mark, file_name, decl, is_dynamic, is_unconditional) = res?;
|
||||
let (id, local_mark, export_mark, file_name, decl, is_dynamic, is_unconditional) =
|
||||
res?;
|
||||
|
||||
let src = Source {
|
||||
is_loaded_synchronously: !is_dynamic,
|
||||
is_unconditional,
|
||||
module_id: id,
|
||||
ctxt: SyntaxContext::empty().apply_mark(mark),
|
||||
local_ctxt: SyntaxContext::empty().apply_mark(local_mark),
|
||||
export_ctxt: SyntaxContext::empty().apply_mark(export_mark),
|
||||
src: decl.src,
|
||||
};
|
||||
files.push((src.clone(), file_name));
|
||||
@ -365,7 +391,8 @@ pub(super) struct Source {
|
||||
pub is_unconditional: bool,
|
||||
|
||||
pub module_id: ModuleId,
|
||||
pub ctxt: SyntaxContext,
|
||||
pub local_ctxt: SyntaxContext,
|
||||
pub export_ctxt: SyntaxContext,
|
||||
|
||||
// Clone is relatively cheap, thanks to string_cache.
|
||||
pub src: Str,
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{Bundler, Load, Resolve};
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_transforms::optimization::simplify::{dce, inlining};
|
||||
use swc_ecma_transforms::optimization::simplify::{const_propgation::constant_propagation, dce};
|
||||
use swc_ecma_visit::FoldWith;
|
||||
|
||||
impl<L, R> Bundler<'_, L, R>
|
||||
@ -15,7 +15,7 @@ where
|
||||
pub(super) fn optimize(&self, mut node: Module) -> Module {
|
||||
self.run(|| {
|
||||
if !self.config.disable_inliner {
|
||||
node = node.fold_with(&mut inlining::inlining(inlining::Config {}))
|
||||
node = node.fold_with(&mut constant_propagation())
|
||||
}
|
||||
|
||||
node = node.fold_with(&mut dce::dce(dce::Config {
|
||||
|
@ -1,6 +1,6 @@
|
||||
use super::load::TransformedModule;
|
||||
use crate::{
|
||||
id::{ModuleId, ModuleIdGenerator},
|
||||
id::{Id, ModuleId, ModuleIdGenerator},
|
||||
util::CloneMap,
|
||||
};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
@ -36,7 +36,7 @@ impl Scope {
|
||||
}
|
||||
|
||||
pub fn get_module_by_path(&self, file_name: &FileName) -> Option<TransformedModule> {
|
||||
let (id, _) = self.module_id_gen.gen(file_name);
|
||||
let (id, _, _) = self.module_id_gen.gen(file_name);
|
||||
self.get_module(id)
|
||||
}
|
||||
|
||||
@ -62,4 +62,15 @@ impl Scope {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Some(module_ident)` if the module should be wrapped
|
||||
/// with a function.
|
||||
pub fn wrapped_esm_id(&self, id: ModuleId) -> Option<Id> {
|
||||
if !self.should_be_wrapped_with_a_fn(id) {
|
||||
return None;
|
||||
}
|
||||
let info = self.get_module(id)?;
|
||||
|
||||
Some(Id::new("mod".into(), info.export_ctxt()))
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use std::{
|
||||
};
|
||||
use swc_atoms::JsWord;
|
||||
use swc_common::{sync::Lock, FileName, Mark, SyntaxContext, DUMMY_SP};
|
||||
use swc_ecma_ast::Ident;
|
||||
use swc_ecma_ast::{Expr, Ident};
|
||||
use swc_ecma_utils::ident::IdentLike;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
@ -27,20 +27,23 @@ impl fmt::Debug for ModuleId {
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct ModuleIdGenerator {
|
||||
v: AtomicU32,
|
||||
cache: Lock<HashMap<FileName, (ModuleId, Mark)>>,
|
||||
/// `(module_id, local_mark, export_mark)`
|
||||
cache: Lock<HashMap<FileName, (ModuleId, Mark, Mark)>>,
|
||||
}
|
||||
|
||||
impl ModuleIdGenerator {
|
||||
pub fn gen(&self, file_name: &FileName) -> (ModuleId, Mark) {
|
||||
pub fn gen(&self, file_name: &FileName) -> (ModuleId, Mark, Mark) {
|
||||
let mut w = self.cache.lock();
|
||||
if let Some(v) = w.get(file_name) {
|
||||
return v.clone();
|
||||
}
|
||||
|
||||
let id = ModuleId(self.v.fetch_add(1, SeqCst));
|
||||
let mark = Mark::fresh(Mark::root());
|
||||
w.insert(file_name.clone(), (id, mark));
|
||||
(id, mark)
|
||||
let local_mark = Mark::fresh(Mark::root());
|
||||
let export_mark = Mark::fresh(Mark::root());
|
||||
let v = (id, local_mark, export_mark);
|
||||
w.insert(file_name.clone(), v);
|
||||
(id, local_mark, export_mark)
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,12 +65,16 @@ impl Id {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn ctxt(&self) -> SyntaxContext {
|
||||
self.1
|
||||
}
|
||||
|
||||
pub fn into_ident(self) -> Ident {
|
||||
Ident::new(self.0, DUMMY_SP.with_ctxt(self.1))
|
||||
}
|
||||
|
||||
pub fn replace_mark(mut self, mark: Mark) -> Self {
|
||||
self.1 = SyntaxContext::empty().apply_mark(mark);
|
||||
pub fn with_ctxt(mut self, ctxt: SyntaxContext) -> Self {
|
||||
self.1 = ctxt;
|
||||
self
|
||||
}
|
||||
}
|
||||
@ -109,3 +116,17 @@ impl PartialEq<JsWord> for Id {
|
||||
self.0 == *other
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for Ident {
|
||||
#[inline]
|
||||
fn from(id: Id) -> Self {
|
||||
id.into_ident()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for Expr {
|
||||
#[inline]
|
||||
fn from(id: Id) -> Self {
|
||||
Expr::Ident(id.into_ident())
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,56 @@
|
||||
use std::{hash::Hash, mem::replace};
|
||||
use std::{clone::Clone, cmp::Eq, hash::Hash, mem::replace};
|
||||
use swc_atoms::js_word;
|
||||
use swc_common::{Span, SyntaxContext, DUMMY_SP};
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_utils::ident::IdentLike;
|
||||
use swc_ecma_visit::{noop_visit_mut_type, VisitMut};
|
||||
|
||||
pub(crate) trait VarDeclaratorExt: Into<VarDeclarator> {
|
||||
fn into_module_item(self, _name: &'static str) -> ModuleItem {
|
||||
ModuleItem::Stmt(Stmt::Decl(Decl::Var(VarDecl {
|
||||
span: DUMMY_SP,
|
||||
kind: VarDeclKind::Const,
|
||||
declare: false,
|
||||
decls: vec![
|
||||
self.into(),
|
||||
/* Ident::new(name.into(), DUMMY_SP)
|
||||
* .assign_to(Ident::new("INJECTED_FROM".into(), DUMMY_SP)), */
|
||||
],
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> VarDeclaratorExt for T where T: Into<VarDeclarator> {}
|
||||
|
||||
pub(crate) trait ExprExt: Into<Expr> {
|
||||
#[track_caller]
|
||||
fn assign_to<T>(self, lhs: T) -> VarDeclarator
|
||||
where
|
||||
T: IdentLike,
|
||||
{
|
||||
let init = self.into();
|
||||
let lhs = lhs.into_id();
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
match &init {
|
||||
Expr::Ident(rhs) => {
|
||||
debug_assert_ne!(lhs, rhs.to_id());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
VarDeclarator {
|
||||
span: DUMMY_SP,
|
||||
name: Pat::Ident(Ident::new(lhs.0, DUMMY_SP.with_ctxt(lhs.1))),
|
||||
init: Some(Box::new(init)),
|
||||
definite: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ExprExt for T where T: Into<Expr> {}
|
||||
|
||||
/// Helper for migration from [Fold] to [VisitMut]
|
||||
pub(crate) trait MapWithMut: Sized {
|
||||
fn dummy() -> Self;
|
||||
@ -98,6 +145,54 @@ impl MapWithMut for PatOrExpr {
|
||||
}
|
||||
}
|
||||
|
||||
impl MapWithMut for ClassExpr {
|
||||
fn dummy() -> Self {
|
||||
ClassExpr {
|
||||
ident: None,
|
||||
class: MapWithMut::dummy(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MapWithMut for FnExpr {
|
||||
fn dummy() -> Self {
|
||||
FnExpr {
|
||||
ident: None,
|
||||
function: MapWithMut::dummy(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MapWithMut for Class {
|
||||
fn dummy() -> Self {
|
||||
Class {
|
||||
span: Default::default(),
|
||||
decorators: Default::default(),
|
||||
body: Default::default(),
|
||||
super_class: Default::default(),
|
||||
is_abstract: Default::default(),
|
||||
type_params: Default::default(),
|
||||
super_type_params: Default::default(),
|
||||
implements: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MapWithMut for Function {
|
||||
fn dummy() -> Self {
|
||||
Function {
|
||||
params: Default::default(),
|
||||
decorators: Default::default(),
|
||||
span: Default::default(),
|
||||
body: Default::default(),
|
||||
is_generator: Default::default(),
|
||||
is_async: Default::default(),
|
||||
type_params: Default::default(),
|
||||
return_type: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct CHashSet<V>
|
||||
where
|
||||
|
@ -5,7 +5,7 @@
|
||||
use anyhow::{Context, Error};
|
||||
use sha1::{Digest, Sha1};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
collections::{HashMap, HashSet},
|
||||
env,
|
||||
fs::{create_dir_all, read_to_string, write},
|
||||
path::PathBuf,
|
||||
@ -13,81 +13,379 @@ use std::{
|
||||
};
|
||||
use swc_atoms::js_word;
|
||||
use swc_bundler::{Bundler, Load, ModuleData, ModuleRecord, Resolve};
|
||||
use swc_common::{sync::Lrc, FileName, SourceMap, Span, GLOBALS};
|
||||
use swc_ecma_ast::{
|
||||
Bool, Expr, ExprOrSuper, Ident, KeyValueProp, Lit, MemberExpr, MetaPropExpr, PropName, Str,
|
||||
};
|
||||
use swc_common::{comments::SingleThreadedComments, sync::Lrc, FileName, SourceMap, Span, GLOBALS};
|
||||
use swc_ecma_ast::*;
|
||||
use swc_ecma_codegen::{text_writer::JsWriter, Emitter};
|
||||
use swc_ecma_parser::{lexer::Lexer, JscTarget, Parser, StringInput, Syntax, TsConfig};
|
||||
use swc_ecma_transforms::{proposals::decorators, typescript::strip};
|
||||
use swc_ecma_visit::FoldWith;
|
||||
use swc_ecma_transforms::{proposals::decorators, react, typescript::strip};
|
||||
use swc_ecma_utils::{find_ids, Id};
|
||||
use swc_ecma_visit::{FoldWith, Node, Visit, VisitWith};
|
||||
use testing::assert_eq;
|
||||
use url::Url;
|
||||
|
||||
#[test]
|
||||
fn oak_6_3_1_application() {
|
||||
run("https://deno.land/x/oak@v6.3.1/application.ts", None);
|
||||
run(
|
||||
"https://deno.land/x/oak@v6.3.1/application.ts",
|
||||
&[
|
||||
"ApplicationErrorEvent",
|
||||
"ApplicationListenEvent",
|
||||
"Application",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oak_6_3_1_mod() {
|
||||
run("https://deno.land/x/oak@v6.3.1/mod.ts", None);
|
||||
run(
|
||||
"https://deno.land/x/oak@v6.3.1/mod.ts",
|
||||
&[
|
||||
"Application",
|
||||
"Context",
|
||||
"helpers",
|
||||
"Cookies",
|
||||
"HttpError",
|
||||
"httpErrors",
|
||||
"isHttpError",
|
||||
"composeMiddleware",
|
||||
"FormDataReader",
|
||||
"Request",
|
||||
"REDIRECT_BACK",
|
||||
"Response",
|
||||
"Router",
|
||||
"send",
|
||||
"ServerSentEvent",
|
||||
"ServerSentEventTarget",
|
||||
"isErrorStatus",
|
||||
"isRedirectStatus",
|
||||
"Status",
|
||||
"STATUS_TEXT",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn std_0_74_9_http_server() {
|
||||
run("https://deno.land/std@0.74.0/http/server.ts", None);
|
||||
fn std_0_74_0_http_server() {
|
||||
run(
|
||||
"https://deno.land/std@0.74.0/http/server.ts",
|
||||
&[
|
||||
"ServerRequest",
|
||||
"Server",
|
||||
"_parseAddrFromStr",
|
||||
"serve",
|
||||
"listenAndServe",
|
||||
"serveTLS",
|
||||
"listenAndServeTLS",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "Does not finish by default"]
|
||||
fn oak_6_3_1_example_server() {
|
||||
run("https://deno.land/x/oak@v6.3.1/examples/server.ts", None);
|
||||
run("https://deno.land/x/oak@v6.3.1/examples/server.ts", &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "Does not finish by default"]
|
||||
fn oak_6_3_1_example_sse_server() {
|
||||
run("https://deno.land/x/oak@v6.3.1/examples/sseServer.ts", None);
|
||||
run("https://deno.land/x/oak@v6.3.1/examples/sseServer.ts", &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn std_0_75_0_http_server() {
|
||||
run("https://deno.land/std@0.75.0/http/server.ts", None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deno_8188() {
|
||||
fn deno_8188_full() {
|
||||
run(
|
||||
"https://raw.githubusercontent.com/nats-io/nats.ws/master/src/mod.ts",
|
||||
None,
|
||||
&[
|
||||
"connect",
|
||||
"NatsConnectionImpl",
|
||||
"Nuid",
|
||||
"nuid",
|
||||
"ErrorCode",
|
||||
"NatsError",
|
||||
"DebugEvents",
|
||||
"Empty",
|
||||
"Events",
|
||||
"MsgImpl",
|
||||
"SubscriptionImpl",
|
||||
"Subscriptions",
|
||||
"setTransportFactory",
|
||||
"setUrlParseFn",
|
||||
"Connect",
|
||||
"createInbox",
|
||||
"INFO",
|
||||
"ProtocolHandler",
|
||||
"deferred",
|
||||
"delay",
|
||||
"extractProtocolMessage",
|
||||
"render",
|
||||
"timeout",
|
||||
"headers",
|
||||
"MsgHdrsImpl",
|
||||
"Heartbeat",
|
||||
"MuxSubscription",
|
||||
"DataBuffer",
|
||||
"checkOptions",
|
||||
"Request",
|
||||
"credsAuthenticator",
|
||||
"jwtAuthenticator",
|
||||
"nkeyAuthenticator",
|
||||
"JSONCodec",
|
||||
"StringCodec",
|
||||
"QueuedIterator",
|
||||
"Kind",
|
||||
"Parser",
|
||||
"State",
|
||||
"DenoBuffer",
|
||||
"MAX_SIZE",
|
||||
"readAll",
|
||||
"writeAll",
|
||||
"Bench",
|
||||
"Metric",
|
||||
"TD",
|
||||
"TE",
|
||||
"isIP",
|
||||
"parseIP",
|
||||
"nkeys",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deno_8188_01() {
|
||||
run(
|
||||
"https://raw.githubusercontent.com/nats-io/nats.deno/v1.0.0-12/nats-base-client/nkeys.ts",
|
||||
&["nkeys"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deno_8188_02() {
|
||||
run(
|
||||
"https://raw.githubusercontent.com/nats-io/nkeys.js/v1.0.0-7/modules/esm/mod.ts",
|
||||
&[
|
||||
"NKeysError",
|
||||
"NKeysErrorCode",
|
||||
"createAccount",
|
||||
"createOperator",
|
||||
"createPair",
|
||||
"createUser",
|
||||
"decode",
|
||||
"encode",
|
||||
"fromPublic",
|
||||
"fromSeed",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deno_8188_03() {
|
||||
run(
|
||||
"https://raw.githubusercontent.com/nats-io/nkeys.js/v1.0.0-7/modules/esm/deps.ts",
|
||||
&["denoHelper"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deno_8189() {
|
||||
run("https://deno.land/x/lz4/mod.ts", None);
|
||||
run(
|
||||
"https://deno.land/x/lz4/mod.ts",
|
||||
&["compress", "decompress"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deno_8211() {
|
||||
run("https://unpkg.com/luxon@1.25.0/src/luxon.js", None);
|
||||
run(
|
||||
"https://unpkg.com/luxon@1.25.0/src/luxon.js",
|
||||
&[
|
||||
"DateTime",
|
||||
"Duration",
|
||||
"Interval",
|
||||
"Info",
|
||||
"Zone",
|
||||
"FixedOffsetZone",
|
||||
"IANAZone",
|
||||
"InvalidZone",
|
||||
"LocalZone",
|
||||
"Settings",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deno_8246() {
|
||||
run("https://raw.githubusercontent.com/nats-io/nats.deno/v1.0.0-11/nats-base-client/internal_mod.ts",None);
|
||||
run("https://raw.githubusercontent.com/nats-io/nats.deno/v1.0.0-11/nats-base-client/internal_mod.ts",&[
|
||||
"NatsConnectionImpl",
|
||||
"Nuid",
|
||||
"nuid",
|
||||
"ErrorCode",
|
||||
"NatsError",
|
||||
"DebugEvents",
|
||||
"Empty",
|
||||
"Events",
|
||||
"MsgImpl",
|
||||
"SubscriptionImpl",
|
||||
"Subscriptions",
|
||||
"setTransportFactory",
|
||||
"setUrlParseFn",
|
||||
"Connect",
|
||||
"createInbox",
|
||||
"INFO",
|
||||
"ProtocolHandler",
|
||||
"deferred",
|
||||
"delay",
|
||||
"extractProtocolMessage",
|
||||
"render",
|
||||
"timeout",
|
||||
"headers",
|
||||
"MsgHdrsImpl",
|
||||
"Heartbeat",
|
||||
"MuxSubscription",
|
||||
"DataBuffer",
|
||||
"checkOptions",
|
||||
"Request",
|
||||
"credsAuthenticator",
|
||||
"jwtAuthenticator",
|
||||
"nkeyAuthenticator",
|
||||
"JSONCodec",
|
||||
"StringCodec",
|
||||
"QueuedIterator",
|
||||
"Kind",
|
||||
"Parser",
|
||||
"State",
|
||||
"DenoBuffer",
|
||||
"MAX_SIZE",
|
||||
"readAll",
|
||||
"writeAll",
|
||||
"Bench",
|
||||
"Metric",
|
||||
"TD",
|
||||
"TE",
|
||||
"isIP",
|
||||
"parseIP",
|
||||
"nkeys",
|
||||
]);
|
||||
}
|
||||
|
||||
fn run(url: &str, expeceted_bytes: Option<usize>) {
|
||||
#[test]
|
||||
#[ignore = "document is not defined when I use deno run"]
|
||||
fn deno_6802() {
|
||||
run("tests/deno/issue-6802/input.tsx", &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deno_8314_1() {
|
||||
run("tests/deno/issue-8314/input.ts", &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deno_8314_2() {
|
||||
run("https://dev.jspm.io/ngraph.graph", &["default"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deno_8302() {
|
||||
run("tests/deno/issue-8302/input.ts", &["DB", "Empty", "Status"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deno_8399_1() {
|
||||
run("tests/deno/issue-8399-1/input.ts", &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deno_8399_2() {
|
||||
run("tests/deno/issue-8399-2/input.ts", &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merging_order_01() {
|
||||
run(
|
||||
"https://deno.land/x/oak@v6.3.1/multipart.ts",
|
||||
&["FormDataReader"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reexport_01() {
|
||||
run(
|
||||
"https://raw.githubusercontent.com/aricart/tweetnacl-deno/import-type-fixes/src/nacl.ts",
|
||||
&[
|
||||
"AuthLength",
|
||||
"ByteArray",
|
||||
"HalfArray",
|
||||
"HashLength",
|
||||
"IntArray",
|
||||
"NumArray",
|
||||
"SealedBoxLength",
|
||||
"SignLength",
|
||||
"WordArray",
|
||||
"_hash",
|
||||
"_verify_16",
|
||||
"_verify_32",
|
||||
"auth",
|
||||
"auth_full",
|
||||
"blake2b",
|
||||
"blake2b_final",
|
||||
"blake2b_init",
|
||||
"blake2b_update",
|
||||
"blake2s",
|
||||
"blake2s_final",
|
||||
"blake2s_init",
|
||||
"blake2s_update",
|
||||
"decodeBase64",
|
||||
"decodeHex",
|
||||
"decodeUTF8",
|
||||
"encodeBase64",
|
||||
"encodeHex",
|
||||
"encodeUTF8",
|
||||
"hash",
|
||||
"randomBytes",
|
||||
"scalarbase",
|
||||
"scalarmult",
|
||||
"sealedbox",
|
||||
"sealedbox_open",
|
||||
"sign",
|
||||
"sign_detached",
|
||||
"sign_detached_verify",
|
||||
"sign_keyPair",
|
||||
"sign_keyPair_fromSecretKey",
|
||||
"sign_keyPair_fromSeed",
|
||||
"sign_open",
|
||||
"validateBase64",
|
||||
"validateHex",
|
||||
"verify",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
fn run(url: &str, exports: &[&str]) {
|
||||
let dir = tempfile::tempdir().expect("failed to crate temp file");
|
||||
let path = dir.path().join("main.js");
|
||||
println!("{}", path.display());
|
||||
|
||||
let src = bundle(url);
|
||||
write(&path, &src).unwrap();
|
||||
if let Some(expected) = expeceted_bytes {
|
||||
assert_eq!(src.len(), expected);
|
||||
}
|
||||
|
||||
::testing::run_test2(false, |cm, _| {
|
||||
let fm = cm.load_file(&path).unwrap();
|
||||
let loader = Loader { cm: cm.clone() };
|
||||
let module = loader.load(&fm.name).unwrap().module;
|
||||
|
||||
let mut actual_exports = collect_exports(&module).into_iter().collect::<Vec<_>>();
|
||||
actual_exports.sort();
|
||||
let mut expected_exports = exports
|
||||
.into_iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
expected_exports.sort();
|
||||
|
||||
assert_eq!(expected_exports, actual_exports);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
if env::var("CI").is_ok() {
|
||||
return;
|
||||
@ -125,7 +423,14 @@ fn bundle(url: &str) -> String {
|
||||
Box::new(Hook),
|
||||
);
|
||||
let mut entries = HashMap::new();
|
||||
entries.insert("main".to_string(), FileName::Custom(url.to_string()));
|
||||
entries.insert(
|
||||
"main".to_string(),
|
||||
if url.starts_with("http") {
|
||||
FileName::Custom(url.to_string())
|
||||
} else {
|
||||
FileName::Real(url.to_string().into())
|
||||
},
|
||||
);
|
||||
let output = bundler.bundle(entries).unwrap();
|
||||
let module = output.into_iter().next().unwrap().module;
|
||||
|
||||
@ -192,21 +497,30 @@ impl Load for Loader {
|
||||
fn load(&self, file: &FileName) -> Result<ModuleData, Error> {
|
||||
eprintln!("{}", file);
|
||||
|
||||
let url = match file {
|
||||
FileName::Custom(v) => v,
|
||||
_ => unreachable!("this test only uses url"),
|
||||
};
|
||||
let tsx;
|
||||
|
||||
let fm = match file {
|
||||
FileName::Real(path) => {
|
||||
tsx = path.to_string_lossy().ends_with(".tsx");
|
||||
self.cm.load_file(path)?
|
||||
}
|
||||
FileName::Custom(url) => {
|
||||
tsx = url.ends_with(".tsx");
|
||||
|
||||
let url = Url::parse(&url).context("failed to parse url")?;
|
||||
|
||||
let src = load_url(url.clone())?;
|
||||
let fm = self
|
||||
.cm
|
||||
.new_source_file(FileName::Custom(url.to_string()), src.to_string());
|
||||
|
||||
self.cm
|
||||
.new_source_file(FileName::Custom(url.to_string()), src.to_string())
|
||||
}
|
||||
_ => unreachable!("this test only uses url"),
|
||||
};
|
||||
|
||||
let lexer = Lexer::new(
|
||||
Syntax::Typescript(TsConfig {
|
||||
decorators: true,
|
||||
tsx,
|
||||
..Default::default()
|
||||
}),
|
||||
JscTarget::Es2020,
|
||||
@ -215,12 +529,18 @@ impl Load for Loader {
|
||||
);
|
||||
|
||||
let mut parser = Parser::new_from(lexer);
|
||||
let module = parser.parse_typescript_module().unwrap();
|
||||
let module = module.fold_with(&mut decorators::decorators(decorators::Config {
|
||||
let module = parser.parse_module().unwrap();
|
||||
let module = module
|
||||
.fold_with(&mut decorators::decorators(decorators::Config {
|
||||
legacy: true,
|
||||
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 {
|
||||
fm,
|
||||
@ -235,6 +555,11 @@ struct Resolver;
|
||||
|
||||
impl Resolve for Resolver {
|
||||
fn resolve(&self, base: &FileName, module_specifier: &str) -> Result<FileName, Error> {
|
||||
match Url::parse(module_specifier) {
|
||||
Ok(v) => return Ok(FileName::Custom(v.to_string())),
|
||||
Err(_) => {}
|
||||
}
|
||||
|
||||
let base_url = match base {
|
||||
FileName::Custom(v) => v,
|
||||
_ => unreachable!("this test only uses url"),
|
||||
@ -287,3 +612,61 @@ impl swc_bundler::Hook for Hook {
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_exports(module: &Module) -> HashSet<String> {
|
||||
let mut v = ExportCollector::default();
|
||||
module.visit_with(module, &mut v);
|
||||
|
||||
v.exports
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ExportCollector {
|
||||
exports: HashSet<String>,
|
||||
}
|
||||
|
||||
impl Visit for ExportCollector {
|
||||
fn visit_export_specifier(&mut self, s: &ExportSpecifier, _: &dyn Node) {
|
||||
match s {
|
||||
ExportSpecifier::Namespace(ns) => {
|
||||
self.exports.insert(ns.name.sym.to_string());
|
||||
}
|
||||
ExportSpecifier::Default(_) => {
|
||||
self.exports.insert("default".into());
|
||||
}
|
||||
ExportSpecifier::Named(named) => {
|
||||
self.exports.insert(
|
||||
named
|
||||
.exported
|
||||
.as_ref()
|
||||
.unwrap_or(&named.orig)
|
||||
.sym
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_export_default_decl(&mut self, _: &ExportDefaultDecl, _: &dyn Node) {
|
||||
self.exports.insert("default".into());
|
||||
}
|
||||
|
||||
fn visit_export_default_expr(&mut self, _: &ExportDefaultExpr, _: &dyn Node) {
|
||||
self.exports.insert("default".into());
|
||||
}
|
||||
|
||||
fn visit_export_decl(&mut self, export: &ExportDecl, _: &dyn Node) {
|
||||
match &export.decl {
|
||||
swc_ecma_ast::Decl::Class(ClassDecl { ident, .. })
|
||||
| swc_ecma_ast::Decl::Fn(FnDecl { ident, .. }) => {
|
||||
self.exports.insert(ident.sym.to_string());
|
||||
}
|
||||
swc_ecma_ast::Decl::Var(var) => {
|
||||
let ids: Vec<Id> = find_ids(var);
|
||||
self.exports
|
||||
.extend(ids.into_iter().map(|v| v.0.to_string()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
4
bundler/tests/deno/issue-6802/input.tsx
Normal file
4
bundler/tests/deno/issue-6802/input.tsx
Normal 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"));
|
1
bundler/tests/deno/issue-8302/input.ts
Normal file
1
bundler/tests/deno/issue-8302/input.ts
Normal file
@ -0,0 +1 @@
|
||||
export { DB, Empty, Status } from 'https://deno.land/x/sqlite@v2.3.1/mod.ts';
|
3
bundler/tests/deno/issue-8314/input.ts
Normal file
3
bundler/tests/deno/issue-8314/input.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import createGraph from "https://dev.jspm.io/ngraph.graph";
|
||||
|
||||
console.log(createGraph);
|
2
bundler/tests/deno/issue-8399-1/input.ts
Normal file
2
bundler/tests/deno/issue-8399-1/input.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import * as yaml from "https://deno.land/std@0.78.0/encoding/yaml.ts"
|
||||
console.log(yaml && 1)
|
2
bundler/tests/deno/issue-8399-2/input.ts
Normal file
2
bundler/tests/deno/issue-8399-2/input.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import { stringify } from "https://deno.land/std@0.78.0/encoding/yaml.ts";
|
||||
console.log(stringify({ a: 1 }));
|
@ -0,0 +1,4 @@
|
||||
import { b } from './b';
|
||||
|
||||
export const a = 'a';
|
||||
export { b }
|
@ -0,0 +1,4 @@
|
||||
import { c } from './c';
|
||||
|
||||
export const b = 'b';
|
||||
export { c }
|
@ -0,0 +1,4 @@
|
||||
import { a } from './a';
|
||||
|
||||
export const c = 'c';
|
||||
export { a }
|
@ -0,0 +1,2 @@
|
||||
export { a } from './a';
|
||||
export { c } from './c';
|
@ -0,0 +1,6 @@
|
||||
const a2 = 'a';
|
||||
const a1 = a2;
|
||||
const c2 = 'c';
|
||||
const c1 = c2;
|
||||
export { a1 as a };
|
||||
export { c1 as c };
|
@ -0,0 +1,4 @@
|
||||
import { b } from './b';
|
||||
|
||||
export const a = 'a';
|
||||
export { b }
|
@ -0,0 +1,4 @@
|
||||
import { c } from './c';
|
||||
|
||||
export const b = 'b';
|
||||
export { c }
|
@ -0,0 +1,4 @@
|
||||
import { a } from './a';
|
||||
|
||||
export const c = 'c';
|
||||
export { a }
|
@ -0,0 +1,4 @@
|
||||
import { a } from './a';
|
||||
import { c } from './c';
|
||||
|
||||
console.log(a, c)
|
@ -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);
|
5
bundler/tests/fixture/circular-imports/simple/input/a.js
Normal file
5
bundler/tests/fixture/circular-imports/simple/input/a.js
Normal file
@ -0,0 +1,5 @@
|
||||
import { b } from './b';
|
||||
|
||||
export { b };
|
||||
|
||||
export const a = 1;
|
5
bundler/tests/fixture/circular-imports/simple/input/b.js
Normal file
5
bundler/tests/fixture/circular-imports/simple/input/b.js
Normal file
@ -0,0 +1,5 @@
|
||||
import { a } from './a';
|
||||
|
||||
export { a };
|
||||
|
||||
export const b = 2;
|
@ -0,0 +1,4 @@
|
||||
import { a } from './a';
|
||||
import { b } from './b';
|
||||
|
||||
console.log(a, b);
|
@ -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);
|
@ -1,8 +1,10 @@
|
||||
const foo = function() {
|
||||
const mod = function() {
|
||||
class A {
|
||||
}
|
||||
const A1 = A;
|
||||
return {
|
||||
A
|
||||
};
|
||||
}();
|
||||
const foo = mod;
|
||||
export { foo };
|
||||
|
@ -1,8 +1,10 @@
|
||||
const foo = function() {
|
||||
async function foo1() {
|
||||
const mod = function() {
|
||||
async function foo() {
|
||||
}
|
||||
const foo1 = foo;
|
||||
return {
|
||||
foo: foo1
|
||||
foo
|
||||
};
|
||||
}();
|
||||
const foo = mod;
|
||||
export { foo };
|
||||
|
@ -1,13 +1,17 @@
|
||||
const foo = function() {
|
||||
const mod = function() {
|
||||
const [a, b, c] = [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
];
|
||||
const a1 = a;
|
||||
const b1 = b;
|
||||
const c1 = c;
|
||||
return {
|
||||
a,
|
||||
b,
|
||||
c
|
||||
};
|
||||
}();
|
||||
const foo = mod;
|
||||
export { foo };
|
||||
|
@ -1,12 +1,14 @@
|
||||
const foo = {
|
||||
const __default = {
|
||||
"a": "a",
|
||||
"b": "b",
|
||||
"c": "c"
|
||||
};
|
||||
const foo1 = {
|
||||
const foo = __default;
|
||||
const __default1 = {
|
||||
"a": "a1",
|
||||
"b": "b1",
|
||||
"c": "c1"
|
||||
};
|
||||
const foo1 = __default1;
|
||||
console.log(foo);
|
||||
console.log(foo1);
|
||||
|
1
bundler/tests/fixture/deno-8188-1/input/a.ts
Normal file
1
bundler/tests/fixture/deno-8188-1/input/a.ts
Normal file
@ -0,0 +1 @@
|
||||
export const a = 1;
|
2
bundler/tests/fixture/deno-8188-1/input/b.ts
Normal file
2
bundler/tests/fixture/deno-8188-1/input/b.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import { a } from './a';
|
||||
export const b = a + 1;
|
2
bundler/tests/fixture/deno-8188-1/input/c.ts
Normal file
2
bundler/tests/fixture/deno-8188-1/input/c.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import { a } from './a';
|
||||
export const c = a + 2;
|
2
bundler/tests/fixture/deno-8188-1/input/entry.ts
Normal file
2
bundler/tests/fixture/deno-8188-1/input/entry.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import { a, b, c } from './lib';
|
||||
console.log(a, b, c)
|
3
bundler/tests/fixture/deno-8188-1/input/lib.ts
Normal file
3
bundler/tests/fixture/deno-8188-1/input/lib.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './a';
|
||||
export * from './b';
|
||||
export * from './c';
|
13
bundler/tests/fixture/deno-8188-1/output/entry.ts
Normal file
13
bundler/tests/fixture/deno-8188-1/output/entry.ts
Normal 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);
|
1
bundler/tests/fixture/deno-8188-2/input/a.ts
Normal file
1
bundler/tests/fixture/deno-8188-2/input/a.ts
Normal file
@ -0,0 +1 @@
|
||||
export const a = 1;
|
2
bundler/tests/fixture/deno-8188-2/input/b.ts
Normal file
2
bundler/tests/fixture/deno-8188-2/input/b.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import { a } from './a';
|
||||
export const b = a + 1;
|
2
bundler/tests/fixture/deno-8188-2/input/c.ts
Normal file
2
bundler/tests/fixture/deno-8188-2/input/c.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import { a } from './a';
|
||||
export const c = a + 2;
|
5
bundler/tests/fixture/deno-8188-2/input/entry.ts
Normal file
5
bundler/tests/fixture/deno-8188-2/input/entry.ts
Normal 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)
|
3
bundler/tests/fixture/deno-8188-2/input/lib.ts
Normal file
3
bundler/tests/fixture/deno-8188-2/input/lib.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './a';
|
||||
export * from './b';
|
||||
export * from './c';
|
3
bundler/tests/fixture/deno-8188-2/input/user1.ts
Normal file
3
bundler/tests/fixture/deno-8188-2/input/user1.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { a } from './a';
|
||||
export const user1 = a + 1;
|
||||
console.log('user 1', user1)
|
5
bundler/tests/fixture/deno-8188-2/input/user2.ts
Normal file
5
bundler/tests/fixture/deno-8188-2/input/user2.ts
Normal 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)
|
27
bundler/tests/fixture/deno-8188-2/output/entry.ts
Normal file
27
bundler/tests/fixture/deno-8188-2/output/entry.ts
Normal 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);
|
1
bundler/tests/fixture/deno-8188-3/input/a.ts
Normal file
1
bundler/tests/fixture/deno-8188-3/input/a.ts
Normal file
@ -0,0 +1 @@
|
||||
export const a = 1;
|
1
bundler/tests/fixture/deno-8188-3/input/b.ts
Normal file
1
bundler/tests/fixture/deno-8188-3/input/b.ts
Normal file
@ -0,0 +1 @@
|
||||
export { a as b } from './a';
|
2
bundler/tests/fixture/deno-8188-3/input/entry.ts
Normal file
2
bundler/tests/fixture/deno-8188-3/input/entry.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import { a, b } from './lib'
|
||||
console.log(a, b)
|
2
bundler/tests/fixture/deno-8188-3/input/lib.ts
Normal file
2
bundler/tests/fixture/deno-8188-3/input/lib.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './a';
|
||||
export * from './b';
|
7
bundler/tests/fixture/deno-8188-3/output/entry.ts
Normal file
7
bundler/tests/fixture/deno-8188-3/output/entry.ts
Normal 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);
|
@ -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 };
|
||||
|
@ -1,10 +1,18 @@
|
||||
class Zone {
|
||||
class Zone3 {
|
||||
}
|
||||
class FixedOffsetZone extends Zone {
|
||||
const __default = Zone3;
|
||||
const Zone1 = __default;
|
||||
class FixedOffsetZone3 extends Zone1 {
|
||||
}
|
||||
class Info {
|
||||
const __default1 = FixedOffsetZone3;
|
||||
const FixedOffsetZone1 = __default1;
|
||||
class Info2 {
|
||||
use() {
|
||||
console.log(FixedOffsetZone);
|
||||
console.log(FixedOffsetZone1);
|
||||
}
|
||||
}
|
||||
export { Zone, Info, FixedOffsetZone };
|
||||
const __default2 = Info2;
|
||||
const Info1 = __default2;
|
||||
const Zone2 = __default;
|
||||
const FixedOffsetZone2 = __default1;
|
||||
export { Zone2 as Zone, Info1 as Info, FixedOffsetZone2 as FixedOffsetZone };
|
||||
|
6
bundler/tests/fixture/deno-8302-1/input/a.js
Normal file
6
bundler/tests/fixture/deno-8302-1/input/a.js
Normal file
@ -0,0 +1,6 @@
|
||||
import { b } from './b';
|
||||
import { A } from './lib';
|
||||
|
||||
console.log(b, A);
|
||||
|
||||
export const a = 1;
|
6
bundler/tests/fixture/deno-8302-1/input/b.js
Normal file
6
bundler/tests/fixture/deno-8302-1/input/b.js
Normal file
@ -0,0 +1,6 @@
|
||||
import { c } from './c';
|
||||
import { A } from './lib';
|
||||
|
||||
console.log(c, A);
|
||||
|
||||
export const b = 2
|
6
bundler/tests/fixture/deno-8302-1/input/c.js
Normal file
6
bundler/tests/fixture/deno-8302-1/input/c.js
Normal file
@ -0,0 +1,6 @@
|
||||
import { a } from './a';
|
||||
import { A } from './lib';
|
||||
|
||||
console.log(a, A);
|
||||
|
||||
export const c = 3;
|
3
bundler/tests/fixture/deno-8302-1/input/entry.js
Normal file
3
bundler/tests/fixture/deno-8302-1/input/entry.js
Normal file
@ -0,0 +1,3 @@
|
||||
import { a } from './a';
|
||||
|
||||
console.log(a);
|
1
bundler/tests/fixture/deno-8302-1/input/lib.js
Normal file
1
bundler/tests/fixture/deno-8302-1/input/lib.js
Normal file
@ -0,0 +1 @@
|
||||
export class A { }
|
20
bundler/tests/fixture/deno-8302-1/output/entry.js
Normal file
20
bundler/tests/fixture/deno-8302-1/output/entry.js
Normal 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);
|
3
bundler/tests/fixture/deno-8325-1/input/entry.ts
Normal file
3
bundler/tests/fixture/deno-8325-1/input/entry.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export default function square(a) {
|
||||
return a * a;
|
||||
}
|
3
bundler/tests/fixture/deno-8325-1/output/entry.ts
Normal file
3
bundler/tests/fixture/deno-8325-1/output/entry.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export default function square(a) {
|
||||
return a * a;
|
||||
};
|
3
bundler/tests/fixture/deno-8325-2/input/entry.ts
Normal file
3
bundler/tests/fixture/deno-8325-2/input/entry.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import entry from "./lib"
|
||||
|
||||
console.log(entry());
|
3
bundler/tests/fixture/deno-8325-2/input/lib.ts
Normal file
3
bundler/tests/fixture/deno-8325-2/input/lib.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export default function square(a) {
|
||||
return a * a;
|
||||
}
|
6
bundler/tests/fixture/deno-8325-2/output/entry.ts
Normal file
6
bundler/tests/fixture/deno-8325-2/output/entry.ts
Normal file
@ -0,0 +1,6 @@
|
||||
function square(a) {
|
||||
return a * a;
|
||||
}
|
||||
const __default = square;
|
||||
const entry = __default;
|
||||
console.log(entry());
|
3
bundler/tests/fixture/deno-8344-1/input/entry.ts
Normal file
3
bundler/tests/fixture/deno-8344-1/input/entry.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { u } from "./v";
|
||||
|
||||
console.log(u("a"));
|
7
bundler/tests/fixture/deno-8344-1/input/u.js
Normal file
7
bundler/tests/fixture/deno-8344-1/input/u.js
Normal file
@ -0,0 +1,7 @@
|
||||
function u(str) {
|
||||
return str;
|
||||
}
|
||||
|
||||
export { u };
|
||||
export default null;
|
||||
export const __esModule = true;
|
2
bundler/tests/fixture/deno-8344-1/input/v.js
Normal file
2
bundler/tests/fixture/deno-8344-1/input/v.js
Normal file
@ -0,0 +1,2 @@
|
||||
export * from "./u";
|
||||
export { default } from "./u";
|
6
bundler/tests/fixture/deno-8344-1/output/entry.ts
Normal file
6
bundler/tests/fixture/deno-8344-1/output/entry.ts
Normal file
@ -0,0 +1,6 @@
|
||||
function u(str) {
|
||||
return str;
|
||||
}
|
||||
const u1 = u;
|
||||
const u2 = u1;
|
||||
console.log(u2("a"));
|
@ -2,23 +2,22 @@ function a() {
|
||||
console.log("a");
|
||||
}
|
||||
const a1 = a;
|
||||
var O;
|
||||
const a2 = a1;
|
||||
var O3;
|
||||
(function(O1) {
|
||||
O1[O1["A"] = 0] = "A";
|
||||
O1[O1["B"] = 1] = "B";
|
||||
O1[O1["C"] = 2] = "C";
|
||||
})(O || (O = {
|
||||
})(O3 || (O3 = {
|
||||
}));
|
||||
const O1 = O;
|
||||
const defaultA = a1;
|
||||
export { O1 as O };
|
||||
const defaultA = a2;
|
||||
class A {
|
||||
#a;
|
||||
#c;
|
||||
constructor(o = {
|
||||
}){
|
||||
const { a: a2 = defaultA , c , } = o;
|
||||
this.#a = a2;
|
||||
const { a: a3 = defaultA , c , } = o;
|
||||
this.#a = a3;
|
||||
this.#c = c;
|
||||
}
|
||||
a() {
|
||||
@ -28,6 +27,9 @@ class A {
|
||||
console.log(this.#c);
|
||||
}
|
||||
}
|
||||
let a3 = new A();
|
||||
a3.a();
|
||||
a3.c();
|
||||
let a4 = new A();
|
||||
a4.a();
|
||||
a4.c();
|
||||
const O1 = O3;
|
||||
const O2 = O1;
|
||||
export { O2 as O };
|
||||
|
@ -2,23 +2,22 @@ function a() {
|
||||
console.log("a");
|
||||
}
|
||||
const a1 = a;
|
||||
var O;
|
||||
const a2 = a1;
|
||||
var O3;
|
||||
(function(O1) {
|
||||
O1[O1["A"] = 0] = "A";
|
||||
O1[O1["B"] = 1] = "B";
|
||||
O1[O1["C"] = 2] = "C";
|
||||
})(O || (O = {
|
||||
})(O3 || (O3 = {
|
||||
}));
|
||||
const O1 = O;
|
||||
const defaultA = a1;
|
||||
export { O1 as O };
|
||||
const defaultA = a2;
|
||||
class A {
|
||||
#a;
|
||||
#c;
|
||||
constructor(o = {
|
||||
}){
|
||||
const { a: a2 = defaultA , c , } = o;
|
||||
this.#a = a2;
|
||||
const { a: a3 = defaultA , c , } = o;
|
||||
this.#a = a3;
|
||||
this.#c = c;
|
||||
}
|
||||
a() {
|
||||
@ -28,6 +27,9 @@ class A {
|
||||
console.log(this.#c);
|
||||
}
|
||||
}
|
||||
let a3 = new A();
|
||||
a3.a();
|
||||
a3.c();
|
||||
let a4 = new A();
|
||||
a4.a();
|
||||
a4.c();
|
||||
const O1 = O3;
|
||||
const O2 = O1;
|
||||
export { O2 as O };
|
||||
|
@ -2,7 +2,8 @@ function a() {
|
||||
console.log("a");
|
||||
}
|
||||
const a1 = a;
|
||||
const a2 = a1;
|
||||
function b() {
|
||||
a1();
|
||||
a2();
|
||||
}
|
||||
b();
|
||||
|
@ -1,6 +1,7 @@
|
||||
const a = 'a';
|
||||
const a1 = a;
|
||||
const a2 = a1;
|
||||
function b() {
|
||||
return a1;
|
||||
return a2;
|
||||
}
|
||||
b();
|
||||
|
@ -1,7 +1,8 @@
|
||||
class a {
|
||||
}
|
||||
const a1 = a;
|
||||
const a2 = a1;
|
||||
function b() {
|
||||
return new a1();
|
||||
return new a2();
|
||||
}
|
||||
b();
|
||||
|
@ -8,13 +8,15 @@ function d() {
|
||||
});
|
||||
return Object.assign(promise, methods);
|
||||
}
|
||||
const d1 = d;
|
||||
const d2 = d1;
|
||||
class A {
|
||||
s = d();
|
||||
s = d2();
|
||||
a() {
|
||||
this.s.resolve();
|
||||
}
|
||||
b() {
|
||||
this.s = d();
|
||||
this.s = d2();
|
||||
}
|
||||
}
|
||||
new A();
|
||||
|
@ -8,8 +8,10 @@ function d() {
|
||||
});
|
||||
return Object.assign(promise, methods);
|
||||
}
|
||||
const d1 = d;
|
||||
const d2 = d1;
|
||||
class A {
|
||||
s = d();
|
||||
s = d2();
|
||||
a() {
|
||||
this.s.resolve();
|
||||
}
|
||||
|
3
bundler/tests/fixture/reexport/idnex-style/input/a.ts
Normal file
3
bundler/tests/fixture/reexport/idnex-style/input/a.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export class A { }
|
||||
|
||||
export function utilForA() { }
|
3
bundler/tests/fixture/reexport/idnex-style/input/b.ts
Normal file
3
bundler/tests/fixture/reexport/idnex-style/input/b.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export class B { }
|
||||
|
||||
export function utilForB() { }
|
3
bundler/tests/fixture/reexport/idnex-style/input/c.ts
Normal file
3
bundler/tests/fixture/reexport/idnex-style/input/c.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export class C { }
|
||||
|
||||
export function utilForC() { }
|
@ -0,0 +1,3 @@
|
||||
import { A } from './lib';
|
||||
|
||||
console.log(A)
|
3
bundler/tests/fixture/reexport/idnex-style/input/lib.ts
Normal file
3
bundler/tests/fixture/reexport/idnex-style/input/lib.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './a';
|
||||
export * from './b';
|
||||
export * from './c';
|
@ -0,0 +1,5 @@
|
||||
class A {
|
||||
}
|
||||
const A1 = A;
|
||||
const A2 = A1;
|
||||
console.log(A2);
|
1
bundler/tests/fixture/reexport/nested/input/a.js
Normal file
1
bundler/tests/fixture/reexport/nested/input/a.js
Normal file
@ -0,0 +1 @@
|
||||
export * from './b';
|
1
bundler/tests/fixture/reexport/nested/input/b.js
Normal file
1
bundler/tests/fixture/reexport/nested/input/b.js
Normal file
@ -0,0 +1 @@
|
||||
export * from './c';
|
5
bundler/tests/fixture/reexport/nested/input/c.js
Normal file
5
bundler/tests/fixture/reexport/nested/input/c.js
Normal file
@ -0,0 +1,5 @@
|
||||
export const a = 1;
|
||||
|
||||
export function foo() { }
|
||||
|
||||
export class Class { }
|
1
bundler/tests/fixture/reexport/nested/input/entry.js
Normal file
1
bundler/tests/fixture/reexport/nested/input/entry.js
Normal file
@ -0,0 +1 @@
|
||||
export * from './a';
|
5
bundler/tests/fixture/reexport/nested/output/entry.js
Normal file
5
bundler/tests/fixture/reexport/nested/output/entry.js
Normal 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
Loading…
Reference in New Issue
Block a user