Skip to content

Commit

Permalink
bundler: Handle export * properly (#1083)
Browse files Browse the repository at this point in the history
  • Loading branch information
kdy1 authored Sep 18, 2020
1 parent b0049c0 commit fa756a1
Show file tree
Hide file tree
Showing 11 changed files with 321 additions and 11 deletions.
2 changes: 1 addition & 1 deletion bundler/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ edition = "2018"
license = "Apache-2.0/MIT"
name = "swc_bundler"
repository = "https://github.com/swc-project/swc.git"
version = "0.7.3"
version = "0.7.4"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
Expand Down
61 changes: 51 additions & 10 deletions bundler/src/bundler/chunk/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,19 @@ where
.collect::<Vec<_>>();

{
let mut decls = vec![];
let mut normal_reexports = vec![];
let mut star_reexports = vec![];
for (src, mut specifiers) in additional_modules {
// If a dependency is indirect, we need to export items from it manually.
let is_indirect = !nomral_plan.chunks.contains(&src.module_id);

let add_to = if specifiers.is_empty() && is_indirect {
// User provided code like `export * from './foo';`, but planner decide to merge
// it within dependency module. So we reexport them using a named export.
&mut star_reexports
} else {
&mut normal_reexports
};
if specifiers.is_empty() {
//
let dep = self.scope.get_module(src.module_id).unwrap();
Expand All @@ -146,26 +157,56 @@ where
unimplemented!("namespaced re-export: local={:?}, all={}", local, all)
}
};
let var = VarDeclarator {
span: DUMMY_SP,
name: Pat::Ident(imported),
init: Some(Box::new(Expr::Ident(exported))),
definite: false,
};
decls.push(var);

add_to.push((imported, exported));
}
}

if !decls.is_empty() {
if !normal_reexports.is_empty() {
entry
.body
.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(VarDecl {
span: DUMMY_SP,
kind: VarDeclKind::Const,
declare: false,
decls,
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,
},
)));
}
}

