bundler: Handle export * properly (#1083)

This commit is contained in:
강동윤 2020-09-18 18:30:02 +09:00 committed by GitHub
parent b0049c0fa6
commit fa756a1b48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 321 additions and 11 deletions

View File

@ -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]

View File

@ -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();
@ -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 {

View File

@ -326,6 +326,7 @@ where
// }
if is_entry {
// print_hygiene("done", &self.cm, &entry);
self.finalize_merging_of_entry(plan, &mut entry);
}
@ -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;
}

View File

@ -0,0 +1,9 @@
{
"jsc": {
"target": "es2020",
"parser": {
"syntax": "typescript",
"decorators": true
}
}
}

View File

@ -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>;
}

View File

@ -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)
);
}

View File

@ -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";

View File

@ -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();
}
}

View File

@ -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();
}

View File

@ -0,0 +1 @@
export * from './async/mod'

View File

@ -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 };