Repository: parcel-bundler/parcel
Pull request #4366 (2020-03-22 23:35:37)

Custom faster ESM to CommonJS transformer
Currently, when not scope hoisting, we compile ES modules to CommonJS using `@babel/plugin-transform-modules-commonjs`. I noticed while benchmarking that this appeared to be quite slow - by far the slowest thing we do in Parcel. Looking through the source code, it appears to do multiple traversals over the entire AST. I'm sure there's a reason for that, but I don't think we need to do it for our use in Parcel. In addition, using `@babel/core` has some overhead over just using `@babel/traverse` directly.

This PR implements a new AST visitor that transforms ESM to CommonJS in a single pass over only the top-level statements in the program. Using scope information collected by babel traverse already, we can replace references without doing multiple full program traverses. On the esbuild benchmark, this improved overall build performance by ~25%!

In addition, builds may be slightly smaller because the helpers for various interop functionality are not duplicated in every file but included once in a separate asset.

height[bot]: > Link [Height](https://parcel.height.app) tasks by mentioning a task ID in the pull request title or description, commit messages, or comments.
>
> You can also use "Close T-X" to automatically close a task when the pull request is merged.
parcel-benchmark: ## Benchmark Results
<details><summary>packages/benchmarks/three 🚨</summary><p>

#### Timings

| Description | Time | Difference |
| --- | --- | --- |
| Cold | **FAILED** | -0.00ms |
| Cached | **FAILED** | -0.00ms |

#### Cold Bundles

*No bundles found, this is probably a failed build...*
#### Cached Bundles

*No bundles found, this is probably a failed build...*
</p></details>

<details><summary>packages/benchmarks/kitchen-sink 🚨</summary><p>

#### Timings

| Description | Time | Difference |
| --- | --- | --- |
| Cold | **FAILED** | -0.00ms |
| Cached | **FAILED** | -0.00ms |

#### Cold Bundles

*No bundles found, this is probably a failed build...*
#### Cached Bundles

*No bundles found, this is probably a failed build...*
</p></details>

<details><summary>packages/benchmarks/react-hn 🚨</summary><p>

#### Timings

| Description | Time | Difference |
| --- | --- | --- |
| Cold | **FAILED** | -0.00ms |
| Cached | **FAILED** | -0.00ms |

#### Cold Bundles

*No bundles found, this is probably a failed build...*
#### Cached Bundles

*No bundles found, this is probably a failed build...*
</p></details>

<details><summary>packages/benchmarks/ak-editor 🚨</summary><p>

#### Timings

| Description | Time | Difference |
| --- | --- | --- |
| Cold | **FAILED** | -0.00ms |
| Cached | **FAILED** | -0.00ms |

#### Cold Bundles

*No bundles found, this is probably a failed build...*
#### Cached Bundles

*No bundles found, this is probably a failed build...*
</p></details>

**[Click here to view a detailed benchmark overview.](https://benchmark.parceljs.org/benchmark/260742428503310849)**
mischnic: You made casts to any and used `invariant(node.type ==` in many places.
Flow understands `if(isIdentifier(..)) { }` but not `if(t.isIdentifier(..)) { }`, so replace every occurence `t.is*` with `is*` and import them directly `import {isIdentifer} from '@babel/types'`.

And `NodePath<any>` should be `NodePath<Node>` (import that type from `@babel/types`).
devongovett: > Flow understands if(isIdentifier(..)) { } but not if(t.isIdentifier(..)) { }

Oh, what? Why is that? 
mischnic: For no particular reason, it's just not implemented: https://github.com/facebook/flow/pull/7735
mischnic: @devongovett Do you still want to merge this? Should I implement the flow changes I suggested?
devongovett: Yeah I just haven’t had time yet, sorry!
mischnic: In case we still want to do this:

1. The babel transformer replaces `this` in the module scope with `undefined` (if we don't do this in the transformer, the prelude wrapper could do `.call(module.isES6 ? undefined : module.exports)`)
2. Multiple reexports with (non)conflicting symbols should be handled gracefully: https://github.com/babel/babel/issues/11710
devongovett: Finally had time to update this again. Made it even faster by completely removing `@babel/traverse` and using the traversal function I developed for the hoist/link perf PRs.

```
@babel/plugin-transform-modules-commonjs: 8.76s
custom transform using babel-traverse (existing PR): 6.92s (~21% faster)
custom transform without babel-traverse: 4.25s (~51% faster)
```
mischnic: We should also make sure that duplicate reexports works correctly (which [we fixed previously in Babel's transformer](https://github.com/babel/babel/issues/11710)): https://github.com/parcel-bundler/parcel/issues/4399#issuecomment-605636663
mischnic: This should print `1 4`, but after bundling it's `undefined 4` (namespace import includes default, only namespace reexport skips it)
```js
// other.mjs
import * as x from "./other.mjs";

export default 1;
export const a = 4;

console.log(x.default, x.a);
```
mischnic: Does this support https://github.com/tc39/proposal-export-default-from ? We have the parser plugin enabled by default (`exportDefaultFrom` in babel-ast-utils)
devongovett: No it doesn't. Should it given it's only a proposal?
mischnic: Should we remove the parser plugin as well then? At the moment it parses only if the babel transformer doesn't run and then you get an unhelpful assertion error from the babel cjs-esm plugin

https://github.com/parcel-bundler/parcel/issues/5452#issuecomment-743153918
devongovett: Ah. Well maybe it should be the other way then? Disable the parser plugin by default, but do support it in the transformer. That way people can enable it by adding the syntax plugin to their babel config. If we didn't support transforming it, then they'd also have to use babel's modules transform but that might break some scope hoisting stuff...
devongovett: Ok I actually removed all parser plugins from both babel transformer and js transformer. Both dynamic import and export namespace declarations are supported by default now. Export namespace is stage 4 and supported natively in chrome, so I've added support for that in the modules transformer. I added default export support as well, but it must be enabled by adding `@babel/plugin-syntax-export-default-from` to turn on the parser plugin.

Patch:
diff --git a/packages/core/core/src/AssetGraph.js b/packages/core/core/src/AssetGraph.js
index 941f94f73..ad5bd8260 100644
--- a/packages/core/core/src/AssetGraph.js
+++ b/packages/core/core/src/AssetGraph.js
@@ -17,7 +17,11 @@ import type {
 
 import invariant from 'assert';
 import crypto from 'crypto';
-import {md5FromObject} from '@parcel/utils';
+import {
+  md5FromObject,
+  md5FromOrderedObject,
+  objectSortedEntries,
+} from '@parcel/utils';
 import nullthrows from 'nullthrows';
 import Graph, {type GraphOpts} from './Graph';
 import {createDependency} from './Dependency';
@@ -55,12 +59,15 @@ export function nodeFromDep(dep: Dependency): DependencyNode {
 
 export function nodeFromAssetGroup(assetGroup: AssetGroup): AssetGroupNode {
   return {
-    id: md5FromObject({
-      ...assetGroup,
-      // only influences building the asset graph
-      canDefer: undefined,
-      // if only the isURL property is different, no need to re-run transformation.
-      isURL: undefined,
+    id: md5FromOrderedObject({
+      filePath: assetGroup.filePath,
+      env: assetGroup.env.id,
+      isSource: assetGroup.isSource,
+      sideEffects: assetGroup.sideEffects,
+      code: assetGroup.code,
+      pipeline: assetGroup.pipeline,
+      query: assetGroup.query ? objectSortedEntries(assetGroup.query) : null,
+      invalidations: assetGroup.invalidations,
     }),
     type: 'asset_group',
     value: assetGroup,
diff --git a/packages/core/core/src/AssetGraphBuilder.js b/packages/core/core/src/AssetGraphBuilder.js
index 77883f9cb..3dc558bfb 100644
--- a/packages/core/core/src/AssetGraphBuilder.js
+++ b/packages/core/core/src/AssetGraphBuilder.js
@@ -32,7 +32,7 @@ import nullthrows from 'nullthrows';
 import path from 'path';
 import {
   escapeMarkdown,
-  md5FromObject,
+  md5FromOrderedObject,
   md5FromString,
   PromiseQueue,
   flatMap,
@@ -105,7 +105,7 @@ export default class AssetGraphBuilder extends EventEmitter {
     this.workerFarm = workerFarm;
     this.assetRequests = [];
 
-    this.cacheKey = md5FromObject({
+    this.cacheKey = md5FromOrderedObject({
       parcelVersion: PARCEL_VERSION,
       name,
       entries,
diff --git a/packages/core/core/src/BundlerRunner.js b/packages/core/core/src/BundlerRunner.js
index eab0e0866..55a486f83 100644
--- a/packages/core/core/src/BundlerRunner.js
+++ b/packages/core/core/src/BundlerRunner.js
@@ -24,7 +24,7 @@ import MutableBundleGraph from './public/MutableBundleGraph';
 import {Bundle, NamedBundle} from './public/Bundle';
 import {report} from './ReporterRunner';
 import dumpGraphToGraphViz from './dumpGraphToGraphViz';
-import {normalizeSeparators, unique, md5FromObject} from '@parcel/utils';
+import {normalizeSeparators, unique, md5FromOrderedObject} from '@parcel/utils';
 import PluginOptions from './public/PluginOptions';
 import applyRuntimes from './applyRuntimes';
 import {PARCEL_VERSION} from './constants';
@@ -169,7 +169,7 @@ export default class BundlerRunner {
     let name = this.config.getBundlerName();
     let {version} = await this.config.getBundler();
 
-    return md5FromObject({
+    return md5FromOrderedObject({
       parcelVersion: PARCEL_VERSION,
       name,
       version,
diff --git a/packages/core/core/src/Dependency.js b/packages/core/core/src/Dependency.js
index aa17a6dbc..09010dbc0 100644
--- a/packages/core/core/src/Dependency.js
+++ b/packages/core/core/src/Dependency.js
@@ -5,9 +5,8 @@ import type {
   ModuleSpecifier,
   Symbol,
 } from '@parcel/types';
-import {md5FromObject} from '@parcel/utils';
+import {md5FromOrderedObject} from '@parcel/utils';
 import type {Dependency, Environment, Target} from './types';
-import {getEnvironmentHash} from './Environment';
 
 type DependencyOpts = {|
   id?: string,
@@ -22,6 +21,7 @@ type DependencyOpts = {|
   loc?: SourceLocation,
   env: Environment,
   meta?: Meta,
+  resolveFrom?: string,
   target?: Target,
   symbols?: Map<
     Symbol,
@@ -33,10 +33,10 @@ type DependencyOpts = {|
 export function createDependency(opts: DependencyOpts): Dependency {
   let id =
     opts.id ||
-    md5FromObject({
+    md5FromOrderedObject({
       sourceAssetId: opts.sourceAssetId,
       moduleSpecifier: opts.moduleSpecifier,
-      env: getEnvironmentHash(opts.env),
+      env: opts.env.id,
       target: opts.target,
       pipeline: opts.pipeline,
     });
diff --git a/packages/core/core/src/Environment.js b/packages/core/core/src/Environment.js
index 78a8316a5..084010f4d 100644
--- a/packages/core/core/src/Environment.js
+++ b/packages/core/core/src/Environment.js
@@ -1,7 +1,7 @@
 // @flow
 import type {EnvironmentOpts} from '@parcel/types';
 import type {Environment} from './types';
-import {md5FromObject} from '@parcel/utils';
+import {md5FromOrderedObject} from '@parcel/utils';
 
 const DEFAULT_ENGINES = {
   browsers: ['> 0.25%'],
@@ -78,7 +78,8 @@ export function createEnvironment({
     }
   }
 
-  return {
+  let res: Environment = {
+    id: '',
     context,
     engines,
     includeNodeModules,
@@ -88,6 +89,9 @@ export function createEnvironment({
     scopeHoist,
     sourceMap,
   };
+
+  res.id = getEnvironmentHash(res);
+  return res;
 }
 
 export function mergeEnvironments(
@@ -95,20 +99,21 @@ export function mergeEnvironments(
   b: ?EnvironmentOpts,
 ): Environment {
   // If merging the same object, avoid copying.
-  if (a === b) {
+  if (a === b || !b) {
     return a;
   }
 
+  // $FlowFixMe - ignore the `id` that is already on a
   return createEnvironment({
     ...a,
     ...b,
   });
 }
 
-export function getEnvironmentHash(env: Environment): string {
+function getEnvironmentHash(env: Environment): string {
   // context is excluded from hash so that assets can be shared between e.g. workers and browser.
   // Different engines should be sufficient to distinguish multi-target builds.
-  return md5FromObject({
+  return md5FromOrderedObject({
     engines: env.engines,
     includeNodeModules: env.includeNodeModules,
     outputFormat: env.outputFormat,
diff --git a/packages/core/core/src/PackagerRunner.js b/packages/core/core/src/PackagerRunner.js
index 3243736c2..8f5b628e8 100644
--- a/packages/core/core/src/PackagerRunner.js
+++ b/packages/core/core/src/PackagerRunner.js
@@ -19,7 +19,7 @@ import type {FileSystem, FileOptions} from '@parcel/fs';
 
 import invariant from 'assert';
 import {
-  md5FromObject,
+  md5FromOrderedObject,
   md5FromString,
   blobToStream,
   TapStream,
@@ -481,7 +481,7 @@ export default class PackagerRunner {
 
     // TODO: add third party configs to the cache key
     let {publicUrl} = bundle.target;
-    return md5FromObject({
+    return md5FromOrderedObject({
       parcelVersion: PARCEL_VERSION,
       packager,
       optimizers,
diff --git a/packages/core/core/src/ParcelConfig.js b/packages/core/core/src/ParcelConfig.js
index a9bf55983..c57f9f39b 100644
--- a/packages/core/core/src/ParcelConfig.js
+++ b/packages/core/core/src/ParcelConfig.js
@@ -23,7 +23,7 @@ import type {
   PureParcelConfigPipeline,
   ExtendableParcelConfigPipeline,
 } from './types';
-import {isMatch} from 'micromatch';
+import {makeRe} from 'micromatch';
 import {basename} from 'path';
 import loadPlugin from './loadParcelPlugin';
 
@@ -59,6 +59,7 @@ export default class ParcelConfig {
   reporters: PureParcelConfigPipeline;
   pluginCache: Map<PackageName, any>;
   autoinstall: boolean;
+  regexCache: Map<string, RegExp>;
 
   constructor(
     config: ProcessedParcelConfig,
@@ -80,6 +81,7 @@ export default class ParcelConfig {
     this.validators = config.validators || {};
     this.pluginCache = new Map();
     this.autoinstall = autoinstall;
+    this.regexCache = new Map();
   }
 
   static deserialize(serialized: SerializedParcelConfig): ParcelConfig {
@@ -343,10 +345,15 @@ export default class ParcelConfig {
       patternPipeline = null;
     }
 
+    let re = this.regexCache.get(patternGlob);
+    if (!re) {
+      re = makeRe(patternGlob, {dot: true});
+      this.regexCache.set(patternGlob, re);
+    }
+
     return (
       (pipeline === patternPipeline || (!pipeline && !patternPipeline)) &&
-      (isMatch(filePath, patternGlob, {dot: true}) ||
-        isMatch(basename(filePath), patternGlob, {dot: true}))
+      (re.test(filePath) || re.test(basename(filePath)))
     );
   }
 
diff --git a/packages/core/core/src/Transformation.js b/packages/core/core/src/Transformation.js
index fdf493d9e..b3dae1949 100644
--- a/packages/core/core/src/Transformation.js
+++ b/packages/core/core/src/Transformation.js
@@ -23,7 +23,7 @@ import path from 'path';
 import nullthrows from 'nullthrows';
 import {
   escapeMarkdown,
-  md5FromObject,
+  md5FromOrderedObject,
   normalizeSeparators,
 } from '@parcel/utils';
 import logger, {PluginLogger} from '@parcel/logger';
@@ -448,7 +448,7 @@ export default class Transformation {
     await Promise.all(
       assets.map(asset =>
         asset.commit(
-          md5FromObject({
+          md5FromOrderedObject({
             configs: getImpactfulConfigInfo(configs),
           }),
         ),
@@ -472,7 +472,7 @@ export default class Transformation {
       uniqueKey: a.value.uniqueKey,
     }));
 
-    return md5FromObject({
+    return md5FromOrderedObject({
       parcelVersion: PARCEL_VERSION,
       assets: assetsKeyInfo,
       configs: getImpactfulConfigInfo(configs),
@@ -745,6 +745,7 @@ function normalizeAssets(
       }
 
       let internalAsset = mutableAssetToUncommittedAsset(result);
+      // $FlowFixMe - ignore id already on env
       return {
         ast: internalAsset.ast,
         content: await internalAsset.content,
diff --git a/packages/core/core/src/UncommittedAsset.js b/packages/core/core/src/UncommittedAsset.js
index 4386d7d19..3493b04ad 100644
--- a/packages/core/core/src/UncommittedAsset.js
+++ b/packages/core/core/src/UncommittedAsset.js
@@ -105,14 +105,7 @@ export default class UncommittedAsset {
     // and hash while it's being written to the cache.
     await Promise.all([
       contentKey != null &&
-        this.options.cache.setStream(
-          contentKey,
-          this.getStream().pipe(
-            new TapStream(buf => {
-              size += buf.length;
-            }),
-          ),
-        ),
+        this.commitContent(contentKey).then(s => (size = s)),
       this.mapBuffer != null &&
         mapKey != null &&
         this.options.cache.setBlob(mapKey, this.mapBuffer),
@@ -141,6 +134,36 @@ export default class UncommittedAsset {
     this.value.committed = true;
   }
 
+  async commitContent(contentKey: string): Promise<number> {
+    let content = await this.content;
+    if (content == null) {
+      return 0;
+    }
+
+    let size = 0;
+    if (content instanceof Readable) {
+      await this.options.cache.setStream(
+        contentKey,
+        content.pipe(
+          new TapStream(buf => {
+            size += buf.length;
+          }),
+        ),
+      );
+
+      return size;
+    }
+
+    if (typeof content === 'string') {
+      size = Buffer.byteLength(content);
+    } else {
+      size = content.length;
+    }
+
+    await this.options.cache.setBlob(contentKey, content);
+    return size;
+  }
+
   async getCode(): Promise<string> {
     if (this.ast != null && this.isASTDirty) {
       throw new Error(
diff --git a/packages/core/core/src/applyRuntimes.js b/packages/core/core/src/applyRuntimes.js
index 593e739d7..7a2a6e359 100644
--- a/packages/core/core/src/applyRuntimes.js
+++ b/packages/core/core/src/applyRuntimes.js
@@ -70,7 +70,6 @@ export default async function applyRuntimes({
               // Runtime assets should be considered source, as they should be
               // e.g. compiled to run in the target environment
               isSource: true,
-              query: {},
             };
 
             connections.push({
diff --git a/packages/core/core/src/assetUtils.js b/packages/core/core/src/assetUtils.js
index fd6e45ed9..ddcd270ad 100644
--- a/packages/core/core/src/assetUtils.js
+++ b/packages/core/core/src/assetUtils.js
@@ -36,7 +36,6 @@ import {
   md5FromString,
   md5FromFilePath,
 } from '@parcel/utils';
-import {getEnvironmentHash} from './Environment';
 import {hashFromOption} from './utils';
 
 type AssetOptions = {|
@@ -45,7 +44,7 @@ type AssetOptions = {|
   hash?: ?string,
   idBase?: ?string,
   filePath: FilePath,
-  query?: QueryParameters,
+  query?: ?QueryParameters,
   type: string,
   contentKey?: ?string,
   mapKey?: ?string,
@@ -79,14 +78,14 @@ export function createAsset(options: AssetOptions): Asset {
         : md5FromString(
             idBase +
               options.type +
-              getEnvironmentHash(options.env) +
+              options.env.id +
               uniqueKey +
               (options.pipeline ?? ''),
           ),
     committed: options.committed ?? false,
     hash: options.hash,
     filePath: options.filePath,
-    query: options.query || {},
+    query: options.query,
     isIsolated: options.isIsolated ?? false,
     isInline: options.isInline ?? false,
     isSplittable: options.isSplittable,
@@ -222,6 +221,10 @@ export async function getInvalidationHash(
   invalidations: Array<RequestInvalidation>,
   options: ParcelOptions,
 ): Promise<string> {
+  if (invalidations.length === 0) {
+    return '';
+  }
+
   let sortedInvalidations = invalidations
     .slice()
     .sort((a, b) => (getInvalidationId(a) < getInvalidationId(b) ? -1 : 1));
diff --git a/packages/core/core/src/public/Asset.js b/packages/core/core/src/public/Asset.js
index 08fa80e66..5138a2fff 100644
--- a/packages/core/core/src/public/Asset.js
+++ b/packages/core/core/src/public/Asset.js
@@ -108,7 +108,7 @@ class BaseAsset {
   }
 
   get query(): QueryParameters {
-    return this.#asset.value.query;
+    return this.#asset.value.query ?? {};
   }
 
   get meta(): Meta {
diff --git a/packages/core/core/src/public/Config.js b/packages/core/core/src/public/Config.js
index d1777ad1a..8ed3fc23f 100644
--- a/packages/core/core/src/public/Config.js
+++ b/packages/core/core/src/public/Config.js
@@ -109,6 +109,10 @@ export default class PublicConfig implements IConfig {
       }
     }
 
+    if (filePaths.length === 0) {
+      return null;
+    }
+
     let parse = options && options.parse;
     let conf = await loadConfig(
       this.#options.inputFS,
diff --git a/packages/core/core/src/public/Dependency.js b/packages/core/core/src/public/Dependency.js
index 37985417a..ced95aa22 100644
--- a/packages/core/core/src/public/Dependency.js
+++ b/packages/core/core/src/public/Dependency.js
@@ -108,6 +108,10 @@ export default class Dependency implements IDependency {
     return this.#dep.sourcePath;
   }
 
+  get resolveFrom(): ?string {
+    return this.#dep.resolveFrom ?? this.#dep.sourcePath;
+  }
+
   get pipeline(): ?string {
     return this.#dep.pipeline;
   }
diff --git a/packages/core/core/src/requests/AssetRequest.js b/packages/core/core/src/requests/AssetRequest.js
index 0a06b3592..4515d909d 100644
--- a/packages/core/core/src/requests/AssetRequest.js
+++ b/packages/core/core/src/requests/AssetRequest.js
@@ -6,7 +6,7 @@ import type {AssetRequestInput, AssetRequestResult} from '../types';
 import type {ConfigAndCachePath} from './ParcelConfigRequest';
 import type {TransformationResult} from '../Transformation';
 
-import {md5FromObject} from '@parcel/utils';
+import {md5FromOrderedObject, objectSortedEntries} from '@parcel/utils';
 import nullthrows from 'nullthrows';
 import createParcelConfigRequest from './ParcelConfigRequest';
 
@@ -23,7 +23,7 @@ export type AssetRequest = {|
 |};
 
 function generateRequestId(type, obj) {
-  return `${type}:${md5FromObject(obj)}`;
+  return `${type}:${md5FromOrderedObject(obj)}`;
 }
 
 export default function createAssetRequest(
@@ -42,11 +42,22 @@ const type = 'asset_request';
 function getId(input: AssetRequestInput) {
   // eslint-disable-next-line no-unused-vars
   let {optionsRef, ...hashInput} = input;
-  return `${type}:${md5FromObject(hashInput)}`;
+  return md5FromOrderedObject({
+    type,
+    filePath: input.filePath,
+    env: input.env.id,
+    isSource: input.isSource,
+    sideEffects: input.sideEffects,
+    code: input.code,
+    pipeline: input.pipeline,
+    query: input.query ? objectSortedEntries(input.query) : null,
+    invalidations: input.invalidations,
+  });
 }
 
 async function run({input, api, options, farm}: RunInput) {
-  api.invalidateOnFileUpdate(await options.inputFS.realpath(input.filePath));
+  let realpath = await options.inputFS.realpath(input.filePath);
+  api.invalidateOnFileUpdate(realpath);
   let start = Date.now();
   let {optionsRef, ...request} = input;
   let {cachePath} = nullthrows(
@@ -57,9 +68,7 @@ async function run({input, api, options, farm}: RunInput) {
   // These are used to compute the cache key for assets during transformation.
   request.invalidations = api.getInvalidations().filter(invalidation => {
     // Filter out invalidation node for the input file itself.
-    return (
-      invalidation.type !== 'file' || invalidation.filePath !== input.filePath
-    );
+    return invalidation.type !== 'file' || invalidation.filePath !== realpath;
   });
 
   let {assets, configRequests, invalidations} = (await farm.createHandle(
diff --git a/packages/core/core/src/requests/ParcelConfigRequest.js b/packages/core/core/src/requests/ParcelConfigRequest.js
index a381024fd..c9bf69bbb 100644
--- a/packages/core/core/src/requests/ParcelConfigRequest.js
+++ b/packages/core/core/src/requests/ParcelConfigRequest.js
@@ -32,6 +32,7 @@ import assert from 'assert';
 
 import ParcelConfigSchema from '../ParcelConfig.schema';
 import {optionsProxy} from '../utils';
+import ParcelConfig from '../ParcelConfig';
 
 type ConfigMap<K, V> = {[K]: V, ...};
 
@@ -91,6 +92,29 @@ export default function createParcelConfigRequest(): ParcelConfigRequest {
   };
 }
 
+const parcelConfigCache = new Map();
+
+export function getCachedParcelConfig(
+  result: ConfigAndCachePath,
+  options: ParcelOptions,
+): ParcelConfig {
+  let {config: processedConfig, cachePath} = result;
+  let config = parcelConfigCache.get(cachePath);
+  if (config) {
+    return config;
+  }
+
+  config = new ParcelConfig(
+    processedConfig,
+    options.packageManager,
+    options.inputFS,
+    options.autoinstall,
+  );
+
+  parcelConfigCache.set(cachePath, config);
+  return config;
+}
+
 export async function loadParcelConfig(
   options: ParcelOptions,
 ): Promise<{|...ParcelConfigChain, usedDefault: boolean|}> {
diff --git a/packages/core/core/src/requests/PathRequest.js b/packages/core/core/src/requests/PathRequest.js
index 5f86f52a9..ec68aecd9 100644
--- a/packages/core/core/src/requests/PathRequest.js
+++ b/packages/core/core/src/requests/PathRequest.js
@@ -16,7 +16,9 @@ import {report} from '../ReporterRunner';
 import PublicDependency from '../public/Dependency';
 import PluginOptions from '../public/PluginOptions';
 import ParcelConfig from '../ParcelConfig';
-import createParcelConfigRequest from './ParcelConfigRequest';
+import createParcelConfigRequest, {
+  getCachedParcelConfig,
+} from './ParcelConfigRequest';
 
 export type PathRequestResult = AssetGroup | null | void;
 
@@ -50,18 +52,15 @@ export default function createPathRequest(
     input,
   };
 }
+
 async function run({input, api, options}: RunOpts) {
-  let {config} = nullthrows(
+  let configResult = nullthrows(
     await api.runRequest<null, ConfigAndCachePath>(createParcelConfigRequest()),
   );
+  let config = getCachedParcelConfig(configResult, options);
   let resolverRunner = new ResolverRunner({
     options,
-    config: new ParcelConfig(
-      config,
-      options.packageManager,
-      options.inputFS,
-      options.autoinstall,
-    ),
+    config,
   });
   let assetGroup = await resolverRunner.resolve(input);
 
@@ -125,7 +124,7 @@ export class ResolverRunner {
 
     let pipeline;
     let filePath;
-    let query: QueryParameters = {};
+    let query: ?QueryParameters;
     let validPipelines = new Set(this.config.getNamedPipelines());
     if (
       // Don't consider absolute paths. Absolute paths are only supported for entries,
@@ -238,11 +237,10 @@ export class ResolverRunner {
       return null;
     }
 
+    let resolveFrom = dependency.resolveFrom ?? dependency.sourcePath;
     let dir =
-      dependency.sourcePath != null
-        ? escapeMarkdown(
-            relativePath(this.options.projectRoot, dependency.sourcePath),
-          )
+      resolveFrom != null
+        ? escapeMarkdown(relativePath(this.options.projectRoot, resolveFrom))
         : '';
 
     let specifier = escapeMarkdown(dependency.moduleSpecifier || '');
diff --git a/packages/core/core/src/requests/TargetRequest.js b/packages/core/core/src/requests/TargetRequest.js
index 8c88cfe04..c850f26b8 100644
--- a/packages/core/core/src/requests/TargetRequest.js
+++ b/packages/core/core/src/requests/TargetRequest.js
@@ -26,8 +26,9 @@ import {
   validateSchema,
 } from '@parcel/utils';
 import {createEnvironment} from '../Environment';
-import createParcelConfigRequest from './ParcelConfigRequest';
-import ParcelConfig from '../ParcelConfig';
+import createParcelConfigRequest, {
+  getCachedParcelConfig,
+} from './ParcelConfigRequest';
 // $FlowFixMe
 import browserslist from 'browserslist';
 import jsonMap from 'json-source-map';
@@ -74,16 +75,10 @@ async function run({input, api, options}: RunOpts) {
   );
   let targets = await targetResolver.resolve(input.packagePath);
 
-  let {config} = nullthrows(
+  let configResult = nullthrows(
     await api.runRequest<null, ConfigAndCachePath>(createParcelConfigRequest()),
   );
-
-  let parcelConfig = new ParcelConfig(
-    config,
-    options.packageManager,
-    options.inputFS,
-    options.autoinstall,
-  );
+  let parcelConfig = getCachedParcelConfig(configResult, options);
 
   // Find named pipelines for each target.
   let pipelineNames = new Set(parcelConfig.getNamedPipelines());
diff --git a/packages/core/core/src/summarizeRequest.js b/packages/core/core/src/summarizeRequest.js
index a6c1c0b46..dff7a2fb6 100644
--- a/packages/core/core/src/summarizeRequest.js
+++ b/packages/core/core/src/summarizeRequest.js
@@ -2,7 +2,7 @@
 import type {Blob, FilePath} from '@parcel/types';
 import type {FileSystem} from '@parcel/fs';
 
-import {md5FromReadableStream, md5FromString, TapStream} from '@parcel/utils';
+import {md5FromReadableStream, md5FromString} from '@parcel/utils';
 import path from 'path';
 
 const NODE_MODULES = `${path.sep}node_modules${path.sep}`;
@@ -27,48 +27,42 @@ async function isFilePathSource(fs: FileSystem, filePath: FilePath) {
   );
 }
 
-function summarizeDiskRequest(
+async function summarizeDiskRequest(
   fs: FileSystem,
   req: {|filePath: FilePath, code?: string|},
 ): Promise<{|content: Blob, hash: string, size: number|}> {
-  return new Promise((resolve, reject) => {
-    let code = req.code;
-    let content: Blob;
-    let hash: string;
-    let size: number;
-    if (code == null) {
-      // As an optimization for the common case of source code, while we read in
-      // data to compute its md5 and size, buffer its contents in memory.
-      // This avoids reading the data now, and then again during transformation.
-      // If it exceeds BUFFER_LIMIT, throw it out and replace it with a stream to
-      // lazily read it at a later point.
-      content = Buffer.from([]);
-      size = 0;
-
-      let stream = fs.createReadStream(req.filePath);
-      stream.on('error', reject);
-
-      md5FromReadableStream(
-        stream.pipe(
-          new TapStream(buf => {
-            size += buf.length;
-            if (content instanceof Buffer) {
-              if (size > BUFFER_LIMIT) {
-                // if buffering this content would put this over BUFFER_LIMIT, replace
-                // it with a stream
-                content = fs.createReadStream(req.filePath);
-              } else {
-                content = Buffer.concat([content, buf]);
-              }
-            }
-          }),
-        ),
-      ).then(hash => resolve({content, hash, size}), reject);
+  let code = req.code;
+  let content: Blob;
+  let hash: string;
+  let size: number;
+  if (code == null) {
+    // Get the filesize. If greater than BUFFER_LIMIT, use a stream to
+    // compute the hash. In the common case, it's faster to just read the entire
+    // file first and do the hash all at once without the overhead of streams.
+    size = (await fs.stat(req.filePath)).size;
+    if (size > BUFFER_LIMIT) {
+      return new Promise((resolve, reject) => {
+        let stream = fs.createReadStream(req.filePath);
+        stream.on('error', reject);
+        md5FromReadableStream(stream).then(
+          hash =>
+            resolve({
+              content: fs.createReadStream(req.filePath),
+              hash,
+              size,
+            }),
+          reject,
+        );
+      });
     } else {
-      content = code;
-      hash = md5FromString(code);
-      size = Buffer.from(code).length;
-      resolve({content, hash, size});
+      content = await fs.readFile(req.filePath);
+      hash = md5FromString(content);
     }
-  });
+  } else {
+    content = code;
+    hash = md5FromString(code);
+    size = Buffer.byteLength(code);
+  }
+
+  return {content, hash, size};
 }
diff --git a/packages/core/core/src/types.js b/packages/core/core/src/types.js
index 8c41c5cc1..b7d065550 100644
--- a/packages/core/core/src/types.js
+++ b/packages/core/core/src/types.js
@@ -60,6 +60,7 @@ export type ProcessedParcelConfig = {|
 |};
 
 export type Environment = {|
+  id: string,
   context: EnvironmentContext,
   engines: Engines,
   includeNodeModules:
@@ -97,6 +98,7 @@ export type Dependency = {|
   target: ?Target,
   sourceAssetId: ?string,
   sourcePath: ?string,
+  resolveFrom: ?string,
   symbols: ?Map<
     Symbol,
     {|local: Symbol, loc: ?SourceLocation, isWeak: boolean|},
@@ -109,7 +111,7 @@ export type Asset = {|
   committed: boolean,
   hash: ?string,
   filePath: FilePath,
-  query: QueryParameters,
+  query: ?QueryParameters,
   type: string,
   dependencies: Map<string, Dependency>,
   isIsolated: boolean,
@@ -247,7 +249,7 @@ export type AssetRequestInput = {|
   pipeline?: ?string,
   optionsRef: SharedReference,
   isURL?: boolean,
-  query: QueryParameters,
+  query?: ?QueryParameters,
   invalidations?: Array<RequestInvalidation>,
 |};
 
diff --git a/packages/core/core/src/worker.js b/packages/core/core/src/worker.js
index f645fe9ca..25662c067 100644
--- a/packages/core/core/src/worker.js
+++ b/packages/core/core/src/worker.js
@@ -48,10 +48,13 @@ function loadOptions(ref, workerApi) {
 }
 
 async function loadConfig(cachePath, options) {
-  let processedConfig =
-    parcelConfigCache.get(cachePath) ??
-    nullthrows(await options.cache.get(cachePath));
-  let config = new ParcelConfig(
+  let config = parcelConfigCache.get(cachePath);
+  if (config) {
+    return config;
+  }
+
+  let processedConfig = nullthrows(await options.cache.get(cachePath));
+  config = new ParcelConfig(
     // $FlowFixMe
     ((processedConfig: any): ProcessedParcelConfig),
     options.packageManager,
diff --git a/packages/core/fs/src/NodeFS.js b/packages/core/fs/src/NodeFS.js
index b84172629..501f42d41 100644
--- a/packages/core/fs/src/NodeFS.js
+++ b/packages/core/fs/src/NodeFS.js
@@ -22,7 +22,9 @@ import packageJSON from '../package.json';
 // Most of this can go away once we only support Node 10+, which includes
 // require('fs').promises
 
-const realpath = promisify(fs.realpath);
+const realpath = promisify(
+  process.platform === 'win32' ? fs.realpath : fs.realpath.native,
+);
 
 export class NodeFS implements FileSystem {
   readFile: any = promisify(fs.readFile);
diff --git a/packages/core/types/index.js b/packages/core/types/index.js
index c3950aa6f..84c6ad449 100644
--- a/packages/core/types/index.js
+++ b/packages/core/types/index.js
@@ -418,6 +418,7 @@ export type DependencyOptions = {|
   +loc?: SourceLocation,
   +env?: EnvironmentOpts,
   +meta?: Meta,
+  +resolveFrom?: FilePath,
   +target?: Target,
   +symbols?: $ReadOnlyMap<
     Symbol,
@@ -452,6 +453,7 @@ export interface Dependency {
   +sourceAssetId: ?string;
   /** Used for error messages, the importer. */
   +sourcePath: ?string;
+  +resolveFrom: ?string;
   /** a named pipeline (if the <code>moduleSpecifier</code> didn't specify one). */
   +pipeline: ?string;
 
diff --git a/packages/core/utils/src/index.js b/packages/core/utils/src/index.js
index 45806d417..b689dd853 100644
--- a/packages/core/utils/src/index.js
+++ b/packages/core/utils/src/index.js
@@ -47,6 +47,7 @@ export {
   md5FromString,
   md5FromReadableStream,
   md5FromObject,
+  md5FromOrderedObject,
   md5FromFilePath,
 } from './md5';
 export {fuzzySearch} from './schema';
diff --git a/packages/core/utils/src/md5.js b/packages/core/utils/src/md5.js
index b3fb460bb..7d7f7b6a6 100644
--- a/packages/core/utils/src/md5.js
+++ b/packages/core/utils/src/md5.js
@@ -34,6 +34,13 @@ export function md5FromReadableStream(stream: Readable): Promise<string> {
   });
 }
 
+export function md5FromOrderedObject(
+  obj: {+[string]: mixed, ...},
+  encoding: StringHashEncoding = 'hex',
+): string {
+  return md5FromString(JSON.stringify(obj), encoding);
+}
+
 export function md5FromObject(
   obj: {+[string]: mixed, ...},
   encoding: StringHashEncoding = 'hex',
diff --git a/packages/resolvers/default/src/DefaultResolver.js b/packages/resolvers/default/src/DefaultResolver.js
index 702b8c684..d21578fe3 100644
--- a/packages/resolvers/default/src/DefaultResolver.js
+++ b/packages/resolvers/default/src/DefaultResolver.js
@@ -35,7 +35,7 @@ export default (new Resolver({
     return resolver.resolve({
       filename: filePath,
       isURL: dependency.isURL,
-      parent: dependency.sourcePath,
+      parent: dependency.resolveFrom,
       env: dependency.env,
     });
   },
diff --git a/packages/shared/babel-ast-utils/src/index.js b/packages/shared/babel-ast-utils/src/index.js
index 36795bf7d..d66e0c22f 100644
--- a/packages/shared/babel-ast-utils/src/index.js
+++ b/packages/shared/babel-ast-utils/src/index.js
@@ -35,7 +35,6 @@ export async function parse({
         allowReturnOutsideFunction: true,
         strictMode: false,
         sourceType: 'module',
-        plugins: ['exportDefaultFrom', 'exportNamespaceFrom', 'dynamicImport'],
       }),
     };
   } catch (e) {
diff --git a/packages/transformers/babel/src/babel7.js b/packages/transformers/babel/src/babel7.js
index dfa0c2298..70226d3a1 100644
--- a/packages/transformers/babel/src/babel7.js
+++ b/packages/transformers/babel/src/babel7.js
@@ -41,7 +41,6 @@ export default async function babel7(
       allowReturnOutsideFunction: true,
       strictMode: false,
       sourceType: 'module',
-      plugins: ['dynamicImport'],
     },
     caller: {
       name: 'parcel',
diff --git a/packages/transformers/js/package.json b/packages/transformers/js/package.json
index b99e8f5a6..4f5c683ec 100644
--- a/packages/transformers/js/package.json
+++ b/packages/transformers/js/package.json
@@ -23,7 +23,6 @@
     "@babel/core": "^7.12.0",
     "@babel/generator": "^7.0.0",
     "@babel/parser": "^7.0.0",
-    "@babel/plugin-transform-modules-commonjs": "^7.0.0",
     "@babel/template": "^7.4.0",
     "@babel/traverse": "^7.0.0",
     "@babel/types": "^7.0.0",
@@ -32,6 +31,7 @@
     "@parcel/diagnostic": "2.0.0-beta.1",
     "@parcel/plugin": "2.0.0-beta.1",
     "@parcel/scope-hoisting": "2.0.0-beta.1",
+    "@parcel/types": "2.0.0-beta.1",
     "@parcel/utils": "2.0.0-beta.1",
     "micromatch": "^4.0.2",
     "nullthrows": "^1.1.1",
diff --git a/packages/transformers/js/src/JSTransformer.js b/packages/transformers/js/src/JSTransformer.js
index 1a83e534b..a2e4c48d1 100644
--- a/packages/transformers/js/src/JSTransformer.js
+++ b/packages/transformers/js/src/JSTransformer.js
@@ -12,11 +12,11 @@ import fsVisitor from './visitors/fs';
 import insertGlobals from './visitors/globals';
 import traverse from '@babel/traverse';
 import {ancestor as walkAncestor} from '@parcel/babylon-walk';
-import * as babelCore from '@babel/core';
 import {hoist} from '@parcel/scope-hoisting';
 import {generate, parse} from '@parcel/babel-ast-utils';
 import {validateSchema} from '@parcel/utils';
 import {encodeJSONKeyComponent} from '@parcel/diagnostic';
+import {esm2cjs} from './visitors/modules';
 
 const CONFIG_SCHEMA: SchemaEntity = {
   type: 'object',
@@ -193,32 +193,16 @@ export default (new Transformer({
       isASTDirty = true;
     }
 
-    if (isASTDirty) {
-      asset.setAST(ast);
-    }
-
     if (asset.env.scopeHoist) {
       hoist(asset, ast);
     } else if (asset.meta.isES6Module) {
       // Convert ES6 modules to CommonJS
-      let res = await babelCore.transformFromAstAsync(
-        ast.program,
-        code ?? undefined,
-        {
-          code: false,
-          ast: true,
-          filename: asset.filePath,
-          babelrc: false,
-          configFile: false,
-          plugins: [require('@babel/plugin-transform-modules-commonjs')],
-        },
-      );
+      esm2cjs(ast.program, asset);
+      isASTDirty = true;
+    }
 
-      asset.setAST({
-        type: 'babel',
-        version: '7.0.0',
-        program: res.ast,
-      });
+    if (isASTDirty) {
+      asset.setAST(ast);
     }
 
     return [asset];
diff --git a/packages/transformers/js/src/esmodule-helpers.js b/packages/transformers/js/src/esmodule-helpers.js
new file mode 100644
index 000000000..4f740b1cd
--- /dev/null
+++ b/packages/transformers/js/src/esmodule-helpers.js
@@ -0,0 +1,36 @@
+exports.interopDefault = function(a) {
+  return a && a.__esModule ? a.default : a;
+};
+
+exports.defineInteropFlag = function(a) {
+  Object.defineProperty(a, '__esModule', {value: true});
+};
+
+exports.exportAll = function(source, dest) {
+  Object.keys(source).forEach(function(key) {
+    if (key === 'default' || key === '__esModule') {
+      return;
+    }
+
+    // Skip duplicate re-exports when they have the same value.
+    if (key in dest && dest[key] === source[key]) {
+      return;
+    }
+
+    Object.defineProperty(dest, key, {
+      enumerable: true,
+      get() {
+        return source[key];
+      },
+    });
+  });
+
+  return dest;
+};
+
+exports.export = function(dest, destName, get) {
+  Object.defineProperty(dest, destName, {
+    enumerable: true,
+    get,
+  });
+};
diff --git a/packages/transformers/js/src/visitors/dependencies.js b/packages/transformers/js/src/visitors/dependencies.js
index c4df2fcc5..cbf022fc3 100644
--- a/packages/transformers/js/src/visitors/dependencies.js
+++ b/packages/transformers/js/src/visitors/dependencies.js
@@ -9,7 +9,7 @@ import type {
   PluginOptions,
 } from '@parcel/types';
 import type {Node, ObjectExpression} from '@babel/types';
-import type {Visitors} from '@parcel/babylon-walk';
+import type {SimpleVisitors} from '@parcel/babylon-walk';
 
 import * as types from '@babel/types';
 import {
@@ -193,7 +193,7 @@ export default ({
       }
     },
   },
-}: Visitors<
+}: SimpleVisitors<
   (
     any,
     {|asset: MutableAsset, ast: AST, options: PluginOptions|},
diff --git a/packages/transformers/js/src/visitors/modules.js b/packages/transformers/js/src/visitors/modules.js
new file mode 100644
index 000000000..f4ad35c89
--- /dev/null
+++ b/packages/transformers/js/src/visitors/modules.js
@@ -0,0 +1,418 @@
+// @flow
+import type {ScopeState, Visitors} from '@parcel/babylon-walk';
+import type {
+  ImportDeclaration,
+  ExportAllDeclaration,
+  ExportNamedDeclaration,
+  Identifier,
+  Expression,
+} from '@babel/types';
+import {MutableAsset} from '@parcel/types';
+import * as t from '@babel/types';
+import {
+  isIdentifier,
+  isImportDeclaration,
+  isImportSpecifier,
+  isImportDefaultSpecifier,
+  isImportNamespaceSpecifier,
+  isExportAllDeclaration,
+  isExportNamespaceSpecifier,
+  isExportDefaultSpecifier,
+  isExportSpecifier,
+  isFunctionDeclaration,
+} from '@babel/types';
+import {
+  traverse2,
+  REMOVE,
+  mergeVisitors,
+  Scope,
+  scopeVisitor,
+} from '@parcel/babylon-walk';
+import invariant from 'assert';
+import path from 'path';
+import {normalizeSeparators} from '@parcel/utils';
+
+type State = {|
+  ...ScopeState,
+  imports: Array<
+    ImportDeclaration | ExportAllDeclaration | ExportNamedDeclaration,
+  >,
+  importNames: Map<
+    string,
+    {|name: string, default: string, namespace: string|},
+  >,
+  exports: Array<{|local: Expression, exported: Identifier|}>,
+  needsInteropFlag: boolean,
+|};
+
+let modulesVisitor: Visitors<State> = {
+  Identifier(node, state, ancestors) {
+    let parent = ancestors[ancestors.length - 2];
+    if (!t.isReferenced(node, parent, ancestors[ancestors.length - 3])) {
+      return;
+    }
+    let {scope} = state;
+
+    return () => {
+      let binding = scope.getBinding(node.name);
+      if (!binding || !isImportDeclaration(binding)) {
+        return;
+      }
+
+      let specifier = binding.specifiers.find(
+        specifier => specifier.local.name === node.name,
+      );
+      if (isImportSpecifier(specifier)) {
+        return getSpecifier(state, binding.source, specifier.imported.name);
+      } else if (isImportNamespaceSpecifier(specifier)) {
+        return getNamespace(state, binding.source);
+      } else if (isImportDefaultSpecifier(specifier)) {
+        return getDefault(state, binding.source);
+      }
+    };
+  },
+  ImportDeclaration: {
+    exit(node, {imports}) {
+      imports.push(node);
+      return REMOVE;
+    },
+  },
+  ExportNamedDeclaration: {
+    exit(node, state) {
+      let {exports, imports} = state;
+      let {declaration, source, specifiers} = node;
+
+      state.needsInteropFlag = true;
+
+      if (source) {
+        imports.push(node);
+        return () => {
+          for (let specifier of specifiers) {
+            let local;
+            if (isExportNamespaceSpecifier(specifier)) {
+              local = getNamespace(state, source);
+            } else if (isExportDefaultSpecifier(specifier)) {
+              local = getDefault(state, source);
+            } else if (isExportSpecifier(specifier)) {
+              local =
+                specifier.local.name === 'default'
+                  ? getDefault(state, source)
+                  : getSpecifier(state, source, specifier.local.name);
+            } else {
+              throw new Error('Unknown specifier type: ' + specifier.type);
+            }
+
+            exports.push({exported: specifier.exported, local});
+          }
+
+          return REMOVE;
+        };
+      } else if (declaration) {
+        if (
+          isFunctionDeclaration(declaration) &&
+          isIdentifier(declaration.id)
+        ) {
+          let name = declaration.id.name;
+          exports.push({
+            local: t.identifier(name),
+            exported: t.identifier(name),
+          });
+        } else {
+          // Find all binding identifiers, and insert assignments to `exports`
+          let identifiers = t.getBindingIdentifiers(declaration);
+          for (let id of Object.keys(identifiers)) {
+            exports.push({local: t.identifier(id), exported: t.identifier(id)});
+          }
+        }
+
+        return declaration;
+      } else if (specifiers.length > 0) {
+        // This must happen AFTER the Identifier visitor above replaces the specifiers
+        // in the case of import foo from 'foo'; export {foo};
+        return () => {
+          // Add assignments to `exports` for each specifier
+          for (let specifier of specifiers) {
+            invariant(specifier.type === 'ExportSpecifier');
+            exports.push({
+              local: specifier.local,
+              exported: specifier.exported,
+            });
+          }
+
+          return REMOVE;
+        };
+      }
+
+      return REMOVE;
+    },
+  },
+  ExportAllDeclaration(node, state) {
+    state.imports.push(node);
+    state.needsInteropFlag = true;
+    return REMOVE;
+  },
+  ExportDefaultDeclaration: {
+    exit(node, state) {
+      // This has to happen AFTER any referenced identifiers are replaced.
+      return () => {
+        let {declaration} = node;
+        state.needsInteropFlag = true;
+
+        // If the declaration has a name, insert an assignment to `exports` afterward.
+        if (declaration.id != null && isIdentifier(declaration.id)) {
+          let id = t.identifier(declaration.id.name);
+          state.scope.addReference(id);
+
+          return [
+            declaration,
+            t.expressionStatement(
+              t.assignmentExpression(
+                '=',
+                t.memberExpression(
+                  t.identifier('exports'),
+                  t.identifier('default'),
+                ),
+                id,
+              ),
+            ),
+          ];
+        } else if (declaration.type !== 'TSDeclareFunction') {
+          // Replace with an assignment to `exports`.
+          return t.expressionStatement(
+            t.assignmentExpression(
+              '=',
+              t.memberExpression(
+                t.identifier('exports'),
+                t.identifier('default'),
+              ),
+              t.toExpression(declaration),
+            ),
+          );
+        } else {
+          return REMOVE;
+        }
+      };
+    },
+  },
+  ThisExpression(node, {scope}) {
+    while (scope) {
+      if (scope.type === 'function') {
+        return;
+      }
+
+      scope = scope.parent;
+    }
+
+    return t.identifier('undefined');
+  },
+};
+
+function getNames(state, source) {
+  let names = state.importNames.get(source.value);
+  if (!names) {
+    let name = state.scope.generateUid(source.value);
+    names = {name, namespace: '', default: ''};
+    state.importNames.set(source.value, names);
+  }
+
+  return names;
+}
+
+function getSpecifier(state, source, name) {
+  let names = getNames(state, source);
+  return t.memberExpression(t.identifier(names.name), t.identifier(name));
+}
+
+function getDefault(state, source) {
+  let names = getNames(state, source);
+  if (!names.default) {
+    names.default = state.scope.generateUid(names.name + 'Default');
+  }
+  return t.identifier(names.default);
+}
+
+function getNamespace(state, source) {
+  // Interop between import namespace declarations and CJS is very inconsistent.
+  // Node always returns {default: module.exports}.
+  // Babel does the same for functions and primative values, but for objects returns
+  // {...module.exports, default: module.exports}.
+  // Parcel currently just returns the original module.exports in scope hoisting.
+  // Doing the same here to match for now, but we should revisit this.
+  let names = getNames(state, source);
+  // if (!names.namespace) {
+  //   names.namespace = state.scope.generateUid(names.name + 'Namespace');
+  // }
+  return t.identifier(names.name);
+}
+
+const visitor = mergeVisitors(scopeVisitor, modulesVisitor);
+
+export function esm2cjs(ast: BabelNodeFile, asset?: MutableAsset) {
+  let imports = [];
+  let importNames = new Map();
+  let scope = new Scope('program');
+  let exports = [];
+  let state: State = {
+    imports,
+    importNames,
+    exports,
+    needsInteropFlag: false,
+    scope,
+  };
+
+  traverse2(ast, visitor, state);
+
+  let body = ast.program.body;
+  let prepend = [];
+  let helpersId;
+  let addHelpers = () => {
+    if (helpersId) {
+      return helpersId;
+    }
+
+    // Add a dependency so Parcel includes the helpers
+    let moduleRoot = path.resolve(__dirname, '..', '..');
+    let helpersPath = path.resolve(__dirname, '..', 'esmodule-helpers.js');
+    let helperSpecifier = `@parcel/transformer-js/${normalizeSeparators(
+      path.relative(moduleRoot, helpersPath),
+    )}`;
+    asset?.addDependency({
+      moduleSpecifier: helperSpecifier,
+      resolveFrom: __filename,
+      env: {
+        includeNodeModules: {
+          '@parcel/transformer-js': true,
+        },
+      },
+    });
+
+    helpersId = scope.generateUid('parcelHelpers');
+    prepend.push(
+      t.variableDeclaration('var', [
+        t.variableDeclarator(
+          t.identifier(helpersId),
+          t.callExpression(t.identifier('require'), [
+            t.stringLiteral(helperSpecifier),
+          ]),
+        ),
+      ]),
+    );
+
+    return helpersId;
+  };
+
+  if (state.needsInteropFlag) {
+    prepend.push(
+      t.expressionStatement(
+        t.callExpression(
+          t.memberExpression(
+            t.identifier(addHelpers()),
+            t.identifier('defineInteropFlag'),
+          ),
+          [t.identifier('exports')],
+        ),
+      ),
+    );
+  }
+
+  for (let {local, exported} of exports) {
+    prepend.push(
+      t.expressionStatement(
+        t.callExpression(
+          t.memberExpression(
+            t.identifier(addHelpers()),
+            t.identifier('export'),
+          ),
+          [
+            t.identifier('exports'),
+            t.stringLiteral(exported.name),
+            t.functionExpression(
+              null,
+              [],
+              t.blockStatement([t.returnStatement(local)]),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+
+  for (let imp of imports) {
+    invariant(imp.source != null);
+    let source = imp.source;
+
+    // If the result of the import is unused, simply insert a require call.
+    if (!state.importNames.has(source.value) && !isExportAllDeclaration(imp)) {
+      prepend.push(
+        t.expressionStatement(
+          t.callExpression(t.identifier('require'), [source]),
+        ),
+      );
+      continue;
+    }
+
+    let names = getNames(state, source);
+
+    if (!scope.bindings.has(names.name)) {
+      let decl = t.variableDeclaration('var', [
+        t.variableDeclarator(
+          t.identifier(names.name),
+          t.callExpression(t.identifier('require'), [source]),
+        ),
+      ]);
+
+      prepend.push(decl);
+      scope.addBinding(names.name, decl);
+    }
+
+    if (isExportAllDeclaration(imp)) {
+      prepend.push(
+        t.expressionStatement(
+          t.callExpression(
+            t.memberExpression(
+              t.identifier(addHelpers()),
+              t.identifier('exportAll'),
+            ),
+            [t.identifier(names.name), t.identifier('exports')],
+          ),
+        ),
+      );
+    }
+
+    if (names.default) {
+      prepend.push(
+        t.variableDeclaration('var', [
+          t.variableDeclarator(
+            t.identifier(names.default),
+            t.callExpression(
+              t.memberExpression(
+                t.identifier(addHelpers()),
+                t.identifier('interopDefault'),
+              ),
+              [t.identifier(names.name)],
+            ),
+          ),
+        ]),
+      );
+    }
+
+    // if (names.namespace) {
+    //   prepend.push(
+    //     t.variableDeclaration('var', [
+    //       t.variableDeclarator(
+    //         t.identifier(names.namespace),
+    //         t.callExpression(
+    //           t.memberExpression(
+    //             t.identifier(addHelpers()),
+    //             t.identifier('namespace'),
+    //           ),
+    //           [t.identifier(names.name)],
+    //         ),
+    //       ),
+    //     ]),
+    //   );
+    // }
+  }
+
+  body.unshift(...prepend);
+}
diff --git a/packages/utils/babylon-walk/src/explode.js b/packages/utils/babylon-walk/src/explode.js
index 43badd9c5..9699e9811 100755
--- a/packages/utils/babylon-walk/src/explode.js
+++ b/packages/utils/babylon-walk/src/explode.js
@@ -1,7 +1,7 @@
 // @flow
 // Copied from babel-traverse, but with virtual types handling removed
 // https://github.com/babel/babel/blob/07b3dc18a09f2217b38a3a63c8613add6df1b47d/packages/babel-traverse/src/visitors.js
-import type {Visitors, VisitorsExploded} from './index';
+import type {SimpleVisitors, VisitorsExploded} from './index';
 
 // import * as messages from 'babel-messages';
 import * as t from '@babel/types';
@@ -21,7 +21,9 @@ import clone from 'lodash.clone';
  * * `enter` and `exit` functions are wrapped in arrays, to ease merging of
  *   visitors
  */
-export default function explode<T>(visitor: Visitors<T>): VisitorsExploded<T> {
+export default function explode<T>(
+  visitor: SimpleVisitors<T>,
+): VisitorsExploded<T> {
   // $FlowFixMe
   if (visitor._exploded) return visitor;
   // $FlowFixMe
diff --git a/packages/utils/babylon-walk/src/index.js b/packages/utils/babylon-walk/src/index.js
index a4bcbe0d4..52ea519aa 100755
--- a/packages/utils/babylon-walk/src/index.js
+++ b/packages/utils/babylon-walk/src/index.js
@@ -1,27 +1,18 @@
 // @flow
 import type {Node} from '@babel/types';
+import type {SimpleVisitors, VisitorsExploded} from './types';
 
 import * as t from '@babel/types';
 import explode from './explode.js';
 import traverse from './traverse';
 
-export type Visitors<F> = {
-  [string]: F | {|enter?: F, exit?: F|},
-  shouldSkip?: Node => boolean,
-  ...
-};
-export type VisitorsExploded<F> = {
-  [string]: {|
-    enter?: Array<F>,
-    exit?: Array<F>,
-  |},
-  shouldSkip?: Node => boolean,
-  ...
-};
+export * from './traverse2';
+export * from './scope';
+export * from './types';
 
 export function simple<T>(
   node: Node,
-  _visitors: Visitors<(any, T) => void>,
+  _visitors: SimpleVisitors<(any, T) => void>,
   state: T,
 ) {
   if (!node) return;
@@ -61,7 +52,7 @@ export function simple<T>(
 
 export function ancestor<T>(
   node: Node,
-  _visitors: Visitors<(any, T, Array<Node>) => void>,
+  _visitors: SimpleVisitors<(any, T, Array<Node>) => void>,
   state: T,
 ) {
   if (!node) return;
@@ -109,7 +100,7 @@ export function ancestor<T>(
 
 export function recursive<T>(
   node: Node,
-  _visitors: Visitors<(any, T, recurse: (Node) => void) => void>,
+  _visitors: SimpleVisitors<(any, T, recurse: (Node) => void) => void>,
   state: T,
 ) {
   if (!node) return;
diff --git a/packages/utils/babylon-walk/src/scope.js b/packages/utils/babylon-walk/src/scope.js
new file mode 100644
index 000000000..19c3af954
--- /dev/null
+++ b/packages/utils/babylon-walk/src/scope.js
@@ -0,0 +1,209 @@
+// @flow
+
+import type {Identifier, Node} from '@babel/types';
+import type {Visitors} from './types';
+import * as t from '@babel/types';
+import {
+  isVariableDeclaration,
+  isFunctionDeclaration,
+  isIdentifier,
+} from '@babel/types';
+
+export type ScopeType = 'program' | 'function' | 'arrow_function' | 'block';
+export class Scope {
+  type: ScopeType;
+  names: Set<string> = new Set();
+  bindings: Map<string, Node> = new Map();
+  references: Map<string, Set<Identifier>> = new Map();
+  parent: ?Scope;
+  program: Scope;
+  renames: Map<string, string> = new Map();
+  inverseRenames: Map<string, string> = new Map();
+
+  constructor(type: ScopeType, parent: ?Scope) {
+    this.type = type;
+    this.parent = parent;
+    this.program = parent ? parent.program : this;
+  }
+
+  has(name: string): boolean {
+    if (this.names.has(name)) {
+      return true;
+    }
+
+    if (this.parent) {
+      return this.parent.has(name);
+    }
+
+    return false;
+  }
+
+  add(name: string) {
+    this.names.add(name);
+  }
+
+  generateUid(name: string = 'temp'): string {
+    name = t
+      .toIdentifier(name)
+      .replace(/^_+/, '')
+      .replace(/[0-9]+$/g, '');
+
+    let uid;
+    let i = 0;
+    do {
+      uid = '_' + name + (i > 1 ? i : '');
+      i++;
+    } while (this.program.names.has(uid));
+
+    this.program.names.add(uid);
+    return uid;
+  }
+
+  addBinding(name: string, decl: Node) {
+    this.names.add(name);
+    this.program.names.add(name);
+    this.bindings.set(name, decl);
+  }
+
+  getBinding(name: string): ?Node {
+    return this.bindings.get(name) ?? this.parent?.getBinding(name);
+  }
+
+  addReference(identifier: Identifier) {
+    let references = this.references.get(identifier.name);
+    if (!references) {
+      references = new Set();
+      this.references.set(identifier.name, references);
+    }
+
+    references.add(identifier);
+  }
+
+  rename(from: string, to: string) {
+    // If already renamed, update the original to the final name.
+    let renamed = this.inverseRenames.get(from);
+    if (renamed) {
+      this.renames.set(renamed, to);
+    } else {
+      this.renames.set(from, to);
+      this.inverseRenames.set(to, from);
+    }
+  }
+
+  exit() {
+    // Rename declarations in this scope.
+    for (let [from, to] of this.renames) {
+      if (!this.names.has(from) && this.parent) {
+        this.parent.rename(from, to);
+      }
+
+      let references = this.references.get(from);
+      if (!references) {
+        continue;
+      }
+
+      for (let id of references) {
+        id.name = to;
+      }
+    }
+
+    // Propagate unknown references to the parent scope.
+    let parent = this.parent;
+    if (parent) {
+      for (let [name, ids] of this.references) {
+        if (!this.names.has(name)) {
+          for (let id of ids) {
+            parent.addReference(id);
+          }
+        }
+      }
+    }
+  }
+}
+
+export type ScopeState = {|
+  scope: Scope,
+|};
+
+export let scopeVisitor: Visitors<ScopeState> = {
+  Program(node, state) {
+    state.scope = new Scope('program');
+  },
+  Scopable: {
+    enter(node, state, ancestors) {
+      if (
+        !t.isScope(node, ancestors[ancestors.length - 2]) ||
+        t.isProgram(node) ||
+        t.isFunction(node)
+      ) {
+        return;
+      }
+      state.scope = new Scope('block', state.scope);
+    },
+    exit(node, state, ancestors) {
+      if (
+        !t.isScope(node, ancestors[ancestors.length - 2]) ||
+        t.isProgram(node)
+      ) {
+        return;
+      }
+
+      state.scope.exit();
+      if (state.scope.parent) {
+        state.scope = state.scope.parent;
+      }
+    },
+  },
+  Declaration: {
+    exit(node, {scope}) {
+      if (t.isFunction(node) || t.isExportDeclaration(node)) {
+        return;
+      }
+
+      // Register declarations with the scope.
+      if (isVariableDeclaration(node)) {
+        for (let decl of node.declarations) {
+          let ids = t.getBindingIdentifiers(node);
+          for (let id in ids) {
+            scope.addBinding(id, decl);
+          }
+        }
+      } else {
+        let ids = t.getBindingIdentifiers(node);
+        for (let id in ids) {
+          scope.addBinding(id, node);
+        }
+      }
+    },
+  },
+  Function(node, state) {
+    // Add function name to outer scope
+    let name;
+    if (isFunctionDeclaration(node) && isIdentifier(node.id)) {
+      name = node.id.name;
+      state.scope.addBinding(name, node);
+    }
+
+    // Create new scope
+    let type = t.isArrowFunctionExpression(node)
+      ? 'arrow_function'
+      : 'function';
+    state.scope = new Scope(type, state.scope);
+
+    // Add inner bindings to inner scope
+    let inner = t.getBindingIdentifiers(node);
+    for (let id in inner) {
+      if (id !== name) {
+        state.scope.addBinding(id, inner[id]);
+      }
+    }
+  },
+  Identifier(node, state, ancestors) {
+    let parent = ancestors[ancestors.length - 2];
+    if (!t.isReferenced(node, parent, ancestors[ancestors.length - 3])) {
+      return;
+    }
+
+    state.scope.addReference(node);
+  },
+};
diff --git a/packages/utils/babylon-walk/src/traverse.js b/packages/utils/babylon-walk/src/traverse.js
index 28bdaf2b5..e2d19c516 100644
--- a/packages/utils/babylon-walk/src/traverse.js
+++ b/packages/utils/babylon-walk/src/traverse.js
@@ -1,6 +1,6 @@
 // @flow
 import type {Node} from '@babel/types';
-import type {Visitors, VisitorsExploded} from './index';
+import type {SimpleVisitors, VisitorsExploded} from './index';
 
 import * as t from '@babel/types';
 import invariant from 'assert';
@@ -46,7 +46,7 @@ class Path {
 
 export default function traverse<T>(
   node: Node,
-  visitors: Visitors<(Path, T) => void>,
+  visitors: SimpleVisitors<(Path, T) => void>,
   state: T,
 ) {
   traverseWalk(explode(visitors), state, node, null, null, null);
diff --git a/packages/utils/babylon-walk/src/traverse2.js b/packages/utils/babylon-walk/src/traverse2.js
new file mode 100644
index 000000000..7b011e79e
--- /dev/null
+++ b/packages/utils/babylon-walk/src/traverse2.js
@@ -0,0 +1,162 @@
+// @flow
+import type {Node} from '@babel/types';
+import type {VisitorFunc, Visitors, VisitorsExploded} from './types';
+
+import * as t from '@babel/types';
+import explode from './explode.js';
+
+export const SKIP: symbol = Symbol('traverse.SKIP');
+export const REMOVE: symbol = Symbol('traverse.REMOVE');
+
+export function traverse2<T>(
+  node: Node,
+  visitors: Visitors<T> | VisitorsExploded<VisitorFunc<Node, T>>,
+  state: T,
+) {
+  let ancestors = [];
+  let revisit = [];
+  traverseWalk(explode((visitors: any)), ancestors, revisit, state, node);
+
+  for (let fn of revisit) {
+    fn();
+  }
+}
+
+function traverseWalk<T>(
+  visitors: VisitorsExploded<VisitorFunc<Node, T>>,
+  ancestors: Node[],
+  revisit: any[],
+  state: T,
+  node: Node,
+) {
+  if (!node || (visitors.shouldSkip && visitors.shouldSkip(node) === true)) {
+    return;
+  }
+
+  let isNew = node != ancestors[ancestors.length - 1];
+  if (isNew) ancestors.push(node);
+
+  const {enter, exit} = visitors[node.type] || {};
+
+  if (enter) {
+    for (let visitor of enter) {
+      let res = visitor(node, state, ancestors);
+      if (res != null) {
+        if (isNew) ancestors.pop();
+        return res;
+      }
+    }
+  }
+
+  for (let key of t.VISITOR_KEYS[node.type] || []) {
+    // $FlowFixMe
+    let subNode: Node | Array<Node> = node[key];
+    if (Array.isArray(subNode)) {
+      let revisitDiff = 0;
+      for (let i = 0; i < subNode.length; i++) {
+        let res = traverseWalk(visitors, ancestors, revisit, state, subNode[i]);
+        if (res === REMOVE) {
+          subNode.splice(i, 1);
+          i--;
+        } else if (res !== SKIP && res != null) {
+          if (typeof res === 'function') {
+            revisit.push(() => {
+              let index = i + revisitDiff;
+              let r = replaceArray(subNode, index, res());
+              revisitDiff += r - index;
+            });
+          } else {
+            i = replaceArray(subNode, i, res);
+          }
+        }
+      }
+    } else {
+      let res = traverseWalk(visitors, ancestors, revisit, state, subNode);
+      if (res === REMOVE) {
+        if (isNew) ancestors.pop();
+        return REMOVE;
+      } else if (res !== SKIP && res != null) {
+        if (typeof res === 'function') {
+          revisit.push(() => {
+            let n = res();
+            if (n != null) {
+              // $FlowFixMe
+              node[key] = n;
+            }
+          });
+        } else {
+          // $FlowFixMe
+          node[key] = res;
+        }
+      }
+    }
+  }
+
+  if (exit) {
+    for (let visitor of exit) {
+      let res = visitor(node, state, ancestors);
+      if (res != null) {
+        if (isNew) ancestors.pop();
+        return res;
+      }
+    }
+  }
+
+  if (isNew) ancestors.pop();
+}
+
+function replaceArray(subNode, i, res) {
+  if (res === REMOVE) {
+    subNode.splice(i, 1);
+    i--;
+  } else if (Array.isArray(res)) {
+    subNode.splice(i, 1, ...res);
+    if (res.length === 0) {
+      i--;
+    } else if (res.length > 1) {
+      i += res.length - 1;
+    }
+  } else if (res != null) {
+    // $FlowFixMe
+    subNode[i] = res;
+  }
+
+  return i;
+}
+
+export function mergeVisitors<T, U>(
+  a: Visitors<T>,
+  b: Visitors<U>,
+): VisitorsExploded<VisitorFunc<Node, T & U>> {
+  let res: VisitorsExploded<VisitorFunc<Node, T & U>> = {};
+  // $FlowFixMe
+  res._exploded = true;
+
+  for (let visitor of [a, b]) {
+    let {shouldSkip, ...exploded} = explode((visitor: any));
+    for (let type in exploded) {
+      if (!res[type]) {
+        res[type] = {};
+      }
+
+      if (exploded[type].enter) {
+        res[type].enter = [...(res[type].enter || []), ...exploded[type].enter];
+      }
+
+      if (exploded[type].exit) {
+        res[type].exit = [...(res[type].exit || []), ...exploded[type].exit];
+      }
+    }
+
+    if (shouldSkip) {
+      if (res.shouldSkip) {
+        let prev = res.shouldSkip;
+        res.shouldSkip = node => prev(node) || shouldSkip(node);
+      } else {
+        res.shouldSkip = shouldSkip;
+      }
+    }
+  }
+
+  return res;
+}
diff --git a/packages/utils/babylon-walk/src/types.js b/packages/utils/babylon-walk/src/types.js
new file mode 100644
index 000000000..2f54bff4e
--- /dev/null
+++ b/packages/utils/babylon-walk/src/types.js
@@ -0,0 +1,337 @@
+// @flow
+import * as t from '@babel/types';
+
+export type VisitorFunc<N, S> = (
+  N,
+  S,
+  Array<t.Node>,
+) =>
+  | void
+  | t.Node
+  | Array<t.Node>
+  | symbol
+  | (() => void | t.Node | Array<t.Node> | symbol);
+type SingleVisitor<N, S> =
+  | VisitorFunc<N, S>
+  | {|
+      enter?: VisitorFunc<N, S>,
+      exit?: VisitorFunc<N, S>,
+    |};
+
+export type Visitors<S> = {
+  shouldSkip?: t.Node => boolean,
+  ReferencedIdentifier?: SingleVisitor<t.Identifier, S>,
+  Function?: SingleVisitor<t.Function, S>,
+  Class?: SingleVisitor<t.Class, S>,
+  ArrayExpression?: SingleVisitor<t.ArrayExpression, S>,
+  AssignmentExpression?: SingleVisitor<t.AssignmentExpression, S>,
+  BinaryExpression?: SingleVisitor<t.BinaryExpression, S>,
+  InterpreterDirective?: SingleVisitor<t.InterpreterDirective, S>,
+  Directive?: SingleVisitor<t.Directive, S>,
+  DirectiveLiteral?: SingleVisitor<t.DirectiveLiteral, S>,
+  BlockStatement?: SingleVisitor<t.BlockStatement, S>,
+  BreakStatement?: SingleVisitor<t.BreakStatement, S>,
+  CallExpression?: SingleVisitor<t.CallExpression, S>,
+  CatchClause?: SingleVisitor<t.CatchClause, S>,
+  ConditionalExpression?: SingleVisitor<t.ConditionalExpression, S>,
+  ContinueStatement?: SingleVisitor<t.ContinueStatement, S>,
+  DebuggerStatement?: SingleVisitor<t.DebuggerStatement, S>,
+  DoWhileStatement?: SingleVisitor<t.DoWhileStatement, S>,
+  EmptyStatement?: SingleVisitor<t.EmptyStatement, S>,
+  ExpressionStatement?: SingleVisitor<t.ExpressionStatement, S>,
+  File?: SingleVisitor<t.File, S>,
+  ForInStatement?: SingleVisitor<t.ForInStatement, S>,
+  ForStatement?: SingleVisitor<t.ForStatement, S>,
+  FunctionDeclaration?: SingleVisitor<t.FunctionDeclaration, S>,
+  FunctionExpression?: SingleVisitor<t.FunctionExpression, S>,
+  Identifier?: SingleVisitor<t.Identifier, S>,
+  IfStatement?: SingleVisitor<t.IfStatement, S>,
+  LabeledStatement?: SingleVisitor<t.LabeledStatement, S>,
+  StringLiteral?: SingleVisitor<t.StringLiteral, S>,
+  NumericLiteral?: SingleVisitor<t.NumericLiteral, S>,
+  NullLiteral?: SingleVisitor<t.NullLiteral, S>,
+  BooleanLiteral?: SingleVisitor<t.BooleanLiteral, S>,
+  RegExpLiteral?: SingleVisitor<t.RegExpLiteral, S>,
+  LogicalExpression?: SingleVisitor<t.LogicalExpression, S>,
+  MemberExpression?: SingleVisitor<t.MemberExpression, S>,
+  NewExpression?: SingleVisitor<t.NewExpression, S>,
+  Program?: SingleVisitor<t.Program, S>,
+  ObjectExpression?: SingleVisitor<t.ObjectExpression, S>,
+  ObjectMethod?: SingleVisitor<t.ObjectMethod, S>,
+  ObjectProperty?: SingleVisitor<t.ObjectProperty, S>,
+  RestElement?: SingleVisitor<t.RestElement, S>,
+  ReturnStatement?: SingleVisitor<t.ReturnStatement, S>,
+  SequenceExpression?: SingleVisitor<t.SequenceExpression, S>,
+  ParenthesizedExpression?: SingleVisitor<t.ParenthesizedExpression, S>,
+  SwitchCase?: SingleVisitor<t.SwitchCase, S>,
+  SwitchStatement?: SingleVisitor<t.SwitchStatement, S>,
+  ThisExpression?: SingleVisitor<t.ThisExpression, S>,
+  ThrowStatement?: SingleVisitor<t.ThrowStatement, S>,
+  TryStatement?: SingleVisitor<t.TryStatement, S>,
+  UnaryExpression?: SingleVisitor<t.UnaryExpression, S>,
+  UpdateExpression?: SingleVisitor<t.UpdateExpression, S>,
+  VariableDeclaration?: SingleVisitor<t.VariableDeclaration, S>,
+  VariableDeclarator?: SingleVisitor<t.VariableDeclarator, S>,
+  WhileStatement?: SingleVisitor<t.WhileStatement, S>,
+  WithStatement?: SingleVisitor<t.WithStatement, S>,
+  AssignmentPattern?: SingleVisitor<t.AssignmentPattern, S>,
+  ArrayPattern?: SingleVisitor<t.ArrayPattern, S>,
+  ArrowFunctionExpression?: SingleVisitor<t.ArrowFunctionExpression, S>,
+  ClassBody?: SingleVisitor<t.ClassBody, S>,
+  ClassExpression?: SingleVisitor<t.ClassExpression, S>,
+  ClassDeclaration?: SingleVisitor<t.ClassDeclaration, S>,
+  ExportAllDeclaration?: SingleVisitor<t.ExportAllDeclaration, S>,
+  ExportDefaultDeclaration?: SingleVisitor<t.ExportDefaultDeclaration, S>,
+  ExportNamedDeclaration?: SingleVisitor<t.ExportNamedDeclaration, S>,
+  ExportSpecifier?: SingleVisitor<t.ExportSpecifier, S>,
+  ForOfStatement?: SingleVisitor<t.ForOfStatement, S>,
+  ImportDeclaration?: SingleVisitor<t.ImportDeclaration, S>,
+  ImportDefaultSpecifier?: SingleVisitor<t.ImportDefaultSpecifier, S>,
+  ImportNamespaceSpecifier?: SingleVisitor<t.ImportNamespaceSpecifier, S>,
+  ImportSpecifier?: SingleVisitor<t.ImportSpecifier, S>,
+  MetaProperty?: SingleVisitor<t.MetaProperty, S>,
+  ClassMethod?: SingleVisitor<t.ClassMethod, S>,
+  ObjectPattern?: SingleVisitor<t.ObjectPattern, S>,
+  SpreadElement?: SingleVisitor<t.SpreadElement, S>,
+  Super?: SingleVisitor<t.Super, S>,
+  TaggedTemplateExpression?: SingleVisitor<t.TaggedTemplateExpression, S>,
+  TemplateElement?: SingleVisitor<t.TemplateElement, S>,
+  TemplateLiteral?: SingleVisitor<t.TemplateLiteral, S>,
+  YieldExpression?: SingleVisitor<t.YieldExpression, S>,
+  AnyTypeAnnotation?: SingleVisitor<t.AnyTypeAnnotation, S>,
+  ArrayTypeAnnotation?: SingleVisitor<t.ArrayTypeAnnotation, S>,
+  BooleanTypeAnnotation?: SingleVisitor<t.BooleanTypeAnnotation, S>,
+  BooleanLiteralTypeAnnotation?: SingleVisitor<
+    t.BooleanLiteralTypeAnnotation,
+    S,
+  >,
+  NullLiteralTypeAnnotation?: SingleVisitor<t.NullLiteralTypeAnnotation, S>,
+  ClassImplements?: SingleVisitor<t.ClassImplements, S>,
+  DeclareClass?: SingleVisitor<t.DeclareClass, S>,
+  DeclareFunction?: SingleVisitor<t.DeclareFunction, S>,
+  DeclareInterface?: SingleVisitor<t.DeclareInterface, S>,
+  DeclareModule?: SingleVisitor<t.DeclareModule, S>,
+  DeclareModuleExports?: SingleVisitor<t.DeclareModuleExports, S>,
+  DeclareTypeAlias?: SingleVisitor<t.DeclareTypeAlias, S>,
+  DeclareOpaqueType?: SingleVisitor<t.DeclareOpaqueType, S>,
+  DeclareVariable?: SingleVisitor<t.DeclareVariable, S>,
+  DeclareExportDeclaration?: SingleVisitor<t.DeclareExportDeclaration, S>,
+  DeclareExportAllDeclaration?: SingleVisitor<t.DeclareExportAllDeclaration, S>,
+  DeclaredPredicate?: SingleVisitor<t.DeclaredPredicate, S>,
+  ExistsTypeAnnotation?: SingleVisitor<t.ExistsTypeAnnotation, S>,
+  FunctionTypeAnnotation?: SingleVisitor<t.FunctionTypeAnnotation, S>,
+  FunctionTypeParam?: SingleVisitor<t.FunctionTypeParam, S>,
+  GenericTypeAnnotation?: SingleVisitor<t.GenericTypeAnnotation, S>,
+  InferredPredicate?: SingleVisitor<t.InferredPredicate, S>,
+  InterfaceExtends?: SingleVisitor<t.InterfaceExtends, S>,
+  InterfaceDeclaration?: SingleVisitor<t.InterfaceDeclaration, S>,
+  InterfaceTypeAnnotation?: SingleVisitor<t.InterfaceTypeAnnotation, S>,
+  IntersectionTypeAnnotation?: SingleVisitor<t.IntersectionTypeAnnotation, S>,
+  MixedTypeAnnotation?: SingleVisitor<t.MixedTypeAnnotation, S>,
+  EmptyTypeAnnotation?: SingleVisitor<t.EmptyTypeAnnotation, S>,
+  NullableTypeAnnotation?: SingleVisitor<t.NullableTypeAnnotation, S>,
+  NumberLiteralTypeAnnotation?: SingleVisitor<t.NumberLiteralTypeAnnotation, S>,
+  NumberTypeAnnotation?: SingleVisitor<t.NumberTypeAnnotation, S>,
+  ObjectTypeAnnotation?: SingleVisitor<t.ObjectTypeAnnotation, S>,
+  ObjectTypeInternalSlot?: SingleVisitor<t.ObjectTypeInternalSlot, S>,
+  ObjectTypeCallProperty?: SingleVisitor<t.ObjectTypeCallProperty, S>,
+  ObjectTypeIndexer?: SingleVisitor<t.ObjectTypeIndexer, S>,
+  ObjectTypeProperty?: SingleVisitor<t.ObjectTypeProperty, S>,
+  ObjectTypeSpreadProperty?: SingleVisitor<t.ObjectTypeSpreadProperty, S>,
+  OpaqueType?: SingleVisitor<t.OpaqueType, S>,
+  QualifiedTypeIdentifier?: SingleVisitor<t.QualifiedTypeIdentifier, S>,
+  StringLiteralTypeAnnotation?: SingleVisitor<t.StringLiteralTypeAnnotation, S>,
+  StringTypeAnnotation?: SingleVisitor<t.StringTypeAnnotation, S>,
+  ThisTypeAnnotation?: SingleVisitor<t.ThisTypeAnnotation, S>,
+  TupleTypeAnnotation?: SingleVisitor<t.TupleTypeAnnotation, S>,
+  TypeofTypeAnnotation?: SingleVisitor<t.TypeofTypeAnnotation, S>,
+  TypeAlias?: SingleVisitor<t.TypeAlias, S>,
+  TypeAnnotation?: SingleVisitor<t.TypeAnnotation, S>,
+  TypeCastExpression?: SingleVisitor<t.TypeCastExpression, S>,
+  TypeParameter?: SingleVisitor<t.TypeParameter, S>,
+  TypeParameterDeclaration?: SingleVisitor<t.TypeParameterDeclaration, S>,
+  TypeParameterInstantiation?: SingleVisitor<t.TypeParameterInstantiation, S>,
+  UnionTypeAnnotation?: SingleVisitor<t.UnionTypeAnnotation, S>,
+  Variance?: SingleVisitor<t.Variance, S>,
+  VoidTypeAnnotation?: SingleVisitor<t.VoidTypeAnnotation, S>,
+  EnumDeclaration?: SingleVisitor<t.EnumDeclaration, S>,
+  EnumBooleanBody?: SingleVisitor<t.EnumBooleanBody, S>,
+  EnumNumberBody?: SingleVisitor<t.EnumNumberBody, S>,
+  EnumStringBody?: SingleVisitor<t.EnumStringBody, S>,
+  EnumSymbolBody?: SingleVisitor<t.EnumSymbolBody, S>,
+  EnumBooleanMember?: SingleVisitor<t.EnumBooleanMember, S>,
+  EnumNumberMember?: SingleVisitor<t.EnumNumberMember, S>,
+  EnumStringMember?: SingleVisitor<t.EnumStringMember, S>,
+  EnumDefaultedMember?: SingleVisitor<t.EnumDefaultedMember, S>,
+  JSXAttribute?: SingleVisitor<t.JSXAttribute, S>,
+  JSXClosingElement?: SingleVisitor<t.JSXClosingElement, S>,
+  JSXElement?: SingleVisitor<t.JSXElement, S>,
+  JSXEmptyExpression?: SingleVisitor<t.JSXEmptyExpression, S>,
+  JSXExpressionContainer?: SingleVisitor<t.JSXExpressionContainer, S>,
+  JSXSpreadChild?: SingleVisitor<t.JSXSpreadChild, S>,
+  JSXIdentifier?: SingleVisitor<t.JSXIdentifier, S>,
+  JSXMemberExpression?: SingleVisitor<t.JSXMemberExpression, S>,
+  JSXNamespacedName?: SingleVisitor<t.JSXNamespacedName, S>,
+  JSXOpeningElement?: SingleVisitor<t.JSXOpeningElement, S>,
+  JSXSpreadAttribute?: SingleVisitor<t.JSXSpreadAttribute, S>,
+  JSXText?: SingleVisitor<t.JSXText, S>,
+  JSXFragment?: SingleVisitor<t.JSXFragment, S>,
+  JSXOpeningFragment?: SingleVisitor<t.JSXOpeningFragment, S>,
+  JSXClosingFragment?: SingleVisitor<t.JSXClosingFragment, S>,
+  Noop?: SingleVisitor<t.Noop, S>,
+  Placeholder?: SingleVisitor<t.Placeholder, S>,
+  V8IntrinsicIdentifier?: SingleVisitor<t.V8IntrinsicIdentifier, S>,
+  ArgumentPlaceholder?: SingleVisitor<t.ArgumentPlaceholder, S>,
+  AwaitExpression?: SingleVisitor<t.AwaitExpression, S>,
+  BindExpression?: SingleVisitor<t.BindExpression, S>,
+  ClassProperty?: SingleVisitor<t.ClassProperty, S>,
+  OptionalMemberExpression?: SingleVisitor<t.OptionalMemberExpression, S>,
+  PipelineTopicExpression?: SingleVisitor<t.PipelineTopicExpression, S>,
+  PipelineBareFunction?: SingleVisitor<t.PipelineBareFunction, S>,
+  PipelinePrimaryTopicReference?: SingleVisitor<
+    t.PipelinePrimaryTopicReference,
+    S,
+  >,
+  OptionalCallExpression?: SingleVisitor<t.OptionalCallExpression, S>,
+  ClassPrivateProperty?: SingleVisitor<t.ClassPrivateProperty, S>,
+  ClassPrivateMethod?: SingleVisitor<t.ClassPrivateMethod, S>,
+  Import?: SingleVisitor<t.Import, S>,
+  Decorator?: SingleVisitor<t.Decorator, S>,
+  DoExpression?: SingleVisitor<t.DoExpression, S>,
+  ExportDefaultSpecifier?: SingleVisitor<t.ExportDefaultSpecifier, S>,
+  ExportNamespaceSpecifier?: SingleVisitor<t.ExportNamespaceSpecifier, S>,
+  PrivateName?: SingleVisitor<t.PrivateName, S>,
+  BigIntLiteral?: SingleVisitor<t.BigIntLiteral, S>,
+  TSParameterProperty?: SingleVisitor<t.TSParameterProperty, S>,
+  TSDeclareFunction?: SingleVisitor<t.TSDeclareFunction, S>,
+  TSDeclareMethod?: SingleVisitor<t.TSDeclareMethod, S>,
+  TSQualifiedName?: SingleVisitor<t.TSQualifiedName, S>,
+  TSCallSignatureDeclaration?: SingleVisitor<t.TSCallSignatureDeclaration, S>,
+  TSConstructSignatureDeclaration?: SingleVisitor<
+    t.TSConstructSignatureDeclaration,
+    S,
+  >,
+  TSPropertySignature?: SingleVisitor<t.TSPropertySignature, S>,
+  TSMethodSignature?: SingleVisitor<t.TSMethodSignature, S>,
+  TSIndexSignature?: SingleVisitor<t.TSIndexSignature, S>,
+  TSAnyKeyword?: SingleVisitor<t.TSAnyKeyword, S>,
+  TSBooleanKeyword?: SingleVisitor<t.TSBooleanKeyword, S>,
+  TSBigIntKeyword?: SingleVisitor<t.TSBigIntKeyword, S>,
+  TSNeverKeyword?: SingleVisitor<t.TSNeverKeyword, S>,
+  TSNullKeyword?: SingleVisitor<t.TSNullKeyword, S>,
+  TSNumberKeyword?: SingleVisitor<t.TSNumberKeyword, S>,
+  TSObjectKeyword?: SingleVisitor<t.TSObjectKeyword, S>,
+  TSStringKeyword?: SingleVisitor<t.TSStringKeyword, S>,
+  TSSymbolKeyword?: SingleVisitor<t.TSSymbolKeyword, S>,
+  TSUndefinedKeyword?: SingleVisitor<t.TSUndefinedKeyword, S>,
+  TSUnknownKeyword?: SingleVisitor<t.TSUnknownKeyword, S>,
+  TSVoidKeyword?: SingleVisitor<t.TSVoidKeyword, S>,
+  TSThisType?: SingleVisitor<t.TSThisType, S>,
+  TSFunctionType?: SingleVisitor<t.TSFunctionType, S>,
+  TSConstructorType?: SingleVisitor<t.TSConstructorType, S>,
+  TSTypeReference?: SingleVisitor<t.TSTypeReference, S>,
+  TSTypePredicate?: SingleVisitor<t.TSTypePredicate, S>,
+  TSTypeQuery?: SingleVisitor<t.TSTypeQuery, S>,
+  TSTypeLiteral?: SingleVisitor<t.TSTypeLiteral, S>,
+  TSArrayType?: SingleVisitor<t.TSArrayType, S>,
+  TSTupleType?: SingleVisitor<t.TSTupleType, S>,
+  TSOptionalType?: SingleVisitor<t.TSOptionalType, S>,
+  TSRestType?: SingleVisitor<t.TSRestType, S>,
+  TSUnionType?: SingleVisitor<t.TSUnionType, S>,
+  TSIntersectionType?: SingleVisitor<t.TSIntersectionType, S>,
+  TSConditionalType?: SingleVisitor<t.TSConditionalType, S>,
+  TSInferType?: SingleVisitor<t.TSInferType, S>,
+  TSParenthesizedType?: SingleVisitor<t.TSParenthesizedType, S>,
+  TSTypeOperator?: SingleVisitor<t.TSTypeOperator, S>,
+  TSIndexedAccessType?: SingleVisitor<t.TSIndexedAccessType, S>,
+  TSMappedType?: SingleVisitor<t.TSMappedType, S>,
+  TSLiteralType?: SingleVisitor<t.TSLiteralType, S>,
+  TSExpressionWithTypeArguments?: SingleVisitor<
+    t.TSExpressionWithTypeArguments,
+    S,
+  >,
+  TSInterfaceDeclaration?: SingleVisitor<t.TSInterfaceDeclaration, S>,
+  TSInterfaceBody?: SingleVisitor<t.TSInterfaceBody, S>,
+  TSTypeAliasDeclaration?: SingleVisitor<t.TSTypeAliasDeclaration, S>,
+  TSAsExpression?: SingleVisitor<t.TSAsExpression, S>,
+  TSTypeAssertion?: SingleVisitor<t.TSTypeAssertion, S>,
+  TSEnumDeclaration?: SingleVisitor<t.TSEnumDeclaration, S>,
+  TSEnumMember?: SingleVisitor<t.TSEnumMember, S>,
+  TSModuleDeclaration?: SingleVisitor<t.TSModuleDeclaration, S>,
+  TSModuleBlock?: SingleVisitor<t.TSModuleBlock, S>,
+  TSImportType?: SingleVisitor<t.TSImportType, S>,
+  TSImportEqualsDeclaration?: SingleVisitor<t.TSImportEqualsDeclaration, S>,
+  TSExternalModuleReference?: SingleVisitor<t.TSExternalModuleReference, S>,
+  TSNonNullExpression?: SingleVisitor<t.TSNonNullExpression, S>,
+  TSExportAssignment?: SingleVisitor<t.TSExportAssignment, S>,
+  TSNamespaceExportDeclaration?: SingleVisitor<
+    t.TSNamespaceExportDeclaration,
+    S,
+  >,
+  TSTypeAnnotation?: SingleVisitor<t.TSTypeAnnotation, S>,
+  TSTypeParameterInstantiation?: SingleVisitor<
+    t.TSTypeParameterInstantiation,
+    S,
+  >,
+  TSTypeParameterDeclaration?: SingleVisitor<t.TSTypeParameterDeclaration, S>,
+  TSTypeParameter?: SingleVisitor<t.TSTypeParameter, S>,
+  Expression?: SingleVisitor<t.Expression, S>,
+  Binary?: SingleVisitor<t.Binary, S>,
+  Scopable?: SingleVisitor<t.Scopable, S>,
+  BlockParent?: SingleVisitor<t.BlockParent, S>,
+  Block?: SingleVisitor<t.Block, S>,
+  Statement?: SingleVisitor<t.Statement, S>,
+  Terminatorless?: SingleVisitor<t.Terminatorless, S>,
+  CompletionStatement?: SingleVisitor<t.CompletionStatement, S>,
+  Conditional?: SingleVisitor<t.Conditional, S>,
+  Loop?: SingleVisitor<t.Loop, S>,
+  While?: SingleVisitor<t.While, S>,
+  ExpressionWrapper?: SingleVisitor<t.ExpressionWrapper, S>,
+  For?: SingleVisitor<t.For, S>,
+  ForXStatement?: SingleVisitor<t.ForXStatement, S>,
+  FunctionParent?: SingleVisitor<t.FunctionParent, S>,
+  Pureish?: SingleVisitor<t.Pureish, S>,
+  Declaration?: SingleVisitor<t.Declaration, S>,
+  PatternLike?: SingleVisitor<t.PatternLike, S>,
+  LVal?: SingleVisitor<t.LVal, S>,
+  TSEntityName?: SingleVisitor<t.TSEntityName, S>,
+  Literal?: SingleVisitor<t.Literal, S>,
+  Immutable?: SingleVisitor<t.Immutable, S>,
+  UserWhitespacable?: SingleVisitor<t.UserWhitespacable, S>,
+  Method?: SingleVisitor<t.Method, S>,
+  ObjectMember?: SingleVisitor<t.ObjectMember, S>,
+  Property?: SingleVisitor<t.Property, S>,
+  UnaryLike?: SingleVisitor<t.UnaryLike, S>,
+  Pattern?: SingleVisitor<t.Pattern, S>,
+  ModuleDeclaration?: SingleVisitor<t.ModuleDeclaration, S>,
+  ExportDeclaration?: SingleVisitor<t.ExportDeclaration, S>,
+  ModuleSpecifier?: SingleVisitor<t.ModuleSpecifier, S>,
+  Flow?: SingleVisitor<t.Flow, S>,
+  FlowType?: SingleVisitor<t.FlowType, S>,
+  FlowBaseAnnotation?: SingleVisitor<t.FlowBaseAnnotation, S>,
+  FlowDeclaration?: SingleVisitor<t.FlowDeclaration, S>,
+  FlowPredicate?: SingleVisitor<t.FlowPredicate, S>,
+  EnumBody?: SingleVisitor<t.EnumBody, S>,
+  EnumMember?: SingleVisitor<t.EnumMember, S>,
+  JSX?: SingleVisitor<t.JSX, S>,
+  Private?: SingleVisitor<t.Private, S>,
+  TSTypeElement?: SingleVisitor<t.TSTypeElement, S>,
+  TSType?: SingleVisitor<t.TSType, S>,
+  [string]: SingleVisitor<t.Node, S>,
+  ...
+};
+
+export type SimpleVisitors<F> = {
+  [string]: F | {|enter?: F, exit?: F|},
+  shouldSkip?: t.Node => boolean,
+  ...
+};
+
+export type VisitorsExploded<F> = {
+  [string]: {|
+    enter?: Array<F>,
+    exit?: Array<F>,
+  |},
+  shouldSkip?: t.Node => boolean,
+  ...
+};
diff --git a/yarn.lock b/yarn.lock
index f3d8ca59b..3a03abfbc 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -118,7 +118,7 @@
     lodash "^4.17.13"
     source-map "^0.5.0"
 
-"@babel/generator@^7.12.1", "@babel/generator@^7.9.0", "@babel/generator@^7.9.5":
+"@babel/generator@^7.12.1", "@babel/generator@^7.9.0":
   version "7.12.1"
   resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.1.tgz#0d70be32bdaa03d7c51c8597dda76e0df1f15468"
   integrity sha512-DB+6rafIdc9o72Yc3/Ph5h+6hUjeOp66pF0naQBgUFFuPqzQwIlPTm3xZR7YNvduIMtkDIj2t21LSQwnbCrXvg==
@@ -127,6 +127,15 @@
     jsesc "^2.5.1"
     source-map "^0.5.0"
 
+"@babel/generator@^7.12.5":
+  version "7.12.5"
+  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.12.5.tgz#a2c50de5c8b6d708ab95be5e6053936c1884a4de"
+  integrity sha512-m16TQQJ8hPt7E+OS/XVQg/7U184MLXtvuGbCdA7na61vha+ImkyyNM/9DDA0unYCVZn3ZOhng+qz48/KBOT96A==
+  dependencies:
+    "@babel/types" "^7.12.5"
+    jsesc "^2.5.1"
+    source-map "^0.5.0"
+
 "@babel/helper-annotate-as-pure@^7.8.3":
   version "7.8.3"
   resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz#60bc0bc657f63a0924ff9a4b4a0b24a13cf4deee"
@@ -390,12 +399,12 @@
   resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.5.tgz#c7ff6303df71080ec7a4f5b8c003c58f1cf51037"
   integrity sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==
 
-"@babel/parser@^7.10.4", "@babel/parser@^7.12.1", "@babel/parser@^7.12.3", "@babel/parser@^7.9.0":
+"@babel/parser@^7.10.4", "@babel/parser@^7.12.3", "@babel/parser@^7.9.0":
   version "7.12.3"
   resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.3.tgz#a305415ebe7a6c7023b40b5122a0662d928334cd"
   integrity sha512-kFsOS0IbsuhO5ojF8Hc8z/8vEIOkylVBrjiZUbLTE3XFe0Qi+uu6HjzQixkFaqr0ZPAMZcBVxEwmsnsLPZ2Xsw==
 
-"@babel/parser@^7.12.0":
+"@babel/parser@^7.12.0", "@babel/parser@^7.12.7":
   version "7.12.7"
   resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.12.7.tgz#fee7b39fe809d0e73e5b25eecaf5780ef3d73056"
   integrity sha512-oWR02Ubp4xTLCAqPRiNIuMVgNO5Aif/xpXtabhzW2HWUD47XJsAB4Zd/Rg30+XeQA3juXigV7hlquOTmwqLiwg==
@@ -1054,55 +1063,22 @@
     "@babel/parser" "^7.10.4"
     "@babel/types" "^7.10.4"
 
-"@babel/traverse@^7.0.0", "@babel/traverse@^7.2.3", "@babel/traverse@^7.4.4", "@babel/traverse@^7.8.0", "@babel/traverse@^7.8.3":
-  version "7.9.5"
-  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.5.tgz#6e7c56b44e2ac7011a948c21e283ddd9d9db97a2"
-  integrity sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ==
-  dependencies:
-    "@babel/code-frame" "^7.8.3"
-    "@babel/generator" "^7.9.5"
-    "@babel/helper-function-name" "^7.9.5"
-    "@babel/helper-split-export-declaration" "^7.8.3"
-    "@babel/parser" "^7.9.0"
-    "@babel/types" "^7.9.5"
-    debug "^4.1.0"
-    globals "^11.1.0"
-    lodash "^4.17.13"
-
-"@babel/traverse@^7.12.1", "@babel/traverse@^7.8.6", "@babel/traverse@^7.9.0":
-  version "7.12.1"
-  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.1.tgz#941395e0c5cc86d5d3e75caa095d3924526f0c1e"
-  integrity sha512-MA3WPoRt1ZHo2ZmoGKNqi20YnPt0B1S0GTZEPhhd+hw2KGUzBlHuVunj6K4sNuK+reEvyiPwtp0cpaqLzJDmAw==
+"@babel/traverse@^7.0.0", "@babel/traverse@^7.12.1", "@babel/traverse@^7.2.3", "@babel/traverse@^7.4.4", "@babel/traverse@^7.8.0", "@babel/traverse@^7.8.3", "@babel/traverse@^7.8.6", "@babel/traverse@^7.9.0":
+  version "7.12.9"
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.12.9.tgz#fad26c972eabbc11350e0b695978de6cc8e8596f"
+  integrity sha512-iX9ajqnLdoU1s1nHt36JDI9KG4k+vmI8WgjK5d+aDTwQbL2fUnzedNedssA645Ede3PM2ma1n8Q4h2ohwXgMXw==
   dependencies:
     "@babel/code-frame" "^7.10.4"
-    "@babel/generator" "^7.12.1"
+    "@babel/generator" "^7.12.5"
     "@babel/helper-function-name" "^7.10.4"
     "@babel/helper-split-export-declaration" "^7.11.0"
-    "@babel/parser" "^7.12.1"
-    "@babel/types" "^7.12.1"
+    "@babel/parser" "^7.12.7"
+    "@babel/types" "^7.12.7"
     debug "^4.1.0"
     globals "^11.1.0"
     lodash "^4.17.19"
 
-"@babel/types@^7.0.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.8.0":
-  version "7.11.5"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.11.5.tgz#d9de577d01252d77c6800cee039ee64faf75662d"
-  integrity sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==
-  dependencies:
-    "@babel/helper-validator-identifier" "^7.10.4"
-    lodash "^4.17.19"
-    to-fast-properties "^2.0.0"
-
-"@babel/types@^7.10.4", "@babel/types@^7.11.0", "@babel/types@^7.12.1", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5":
-  version "7.12.1"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.1.tgz#e109d9ab99a8de735be287ee3d6a9947a190c4ae"
-  integrity sha512-BzSY3NJBKM4kyatSOWh3D/JJ2O3CVzBybHWxtgxnggaxEuaSTTDqeiSb/xk9lrkw2Tbqyivw5ZU4rT+EfznQsA==
-  dependencies:
-    "@babel/helper-validator-identifier" "^7.10.4"
-    lodash "^4.17.19"
-    to-fast-properties "^2.0.0"
-
-"@babel/types@^7.12.0":
+"@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.11.0", "@babel/types@^7.12.0", "@babel/types@^7.12.1", "@babel/types@^7.12.5", "@babel/types@^7.12.7", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.8.0", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5":
   version "7.12.7"
   resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.12.7.tgz#6039ff1e242640a29452c9ae572162ec9a8f5d13"
   integrity sha512-MNyI92qZq6jrQkXvtIiykvl4WtoRrVV9MPn+ZfsoEENjiWcBQ3ZSHrkxnJWgWtLX3XXqX5hrSQ+X69wkmesXuQ==


Test patch:
diff --git a/packages/core/core/test/Environment.test.js b/packages/core/core/test/Environment.test.js
index 40dd9b048..2aeaa3a5b 100644
--- a/packages/core/core/test/Environment.test.js
+++ b/packages/core/core/test/Environment.test.js
@@ -6,6 +6,7 @@ import {createEnvironment} from '../src/Environment';
 describe('Environment', () => {
   it('assigns a default environment with nothing passed', () => {
     assert.deepEqual(createEnvironment(), {
+      id: 'd893b0f359833ebee3d11f698c4dc13d',
       context: 'browser',
       engines: {
         browsers: ['> 0.25%'],
@@ -21,6 +22,7 @@ describe('Environment', () => {
 
   it('assigns a node context if a node engine is given', () => {
     assert.deepEqual(createEnvironment({engines: {node: '>= 10.0.0'}}), {
+      id: '12f0c7bb61ca06ca47583860e07602c1',
       context: 'node',
       engines: {
         node: '>= 10.0.0',
@@ -38,6 +40,7 @@ describe('Environment', () => {
     assert.deepEqual(
       createEnvironment({engines: {browsers: ['last 1 version']}}),
       {
+        id: '618f428f683b1b2f0a59a84dbbf66200',
         context: 'browser',
         engines: {
           browsers: ['last 1 version'],
@@ -54,6 +57,7 @@ describe('Environment', () => {
 
   it('assigns default engines for node', () => {
     assert.deepEqual(createEnvironment({context: 'node'}), {
+      id: 'cebf5179f904ad2c9432ca3dd6222bfe',
       context: 'node',
       engines: {
         node: '>= 8.0.0',
@@ -69,6 +73,7 @@ describe('Environment', () => {
 
   it('assigns default engines for browsers', () => {
     assert.deepEqual(createEnvironment({context: 'browser'}), {
+      id: 'd893b0f359833ebee3d11f698c4dc13d',
       context: 'browser',
       engines: {
         browsers: ['> 0.25%'],
diff --git a/packages/core/core/test/TargetRequest.test.js b/packages/core/core/test/TargetRequest.test.js
index 643450d51..b6dcd60fb 100644
--- a/packages/core/core/test/TargetRequest.test.js
+++ b/packages/core/core/test/TargetRequest.test.js
@@ -103,6 +103,7 @@ describe('TargetResolver', () => {
           publicUrl: '/',
           distDir: path.resolve('customA'),
           env: {
+            id: '2bbfcf5ea2bb87e160da13caff67db8f',
             context: 'browser',
             includeNodeModules: true,
             engines: {
@@ -120,6 +121,7 @@ describe('TargetResolver', () => {
           publicUrl: '/',
           distDir: path.resolve('customB'),
           env: {
+            id: '6998383bc9be968857af07758ec34b4f',
             context: 'node',
             includeNodeModules: false,
             engines: {
@@ -148,6 +150,7 @@ describe('TargetResolver', () => {
           distEntry: 'index.js',
           publicUrl: '/',
           env: {
+            id: 'f4baf35b3b94e63f7c6307153fce8f3e',
             context: 'node',
             engines: {
               node: '>= 8.0.0',
@@ -177,6 +180,7 @@ describe('TargetResolver', () => {
           distEntry: 'index.js',
           publicUrl: '/',
           env: {
+            id: '5a263676bee4cf8248c32f0b48b22902',
             context: 'browser',
             engines: {
               browsers: ['last 1 version'],
@@ -208,6 +212,7 @@ describe('TargetResolver', () => {
           distEntry: 'index.js',
           publicUrl: '/assets',
           env: {
+            id: 'ee3d4744fab93996af88b671fb4a0029',
             context: 'browser',
             engines: {
               browsers: ['last 1 version'],
@@ -247,6 +252,7 @@ describe('TargetResolver', () => {
           distEntry: 'index.js',
           publicUrl: '/',
           env: {
+            id: 'cebf5179f904ad2c9432ca3dd6222bfe',
             context: 'node',
             engines: {
               node: '>= 8.0.0',
@@ -288,6 +294,7 @@ describe('TargetResolver', () => {
           distEntry: 'index.js',
           publicUrl: '/',
           env: {
+            id: 'f4baf35b3b94e63f7c6307153fce8f3e',
             context: 'node',
             engines: {
               node: '>= 8.0.0',
@@ -320,6 +327,7 @@ describe('TargetResolver', () => {
           distEntry: 'index.js',
           publicUrl: '/',
           env: {
+            id: 'f6f700fe6b930b17f56439448c57e060',
             context: 'browser',
             engines: {
               browsers: ['last 1 version'],
@@ -352,6 +360,7 @@ describe('TargetResolver', () => {
           distEntry: 'index.js',
           publicUrl: '/',
           env: {
+            id: '61e2044abf03e54962ab861fc0c8e3a8',
             context: 'browser',
             engines: {
               browsers: ['ie11'],
@@ -390,6 +399,7 @@ describe('TargetResolver', () => {
           distEntry: undefined,
           publicUrl: 'www',
           env: {
+            id: 'fbd1c2018703515f23707d6fb810642e',
             context: 'browser',
             engines: {
               browsers: '> 0.25%',
@@ -416,6 +426,7 @@ describe('TargetResolver', () => {
         distEntry: 'index.js',
         publicUrl: '/',
         env: {
+          id: '60e4160438cbc3393873f192d9d5d162',
           context: 'node',
           engines: {},
           includeNodeModules: false,
@@ -450,6 +461,7 @@ describe('TargetResolver', () => {
         distEntry: 'index.html',
         publicUrl: '/',
         env: {
+          id: '731766329e2871538bb5cbfdbe551905',
           context: 'browser',
           engines: {},
           includeNodeModules: true,
@@ -489,6 +501,7 @@ describe('TargetResolver', () => {
           distEntry: 'index.js',
           publicUrl: '/',
           env: {
+            id: 'f4baf35b3b94e63f7c6307153fce8f3e',
             context: 'node',
             engines: {
               node: '>= 8.0.0',
@@ -518,6 +531,7 @@ describe('TargetResolver', () => {
           distEntry: 'index.js',
           publicUrl: '/assets',
           env: {
+            id: 'ee3d4744fab93996af88b671fb4a0029',
             context: 'browser',
             engines: {
               browsers: ['last 1 version'],
@@ -561,6 +575,7 @@ describe('TargetResolver', () => {
           distDir: serveDistDir,
           publicUrl: '/',
           env: {
+            id: '731766329e2871538bb5cbfdbe551905',
             context: 'browser',
             engines: {},
             includeNodeModules: true,
@@ -586,6 +601,7 @@ describe('TargetResolver', () => {
           distDir: path.join(DEFAULT_DISTPATH_FIXTURE_PATHS.none, 'dist'),
           publicUrl: '/',
           env: {
+            id: 'f30b1b1f9bc625becd70ec6bc8198eb3',
             context: 'browser',
             engines: {
               browsers: ['Chrome 80'],
@@ -614,6 +630,7 @@ describe('TargetResolver', () => {
           distEntry: undefined,
           publicUrl: '/',
           env: {
+            id: 'f30b1b1f9bc625becd70ec6bc8198eb3',
             context: 'browser',
             engines: {
               browsers: ['Chrome 80'],
@@ -647,6 +664,7 @@ describe('TargetResolver', () => {
           distEntry: undefined,
           publicUrl: '/',
           env: {
+            id: 'f6f700fe6b930b17f56439448c57e060',
             context: 'browser',
             engines: {
               browsers: ['last 1 version'],
@@ -670,6 +688,7 @@ describe('TargetResolver', () => {
           distEntry: undefined,
           publicUrl: '/',
           env: {
+            id: '0036eed1f5d93ad9b6510b2e27210cec',
             context: 'browser',
             engines: {
               browsers: ['IE 11'],
diff --git a/packages/core/integration-tests/test/cache.js b/packages/core/integration-tests/test/cache.js
index 4e1de620e..fd481b6da 100644
--- a/packages/core/integration-tests/test/cache.js
+++ b/packages/core/integration-tests/test/cache.js
@@ -1260,11 +1260,11 @@ describe('cache', function() {
           assertBundles(b.bundleGraph, [
             {
               name: 'a.js',
-              assets: ['a.js'],
+              assets: ['a.js', 'esmodule-helpers.js'],
             },
             {
               name: 'b.js',
-              assets: ['b.js'],
+              assets: ['b.js', 'esmodule-helpers.js'],
             },
           ]);
 
@@ -1278,15 +1278,15 @@ describe('cache', function() {
       assertBundles(b.bundleGraph, [
         {
           name: 'a.js',
-          assets: ['a.js'],
+          assets: ['a.js', 'esmodule-helpers.js'],
         },
         {
           name: 'b.js',
-          assets: ['b.js'],
+          assets: ['b.js', 'esmodule-helpers.js'],
         },
         {
           name: 'c.js',
-          assets: ['c.js'],
+          assets: ['c.js', 'esmodule-helpers.js'],
         },
       ]);
     });
@@ -1298,11 +1298,11 @@ describe('cache', function() {
           assertBundles(b.bundleGraph, [
             {
               name: 'a.js',
-              assets: ['a.js'],
+              assets: ['a.js', 'esmodule-helpers.js'],
             },
             {
               name: 'b.js',
-              assets: ['b.js'],
+              assets: ['b.js', 'esmodule-helpers.js'],
             },
           ]);
 
@@ -1313,7 +1313,7 @@ describe('cache', function() {
       assertBundles(b.bundleGraph, [
         {
           name: 'a.js',
-          assets: ['a.js'],
+          assets: ['a.js', 'esmodule-helpers.js'],
         },
       ]);
     });
diff --git a/packages/core/integration-tests/test/fs.js b/packages/core/integration-tests/test/fs.js
index 94b5cdb52..cb27fd28f 100644
--- a/packages/core/integration-tests/test/fs.js
+++ b/packages/core/integration-tests/test/fs.js
@@ -140,7 +140,12 @@ describe('fs', function() {
         {
           name: 'ignore-fs.js',
           // empty.js is generated by require('fs'), it gets mocked with an empty module
-          assets: ['_empty.js', 'ignore-fs.js', 'index.js'],
+          assets: [
+            '_empty.js',
+            'esmodule-helpers.js',
+            'ignore-fs.js',
+            'index.js',
+          ],
         },
       ]);
       // $FlowFixMe[incompatible-call]
diff --git a/packages/core/integration-tests/test/html.js b/packages/core/integration-tests/test/html.js
index a9d64b524..41ca23c26 100644
--- a/packages/core/integration-tests/test/html.js
+++ b/packages/core/integration-tests/test/html.js
@@ -351,7 +351,7 @@ describe('html', function() {
       },
       {
         type: 'js',
-        assets: ['component-1.js', 'obj.js'],
+        assets: ['component-1.js', 'obj.js', 'esmodule-helpers.js'],
       },
       {
         type: 'js',
@@ -1431,7 +1431,7 @@ describe('html', function() {
       },
       {
         type: 'js',
-        assets: ['a.js', 'worker.js'],
+        assets: ['a.js', 'worker.js', 'esmodule-helpers.js'],
       },
       {
         type: 'js',
@@ -1439,6 +1439,7 @@ describe('html', function() {
           'a.js',
           'bundle-manifest.js',
           'bundle-url.js',
+          'esmodule-helpers.js',
           'get-worker-url.js',
           'index.js',
           'JSRuntime.js',
@@ -1472,7 +1473,7 @@ describe('html', function() {
       },
       {
         type: 'js',
-        assets: ['form.js', 'a.js', 'a.module.css'],
+        assets: ['form.js', 'a.js', 'a.module.css', 'esmodule-helpers.js'],
       },
       {
         type: 'css',
@@ -1484,7 +1485,13 @@ describe('html', function() {
       },
       {
         type: 'js',
-        assets: ['searchfield.js', 'a.js', 'a.module.css', 'b.js'],
+        assets: [
+          'searchfield.js',
+          'a.js',
+          'a.module.css',
+          'b.js',
+          'esmodule-helpers.js',
+        ],
       },
       {
         type: 'html',
@@ -1492,7 +1499,13 @@ describe('html', function() {
       },
       {
         type: 'js',
-        assets: ['searchfield2.js', 'a.js', 'a.module.css', 'b.js'],
+        assets: [
+          'searchfield2.js',
+          'a.js',
+          'a.module.css',
+          'b.js',
+          'esmodule-helpers.js',
+        ],
       },
     ]);
 
diff --git a/packages/core/integration-tests/test/integration/js-duplicate-re-exports/a.js b/packages/core/integration-tests/test/integration/js-duplicate-re-exports/a.js
new file mode 100644
index 000000000..ecd46a016
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-duplicate-re-exports/a.js
@@ -0,0 +1,2 @@
+export { c } from './c.js';
+export const a = "a";
diff --git a/packages/core/integration-tests/test/integration/js-duplicate-re-exports/b.js b/packages/core/integration-tests/test/integration/js-duplicate-re-exports/b.js
new file mode 100644
index 000000000..f4ae7b0c6
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-duplicate-re-exports/b.js
@@ -0,0 +1,2 @@
+export { c } from './c.js';
+export const b = "b";
diff --git a/packages/core/integration-tests/test/integration/js-duplicate-re-exports/c.js b/packages/core/integration-tests/test/integration/js-duplicate-re-exports/c.js
new file mode 100644
index 000000000..d9813b6b0
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-duplicate-re-exports/c.js
@@ -0,0 +1,3 @@
+export function c() {
+	return false;
+}
diff --git a/packages/core/integration-tests/test/integration/js-duplicate-re-exports/index.js b/packages/core/integration-tests/test/integration/js-duplicate-re-exports/index.js
new file mode 100644
index 000000000..ae51ee828
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-duplicate-re-exports/index.js
@@ -0,0 +1,3 @@
+import { a, b, c } from "./utilities.js";
+
+module.exports = {a, b, c};
diff --git a/packages/core/integration-tests/test/integration/js-duplicate-re-exports/utilities.js b/packages/core/integration-tests/test/integration/js-duplicate-re-exports/utilities.js
new file mode 100644
index 000000000..240b21dc4
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-duplicate-re-exports/utilities.js
@@ -0,0 +1,2 @@
+export * from "./a.js";
+export * from "./b.js";
diff --git a/packages/core/integration-tests/test/integration/js-export-all/index.js b/packages/core/integration-tests/test/integration/js-export-all/index.js
new file mode 100644
index 000000000..a756f8476
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-export-all/index.js
@@ -0,0 +1,1 @@
+export * from './other';
diff --git a/packages/core/integration-tests/test/integration/js-export-all/other.js b/packages/core/integration-tests/test/integration/js-export-all/other.js
new file mode 100644
index 000000000..dff05d0cb
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-export-all/other.js
@@ -0,0 +1,2 @@
+export default 1;
+export const a = 4;
diff --git a/packages/core/integration-tests/test/integration/js-export-default/.babelrc b/packages/core/integration-tests/test/integration/js-export-default/.babelrc
new file mode 100644
index 000000000..e32ac4ec5
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-export-default/.babelrc
@@ -0,0 +1,3 @@
+{
+  "plugins": ["@babel/plugin-syntax-export-default-from"]
+}
diff --git a/packages/core/integration-tests/test/integration/js-export-default/index.js b/packages/core/integration-tests/test/integration/js-export-default/index.js
new file mode 100644
index 000000000..230e67cec
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-export-default/index.js
@@ -0,0 +1,1 @@
+export other from './other';
diff --git a/packages/core/integration-tests/test/integration/js-export-default/other.js b/packages/core/integration-tests/test/integration/js-export-default/other.js
new file mode 100644
index 000000000..dff05d0cb
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-export-default/other.js
@@ -0,0 +1,2 @@
+export default 1;
+export const a = 4;
diff --git a/packages/core/integration-tests/test/integration/js-export-functions/index.js b/packages/core/integration-tests/test/integration/js-export-functions/index.js
new file mode 100644
index 000000000..fc40d66df
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-export-functions/index.js
@@ -0,0 +1,7 @@
+export function foo(arg1) {
+  return 'foo:' + arg1;
+}
+
+export function bar(arg2) {
+  return 'bar:' + arg2;
+}
diff --git a/packages/core/integration-tests/test/integration/js-export-import/index.js b/packages/core/integration-tests/test/integration/js-export-import/index.js
new file mode 100644
index 000000000..f81f63208
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-export-import/index.js
@@ -0,0 +1,2 @@
+import other from './other';
+export {other};
diff --git a/packages/core/integration-tests/test/integration/js-export-import/other.js b/packages/core/integration-tests/test/integration/js-export-import/other.js
new file mode 100644
index 000000000..842e368a0
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-export-import/other.js
@@ -0,0 +1,1 @@
+export default 2;
diff --git a/packages/core/integration-tests/test/integration/js-export-many/bar.js b/packages/core/integration-tests/test/integration/js-export-many/bar.js
new file mode 100644
index 000000000..9f1738685
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-export-many/bar.js
@@ -0,0 +1,1 @@
+export const bar = 'bar';
diff --git a/packages/core/integration-tests/test/integration/js-export-many/baz.js b/packages/core/integration-tests/test/integration/js-export-many/baz.js
new file mode 100644
index 000000000..79d82c6d8
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-export-many/baz.js
@@ -0,0 +1,1 @@
+export default 'baz';
diff --git a/packages/core/integration-tests/test/integration/js-export-many/boo.js b/packages/core/integration-tests/test/integration/js-export-many/boo.js
new file mode 100644
index 000000000..06819b95c
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-export-many/boo.js
@@ -0,0 +1,1 @@
+export const boo = 'boo';
diff --git a/packages/core/integration-tests/test/integration/js-export-many/foo.js b/packages/core/integration-tests/test/integration/js-export-many/foo.js
new file mode 100644
index 000000000..d02ba545b
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-export-many/foo.js
@@ -0,0 +1,1 @@
+export default 'foo';
diff --git a/packages/core/integration-tests/test/integration/js-export-many/foobar.js b/packages/core/integration-tests/test/integration/js-export-many/foobar.js
new file mode 100644
index 000000000..c679e7315
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-export-many/foobar.js
@@ -0,0 +1,1 @@
+export default 'foobar';
diff --git a/packages/core/integration-tests/test/integration/js-export-many/index.js b/packages/core/integration-tests/test/integration/js-export-many/index.js
new file mode 100644
index 000000000..da7906e08
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-export-many/index.js
@@ -0,0 +1,6 @@
+export { default as foo } from './foo';
+export { bar } from './bar';
+export { default } from './baz';
+export { boo } from './boo';
+export { default as foobar } from './foobar';
+export * from './types';
diff --git a/packages/core/integration-tests/test/integration/js-export-many/types.js b/packages/core/integration-tests/test/integration/js-export-many/types.js
new file mode 100644
index 000000000..3a9d6eb8a
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-export-many/types.js
@@ -0,0 +1,2 @@
+export const type1 = 'type1';
+export const type2 = 'type2';
diff --git a/packages/core/integration-tests/test/integration/js-export-namespace/index.js b/packages/core/integration-tests/test/integration/js-export-namespace/index.js
new file mode 100644
index 000000000..eeaf8f6fe
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-export-namespace/index.js
@@ -0,0 +1,1 @@
+export * as ns from './other';
diff --git a/packages/core/integration-tests/test/integration/js-export-namespace/other.js b/packages/core/integration-tests/test/integration/js-export-namespace/other.js
new file mode 100644
index 000000000..dff05d0cb
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-export-namespace/other.js
@@ -0,0 +1,2 @@
+export default 1;
+export const a = 4;
diff --git a/packages/core/integration-tests/test/integration/js-import-namespace/a.js b/packages/core/integration-tests/test/integration/js-import-namespace/a.js
new file mode 100644
index 000000000..38c50c2d2
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-import-namespace/a.js
@@ -0,0 +1,2 @@
+import * as n from './other';
+module.exports = n;
diff --git a/packages/core/integration-tests/test/integration/js-import-namespace/b.js b/packages/core/integration-tests/test/integration/js-import-namespace/b.js
new file mode 100644
index 000000000..7c62fbc35
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-import-namespace/b.js
@@ -0,0 +1,2 @@
+import * as n from './class';
+module.exports = n;
diff --git a/packages/core/integration-tests/test/integration/js-import-namespace/c.js b/packages/core/integration-tests/test/integration/js-import-namespace/c.js
new file mode 100644
index 000000000..ae2c7c649
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-import-namespace/c.js
@@ -0,0 +1,2 @@
+import * as n from './object.js';
+module.exports = n;
diff --git a/packages/core/integration-tests/test/integration/js-import-namespace/class.js b/packages/core/integration-tests/test/integration/js-import-namespace/class.js
new file mode 100644
index 000000000..cb1677cfe
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-import-namespace/class.js
@@ -0,0 +1,1 @@
+module.exports = class {}
diff --git a/packages/core/integration-tests/test/integration/js-import-namespace/object.js b/packages/core/integration-tests/test/integration/js-import-namespace/object.js
new file mode 100644
index 000000000..b62bb812a
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-import-namespace/object.js
@@ -0,0 +1,4 @@
+module.exports = {
+  foo: 2,
+  default: 3
+};
diff --git a/packages/core/integration-tests/test/integration/js-import-namespace/other.js b/packages/core/integration-tests/test/integration/js-import-namespace/other.js
new file mode 100644
index 000000000..dff05d0cb
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-import-namespace/other.js
@@ -0,0 +1,2 @@
+export default 1;
+export const a = 4;
diff --git a/packages/core/integration-tests/test/integration/js-import-shadow/foo.js b/packages/core/integration-tests/test/integration/js-import-shadow/foo.js
new file mode 100644
index 000000000..7efce94a3
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-import-shadow/foo.js
@@ -0,0 +1,3 @@
+export function foo() {
+  return 'foo';
+}
diff --git a/packages/core/integration-tests/test/integration/js-import-shadow/index.js b/packages/core/integration-tests/test/integration/js-import-shadow/index.js
new file mode 100644
index 000000000..0e76b276c
--- /dev/null
+++ b/packages/core/integration-tests/test/integration/js-import-shadow/index.js
@@ -0,0 +1,5 @@
+import { foo } from './foo';
+export function baz() {
+  const _foo = foo();
+  return _foo;
+}
diff --git a/packages/core/integration-tests/test/integration/scope-hoisting/commonjs/side-effects-false/node_modules/bar/index.js b/packages/core/integration-tests/test/integration/scope-hoisting/commonjs/side-effects-false/node_modules/bar/index.js
index f9e940c9f..cf61931c9 100644
--- a/packages/core/integration-tests/test/integration/scope-hoisting/commonjs/side-effects-false/node_modules/bar/index.js
+++ b/packages/core/integration-tests/test/integration/scope-hoisting/commonjs/side-effects-false/node_modules/bar/index.js
@@ -1,2 +1,2 @@
-export foo from './foo';
-export bar from './bar';
+export {default as foo} from './foo';
+export {default as bar} from './bar';
diff --git a/packages/core/integration-tests/test/integration/scope-hoisting/es6/side-effects-array/node_modules/bar/index.js b/packages/core/integration-tests/test/integration/scope-hoisting/es6/side-effects-array/node_modules/bar/index.js
index f9e940c9f..cf61931c9 100644
--- a/packages/core/integration-tests/test/integration/scope-hoisting/es6/side-effects-array/node_modules/bar/index.js
+++ b/packages/core/integration-tests/test/integration/scope-hoisting/es6/side-effects-array/node_modules/bar/index.js
@@ -1,2 +1,2 @@
-export foo from './foo';
-export bar from './bar';
+export {default as foo} from './foo';
+export {default as bar} from './bar';
diff --git a/packages/core/integration-tests/test/integration/scope-hoisting/es6/side-effects-false-duplicate/node_modules/bar/index.js b/packages/core/integration-tests/test/integration/scope-hoisting/es6/side-effects-false-duplicate/node_modules/bar/index.js
index 84651fcca..50c52c80a 100644
--- a/packages/core/integration-tests/test/integration/scope-hoisting/es6/side-effects-false-duplicate/node_modules/bar/index.js
+++ b/packages/core/integration-tests/test/integration/scope-hoisting/es6/side-effects-false-duplicate/node_modules/bar/index.js
@@ -1,3 +1,3 @@
-export foo from './foo';
-export bar from './bar';
-export shared from './shared';
+export {default as foo} from './foo';
+export {default as bar} from './bar';
+export {default as shared} from './shared';
diff --git a/packages/core/integration-tests/test/integration/scope-hoisting/es6/side-effects-false/node_modules/bar/index.1.js b/packages/core/integration-tests/test/integration/scope-hoisting/es6/side-effects-false/node_modules/bar/index.1.js
index 20a0c6053..66309fdf9 100644
--- a/packages/core/integration-tests/test/integration/scope-hoisting/es6/side-effects-false/node_modules/bar/index.1.js
+++ b/packages/core/integration-tests/test/integration/scope-hoisting/es6/side-effects-false/node_modules/bar/index.1.js
@@ -1,1 +1,1 @@
-export foo from './foo';
+export {default as foo} from './foo';
diff --git a/packages/core/integration-tests/test/integration/scope-hoisting/es6/side-effects-false/node_modules/bar/index.js b/packages/core/integration-tests/test/integration/scope-hoisting/es6/side-effects-false/node_modules/bar/index.js
index f9e940c9f..cf61931c9 100644
--- a/packages/core/integration-tests/test/integration/scope-hoisting/es6/side-effects-false/node_modules/bar/index.js
+++ b/packages/core/integration-tests/test/integration/scope-hoisting/es6/side-effects-false/node_modules/bar/index.js
@@ -1,2 +1,2 @@
-export foo from './foo';
-export bar from './bar';
+export {default as foo} from './foo';
+export {default as bar} from './bar';
diff --git a/packages/core/integration-tests/test/integration/scope-hoisting/es6/side-effects/node_modules/bar/index.js b/packages/core/integration-tests/test/integration/scope-hoisting/es6/side-effects/node_modules/bar/index.js
index f9e940c9f..cf61931c9 100644
--- a/packages/core/integration-tests/test/integration/scope-hoisting/es6/side-effects/node_modules/bar/index.js
+++ b/packages/core/integration-tests/test/integration/scope-hoisting/es6/side-effects/node_modules/bar/index.js
@@ -1,2 +1,2 @@
-export foo from './foo';
-export bar from './bar';
+export {default as foo} from './foo';
+export {default as bar} from './bar';
diff --git a/packages/core/integration-tests/test/javascript.js b/packages/core/integration-tests/test/javascript.js
index da058c1f1..506ae0079 100644
--- a/packages/core/integration-tests/test/javascript.js
+++ b/packages/core/integration-tests/test/javascript.js
@@ -62,7 +62,12 @@ describe('javascript', function() {
     assertBundles(b, [
       {
         name: 'index.js',
-        assets: ['bundle-url.js', 'index.js', 'JSRuntime.js'],
+        assets: [
+          'bundle-url.js',
+          'index.js',
+          'JSRuntime.js',
+          'esmodule-helpers.js',
+        ],
       },
       {
         type: 'txt',
@@ -248,7 +253,13 @@ describe('javascript', function() {
     assertBundles(b, [
       {
         name: 'main.js',
-        assets: ['_empty.js', 'browser.js', 'index.js', 'main.js'],
+        assets: [
+          '_empty.js',
+          'browser.js',
+          'esmodule-helpers.js',
+          'index.js',
+          'main.js',
+        ],
       },
     ]);
 
@@ -268,7 +279,7 @@ describe('javascript', function() {
     assertBundles(b, [
       {
         name: 'main.js',
-        assets: ['main.js'],
+        assets: ['esmodule-helpers.js', 'main.js'],
       },
     ]);
 
@@ -495,7 +506,7 @@ describe('javascript', function() {
     assertBundles(b, [
       {
         name: 'index.js',
-        assets: ['index.js'],
+        assets: ['esmodule-helpers.js', 'index.js'],
       },
     ]);
   });
@@ -849,7 +860,7 @@ describe('javascript', function() {
         assets: ['worker-b.js'],
       },
       {
-        assets: ['lodash.js'],
+        assets: ['esmodule-helpers.js', 'lodash.js'],
       },
     ]);
 
@@ -887,7 +898,7 @@ describe('javascript', function() {
         assets: ['worker.js'],
       },
       {
-        assets: ['lodash.js'],
+        assets: ['lodash.js', 'esmodule-helpers.js'],
       },
     ]);
 
@@ -962,6 +973,7 @@ describe('javascript', function() {
           'index.js',
           'bundle-url.js',
           'cacheLoader.js',
+          'esmodule-helpers.js',
           'js-loader.js',
           'JSRuntime.js',
           'bundle-manifest.js',
@@ -1087,7 +1099,7 @@ describe('javascript', function() {
     );
     assertBundles(b, [
       {
-        assets: ['c.js', 'common.js'],
+        assets: ['c.js', 'common.js', 'esmodule-helpers.js'],
       },
       {
         name: 'b.js',
@@ -1109,6 +1121,7 @@ describe('javascript', function() {
           'bundle-url.js',
           'common.js',
           'cacheLoader.js',
+          'esmodule-helpers.js',
           'js-loader.js',
           'JSRuntime.js',
           'bundle-manifest.js',
@@ -1131,6 +1144,7 @@ describe('javascript', function() {
           'index.js',
           'bundle-url.js',
           'cacheLoader.js',
+          'esmodule-helpers.js',
           'js-loader.js',
           'JSRuntime.js',
           'JSRuntime.js',
@@ -1307,7 +1321,7 @@ describe('javascript', function() {
 
     assertBundles(b, [
       {
-        assets: ['a.js', 'b.js', 'c.js'],
+        assets: ['a.js', 'b.js', 'c.js', 'esmodule-helpers.js'],
       },
     ]);
 
@@ -1644,7 +1658,7 @@ describe('javascript', function() {
     assertBundles(b, [
       {
         name: 'browser.js',
-        assets: ['browser.js', 'browser-module.js'],
+        assets: ['browser.js', 'browser-module.js', 'esmodule-helpers.js'],
       },
     ]);
 
@@ -1805,7 +1819,7 @@ describe('javascript', function() {
     assertBundles(b, [
       {
         name: 'main-field.js',
-        assets: ['main-field.js', 'main.js'],
+        assets: ['main-field.js', 'main.js', 'esmodule-helpers.js'],
       },
     ]);
 
@@ -2339,6 +2353,7 @@ describe('javascript', function() {
           'JSRuntime.js',
           'bundle-url.js',
           'cacheLoader.js',
+          'esmodule-helpers.js',
           'js-loader.js',
           'bundle-manifest.js',
           'JSRuntime.js',
@@ -2374,11 +2389,11 @@ describe('javascript', function() {
     assertBundles(b, [
       {
         name: 'a.js',
-        assets: ['a.js', 'lodash.js'],
+        assets: ['a.js', 'esmodule-helpers.js', 'lodash.js'],
       },
       {
         name: 'b.js',
-        assets: ['b.js', 'lodash.js'],
+        assets: ['b.js', 'esmodule-helpers.js', 'lodash.js'],
       },
     ]);
   });
@@ -2439,7 +2454,11 @@ describe('javascript', function() {
     assertBundles(b, [
       {
         name: 'index.js',
-        assets: ['index.js', b.getBundles().find(b => b.isInline).id + '.js'],
+        assets: [
+          'index.js',
+          b.getBundles().find(b => b.isInline).id + '.js',
+          'esmodule-helpers.js',
+        ],
       },
       {
         name: 'index.css',
@@ -2659,6 +2678,7 @@ describe('javascript', function() {
           'get-dep-2.js',
           'dep.js',
           'JSRuntime.js',
+          'esmodule-helpers.js',
         ],
       },
     ]);
@@ -2684,6 +2704,7 @@ describe('javascript', function() {
           'JSRuntime.js',
           'relative-path.js',
           'same-ancestry.js',
+          'esmodule-helpers.js',
         ],
       },
       {
@@ -2713,6 +2734,7 @@ describe('javascript', function() {
           'get-dep-2.js',
           'dep.js',
           'JSRuntime.js',
+          'esmodule-helpers.js',
         ],
       },
       {
@@ -2726,6 +2748,7 @@ describe('javascript', function() {
           'JSRuntime.js',
           'JSRuntime.js',
           'relative-path.js',
+          'esmodule-helpers.js',
         ],
       },
     ]);
@@ -2778,6 +2801,7 @@ describe('javascript', function() {
           'JSRuntime.js',
           'JSRuntime.js',
           'relative-path.js',
+          'esmodule-helpers.js',
         ],
       },
     ]);
@@ -2804,9 +2828,10 @@ describe('javascript', function() {
           'JSRuntime.js',
           'JSRuntime.js',
           'relative-path.js',
+          'esmodule-helpers.js',
         ],
       },
-      {name: 'value.js', assets: ['value.js']},
+      {name: 'value.js', assets: ['value.js', 'esmodule-helpers.js']},
       {assets: ['async.js']},
     ]);
 
@@ -2831,6 +2856,7 @@ describe('javascript', function() {
           'JSRuntime.js',
           'JSRuntime.js',
           'relative-path.js',
+          'esmodule-helpers.js',
         ],
       },
       {assets: ['value.js']},
@@ -2902,6 +2928,7 @@ describe('javascript', function() {
           'JSRuntime.js',
           'JSRuntime.js',
           'relative-path.js',
+          'esmodule-helpers.js',
         ],
       },
       {
@@ -2917,7 +2944,7 @@ describe('javascript', function() {
           'relative-path.js',
         ],
       },
-      {assets: ['a.js', 'value.js']},
+      {assets: ['a.js', 'value.js', 'esmodule-helpers.js']},
       {assets: ['b.js']},
     ]);
 
@@ -2987,4 +3014,104 @@ describe('javascript', function() {
       },
     ]);
   });
+
+  it('should support duplicate re-exports without scope hoisting', async function() {
+    let b = await bundle(
+      path.join(__dirname, 'integration/js-duplicate-re-exports/index.js'),
+    );
+    let res = await run(b);
+    assert.equal(res.a, 'a');
+    assert.equal(res.b, 'b');
+    assert.equal(typeof res.c, 'function');
+  });
+
+  it('should exclude default from export all declaration', async function() {
+    let b = await bundle(
+      path.join(__dirname, 'integration/js-export-all/index.js'),
+    );
+    let res = await run(b);
+    assert.deepEqual(res, {a: 4});
+  });
+
+  it('should support import namespace declarations of other ES modules', async function() {
+    let b = await bundle(
+      path.join(__dirname, 'integration/js-import-namespace/a.js'),
+    );
+    let res = await run(b);
+    assert.deepEqual(res, {a: 4, default: 1});
+  });
+
+  it('should support import namespace declarations of class from CJS', async function() {
+    let b = await bundle(
+      path.join(__dirname, 'integration/js-import-namespace/b.js'),
+    );
+    let res = await run(b);
+    assert.equal(typeof res, 'function');
+  });
+
+  it('should support import namespace declarations of object from CJS', async function() {
+    let b = await bundle(
+      path.join(__dirname, 'integration/js-import-namespace/c.js'),
+    );
+    let res = await run(b);
+    assert.deepEqual(res, {foo: 2, default: 3});
+  });
+
+  it('should support export namespace declarations', async function() {
+    let b = await bundle(
+      path.join(__dirname, 'integration/js-export-namespace/index.js'),
+    );
+    let res = await run(b);
+    assert.deepEqual(res, {ns: {a: 4, default: 1}});
+  });
+
+  it('should support export default declarations', async function() {
+    let b = await bundle(
+      path.join(__dirname, 'integration/js-export-default/index.js'),
+    );
+    let res = await run(b);
+    assert.deepEqual(res, {other: 1});
+  });
+
+  it('should work with many different types of exports', async function() {
+    let b = await bundle(
+      path.join(__dirname, 'integration/js-export-many/index.js'),
+    );
+    let res = await run(b);
+    assert.deepEqual(res, {
+      foo: 'foo',
+      bar: 'bar',
+      default: 'baz',
+      boo: 'boo',
+      foobar: 'foobar',
+      type1: 'type1',
+      type2: 'type2',
+    });
+  });
+
+  it('should correctly export functions', async function() {
+    let b = await bundle(
+      path.join(__dirname, 'integration/js-export-functions/index.js'),
+    );
+    let res = await run(b);
+    assert.deepEqual(Object.keys(res), ['foo', 'bar']);
+    assert.equal(res.foo('test'), 'foo:test');
+    assert.equal(res.bar('test'), 'bar:test');
+  });
+
+  it('should handle exports of imports', async function() {
+    let b = await bundle(
+      path.join(__dirname, 'integration/js-export-import/index.js'),
+    );
+    let res = await run(b);
+    assert.deepEqual(res, {other: 2});
+  });
+
+  it('should generate a unique variable name for imports', async function() {
+    let b = await bundle(
+      path.join(__dirname, 'integration/js-import-shadow/index.js'),
+    );
+    let res = await run(b);
+    assert.equal(res.baz(), 'foo');
+  });
 });
diff --git a/packages/core/integration-tests/test/sourcemaps.js b/packages/core/integration-tests/test/sourcemaps.js
index ce44af1d0..383ad95b1 100644
--- a/packages/core/integration-tests/test/sourcemaps.js
+++ b/packages/core/integration-tests/test/sourcemaps.js
@@ -459,8 +459,11 @@ describe('sourcemaps', function() {
     sourceMap.addRawMappings(map);
 
     let mapData = sourceMap.getMap();
-    assert.equal(mapData.sources.length, 1);
-    assert.deepEqual(mapData.sources, ['./index.ts']);
+    assert.equal(mapData.sources.length, 2);
+    assert.deepEqual(mapData.sources, [
+      './index.ts',
+      '../../../../../../node_modules/@parcel/transformer-js/src/esmodule-helpers.js',
+    ]);
 
     let input = await inputFS.readFile(
       path.join(path.dirname(filename), map.sourceRoot, map.sources[0]),
@@ -498,8 +501,12 @@ describe('sourcemaps', function() {
     sourceMap.addRawMappings(map);
 
     let mapData = sourceMap.getMap();
-    assert.equal(mapData.sources.length, 2);
-    assert.deepEqual(mapData.sources, ['./index.ts', './local.ts']);
+    assert.equal(mapData.sources.length, 3);
+    assert.deepEqual(mapData.sources, [
+      './index.ts',
+      './local.ts',
+      '../../../../../../node_modules/@parcel/transformer-js/src/esmodule-helpers.js',
+    ]);
 
     let input = await inputFS.readFile(
       path.join(path.dirname(filename), map.sourceRoot, map.sources[0]),
@@ -950,7 +957,10 @@ describe('sourcemaps', function() {
 
     let map = mapData.map;
     assert.equal(map.file, 'index.js.map');
-    assert.deepEqual(map.sources, ['./index.js']);
+    assert.deepEqual(map.sources, [
+      './index.js',
+      '../../../../../../node_modules/@parcel/transformer-js/src/esmodule-helpers.js',
+    ]);
     assert.equal(map.sourcesContent[0], sourceContent);
   });
 
@@ -981,7 +991,10 @@ describe('sourcemaps', function() {
 
     let map = mapUrlData.map;
     assert.equal(map.file, 'index.js.map');
-    assert.deepEqual(map.sources, ['./index.js']);
+    assert.deepEqual(map.sources, [
+      './index.js',
+      '../../../../../../node_modules/@parcel/transformer-js/src/esmodule-helpers.js',
+    ]);
   });
 
   it('should respect --no-source-maps', async function() {
diff --git a/packages/core/integration-tests/test/svg.js b/packages/core/integration-tests/test/svg.js
index db3c055a7..9ce92673c 100644
--- a/packages/core/integration-tests/test/svg.js
+++ b/packages/core/integration-tests/test/svg.js
@@ -22,6 +22,6 @@ describe('svg', function() {
 
     let file = await outputFS.readFile(path.join(distDir, 'index.js'), 'utf-8');
     assert(file.includes('function SvgIcon'));
-    assert(file.includes('_react.default.createElement("svg"'));
+    assert(file.includes('_reactDefault.createElement("svg"'));
   });
 });
diff --git a/packages/core/integration-tests/test/ts-types.js b/packages/core/integration-tests/test/ts-types.js
index 9ea66928b..c8be63cb8 100644
--- a/packages/core/integration-tests/test/ts-types.js
+++ b/packages/core/integration-tests/test/ts-types.js
@@ -18,7 +18,7 @@ describe('typescript types', function() {
     assertBundles(b, [
       {
         type: 'js',
-        assets: ['index.ts'],
+        assets: ['esmodule-helpers.js', 'index.ts'],
       },
       {
         type: 'ts',
@@ -47,7 +47,7 @@ describe('typescript types', function() {
     assertBundles(b, [
       {
         type: 'js',
-        assets: ['index.ts', 'file.ts', 'namespace.ts'],
+        assets: ['index.ts', 'file.ts', 'namespace.ts', 'esmodule-helpers.js'],
       },
       {
         type: 'ts',
@@ -76,7 +76,13 @@ describe('typescript types', function() {
     assertBundles(b, [
       {
         type: 'js',
-        assets: ['index.ts', 'message.ts', 'other.ts', 'test.ts'],
+        assets: [
+          'esmodule-helpers.js',
+          'index.ts',
+          'message.ts',
+          'other.ts',
+          'test.ts',
+        ],
       },
       {
         type: 'ts',
@@ -105,7 +111,7 @@ describe('typescript types', function() {
     assertBundles(b, [
       {
         type: 'js',
-        assets: ['index.tsx', 'other.tsx'],
+        assets: ['index.tsx', 'other.tsx', 'esmodule-helpers.js'],
       },
       {
         type: 'ts',
diff --git a/packages/core/integration-tests/test/typescript-tsc.js b/packages/core/integration-tests/test/typescript-tsc.js
index 23932fce2..018e3dc81 100644
--- a/packages/core/integration-tests/test/typescript-tsc.js
+++ b/packages/core/integration-tests/test/typescript-tsc.js
@@ -45,7 +45,7 @@ describe('typescript tsc', function() {
     assertBundles(b, [
       {
         name: 'index.js',
-        assets: ['index.ts'],
+        assets: ['index.ts', 'esmodule-helpers.js'],
       },
       {
         name: 'index.d.ts',
diff --git a/packages/core/integration-tests/test/typescript.js b/packages/core/integration-tests/test/typescript.js
index 97de4bedd..e62e96d91 100644
--- a/packages/core/integration-tests/test/typescript.js
+++ b/packages/core/integration-tests/test/typescript.js
@@ -34,7 +34,7 @@ describe('typescript', function() {
       assertBundles(b, [
         {
           type: 'js',
-          assets: ['index.ts', 'Local.ts'],
+          assets: ['index.ts', 'Local.ts', 'esmodule-helpers.js'],
         },
       ]);
 
@@ -52,7 +52,7 @@ describe('typescript', function() {
       assertBundles(b, [
         {
           type: 'js',
-          assets: ['index.ts', 'Local.ts'],
+          assets: ['index.ts', 'Local.ts', 'esmodule-helpers.js'],
         },
       ]);
 
@@ -83,7 +83,7 @@ describe('typescript', function() {
       assertBundles(b, [
         {
           type: 'js',
-          assets: ['index.ts'],
+          assets: ['index.ts', 'esmodule-helpers.js'],
         },
       ]);
 
@@ -106,6 +106,7 @@ describe('typescript', function() {
             'JSRuntime.js',
             'bundle-url.js',
             'bundle-manifest.js',
+            'esmodule-helpers.js',
             'JSRuntime.js',
             'relative-path.js',
           ],
@@ -138,7 +139,7 @@ describe('typescript', function() {
       assertBundles(b, [
         {
           type: 'js',
-          assets: ['index.ts', 'Local.ts'],
+          assets: ['index.ts', 'Local.ts', 'esmodule-helpers.js'],
         },
       ]);
 
@@ -172,7 +173,7 @@ describe('typescript', function() {
       assertBundles(b, [
         {
           name: 'index.js',
-          assets: ['index.ts', 'commonjs-module.js'],
+          assets: ['esmodule-helpers.js', 'index.ts', 'commonjs-module.js'],
         },
       ]);