for dep in deps {
Expand Down
3 changes: 3 additions & 0 deletions bundler/src/bundler/chunk/merge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ where
// }

if is_entry {
// print_hygiene("done", &self.cm, &entry);
self.finalize_merging_of_entry(plan, &mut entry);
}

Expand All @@ -336,6 +337,8 @@ where
fn finalize_merging_of_entry(&self, plan: &Plan, entry: &mut Module) {
entry.body.retain_mut(|item| {
match item {
ModuleItem::ModuleDecl(ModuleDecl::ExportAll(..)) => return false,

ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(export)) => {
export.src = None;
}
Expand Down
9 changes: 9 additions & 0 deletions spack/tests/pass/deno-002/full/input/.swcrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"jsc": {
"target": "es2020",
"parser": {
"syntax": "typescript",
"decorators": true
}
}
}
26 changes: 26 additions & 0 deletions spack/tests/pass/deno-002/full/input/async/deferred.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
// TODO(ry) It'd be better to make Deferred a class that inherits from
// Promise, rather than an interface. This is possible in ES2016, however
// typescript produces broken code when targeting ES5 code.
// See https://github.com/Microsoft/TypeScript/issues/15202
// At the time of writing, the github issue is closed but the problem remains.
export interface Deferred<T> extends Promise<T> {
resolve: (value?: T | PromiseLike<T>) => void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
reject: (reason?: any) => void;
}

/** Creates a Promise with the `reject` and `resolve` functions
* placed as methods on the promise object itself. It allows you to do:
*
* const p = deferred<number>();
* // ...
* p.resolve(42);
*/
export function deferred<T>(): Deferred<T> {
let methods;
const promise = new Promise<T>((resolve, reject): void => {
methods = { resolve, reject };
});
return Object.assign(promise, methods) as Deferred<T>;
}
9 changes: 9 additions & 0 deletions spack/tests/pass/deno-002/full/input/async/delay.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
/* Resolves after the given number of milliseconds. */
export function delay(ms: number): Promise<void> {
return new Promise((res): number =>
setTimeout((): void => {
res();
}, ms)
);
}
5 changes: 5 additions & 0 deletions spack/tests/pass/deno-002/full/input/async/mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
export * from "./deferred";
export * from "./delay";
export * from "./mux_async_iterator";
export * from "./pool";
69 changes: 69 additions & 0 deletions spack/tests/pass/deno-002/full/input/async/mux_async_iterator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { Deferred, deferred } from "./deferred.ts";

interface TaggedYieldedValue<T> {
iterator: AsyncIterableIterator<T>;
value: T;
}

/** The MuxAsyncIterator class multiplexes multiple async iterators into a
* single stream. It currently makes an assumption:
* - The final result (the value returned and not yielded from the iterator)
* does not matter; if there is any, it is discarded.
*/
export class MuxAsyncIterator<T> implements AsyncIterable<T> {
private iteratorCount = 0;
private yields: Array<TaggedYieldedValue<T>> = [];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private throws: any[] = [];
private signal: Deferred<void> = deferred();

add(iterator: AsyncIterableIterator<T>): void {
++this.iteratorCount;
this.callIteratorNext(iterator);
}

private async callIteratorNext(
iterator: AsyncIterableIterator<T>,
): Promise<void> {
try {
const { value, done } = await iterator.next();
if (done) {
--this.iteratorCount;
} else {
this.yields.push({ iterator, value });
}
} catch (e) {
this.throws.push(e);
}
this.signal.resolve();
}

async *iterate(): AsyncIterableIterator<T> {
while (this.iteratorCount > 0) {
// Sleep until any of the wrapped iterators yields.
await this.signal;

// Note that while we're looping over `yields`, new items may be added.
for (let i = 0; i < this.yields.length; i++) {
const { iterator, value } = this.yields[i];
yield value;
this.callIteratorNext(iterator);
}

if (this.throws.length) {
for (const e of this.throws) {
throw e;
}
this.throws.length = 0;
}
// Clear the `yields` list and reset the `signal` promise.
this.yields.length = 0;
this.signal = deferred();
}
}

[Symbol.asyncIterator](): AsyncIterableIterator<T> {
return this.iterate();
}
}
46 changes: 46 additions & 0 deletions spack/tests/pass/deno-002/full/input/async/pool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.

/**
* pooledMap transforms values from an (async) iterable into another async
* iterable. The transforms are done concurrently, with a max concurrency
* defined by the poolLimit.
*
* @param poolLimit The maximum count of items being processed concurrently.
* @param array The input array for mapping.
* @param iteratorFn The function to call for every item of the array.
*/
export function pooledMap<T, R>(
poolLimit: number,
array: Iterable<T> | AsyncIterable<T>,
iteratorFn: (data: T) => Promise<R>,
): AsyncIterableIterator<R> {
// Create the async iterable that is returned from this function.
const res = new TransformStream<Promise<R>, R>({
async transform(
p: Promise<R>,
controller: TransformStreamDefaultController<R>,
): Promise<void> {
controller.enqueue(await p);
},
});
// Start processing items from the iterator
(async (): Promise<void> => {
const writer = res.writable.getWriter();
const executing: Array<Promise<unknown>> = [];
for await (const item of array) {
const p = Promise.resolve().then(() => iteratorFn(item));
writer.write(p);
const e: Promise<unknown> = p.then(() =>
executing.splice(executing.indexOf(e), 1)
);
executing.push(e);
if (executing.length >= poolLimit) {
await Promise.race(executing);
}
}
// Wait until all ongoing events have processed, then close the writer.
await Promise.all(executing);
writer.close();
})();
return res.readable.getIterator();
}
1 change: 1 addition & 0 deletions spack/tests/pass/deno-002/full/input/entry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './async/mod'
101 changes: 101 additions & 0 deletions spack/tests/pass/deno-002/full/output/entry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
/* Resolves after the given number of milliseconds. */ export function delay(ms) {
return new Promise((res)=>setTimeout(()=>{
res();
}, ms)
);
}
function deferred1() {
let methods;
const promise = new Promise((resolve, reject)=>{
});
return Object.assign(promise, methods);
}
var tmp = Symbol.asyncIterator;
/** The MuxAsyncIterator class multiplexes multiple async iterators into a
* single stream. It currently makes an assumption:
* - The final result (the value returned and not yielded from the iterator)
* does not matter; if there is any, it is discarded.
*/ export class MuxAsyncIterator {
add(iterator) {
++this.iteratorCount;
this.callIteratorNext(iterator);
}
async callIteratorNext(iterator) {
try {
const { value , done } = await iterator.next();
if (done) --this.iteratorCount;
else this.yields.push({
iterator,
value
});
} catch (e) {
this.throws.push(e);
}
this.signal.resolve();
}
async *iterate() {
while(this.iteratorCount > 0){
// Sleep until any of the wrapped iterators yields.
await this.signal;
// Note that while we're looping over `yields`, new items may be added.
for(let i = 0; i < this.yields.length; i++){
const { iterator , value } = this.yields[i];
yield value;
this.callIteratorNext(iterator);
}
if (this.throws.length) {
for (const e of this.throws)throw e;
this.throws.length = 0;
}
// Clear the `yields` list and reset the `signal` promise.
this.yields.length = 0;
this.signal = deferred1();
}
}
[tmp]() {
return this.iterate();
}
constructor(){
this.iteratorCount = 0;
this.yields = [];
this.throws = [];
this.signal = deferred1();
}
}
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
/**
* pooledMap transforms values from an (async) iterable into another async
* iterable. The transforms are done concurrently, with a max concurrency
* defined by the poolLimit.
*
* @param poolLimit The maximum count of items being processed concurrently.
* @param array The input array for mapping.
* @param iteratorFn The function to call for every item of the array.
*/ export function pooledMap(poolLimit, array, iteratorFn) {
// Create the async iterable that is returned from this function.
const res = new TransformStream({
async transform (p, controller) {
controller.enqueue(await p);
}
});
// Start processing items from the iterator
(async ()=>{
const writer = res.writable.getWriter();
const executing = [];
for await (const item of array){
const p = Promise.resolve().then(()=>iteratorFn(item)
);
writer.write(p);
const e = p.then(()=>executing.splice(executing.indexOf(e), 1)
);
executing.push(e);
if (executing.length >= poolLimit) await Promise.race(executing);
}
// Wait until all ongoing events have processed, then close the writer.
await Promise.all(executing);
writer.close();
})();
return res.readable.getIterator();
}
export { deferred1 as deferred };

0 comments on commit fa756a1

Please sign in to comment.