Skip to content

Commit

Permalink
Fix overriding single export of a export * (#8653)
Browse files Browse the repository at this point in the history
  • Loading branch information
mischnic committed Nov 27, 2022
1 parent a07e479 commit 60b8ebc
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 44 deletions.
@@ -0,0 +1,3 @@
import { foo, c } from "./b.js";

export default foo() + c;
@@ -0,0 +1,8 @@
import { foo as old } from "./c";
export * from "./c";

function foo() {
return "fooB" + old();
}

export { foo };
@@ -0,0 +1,5 @@
export function foo() {
return "fooC";
}

export const c = "C";
@@ -0,0 +1,3 @@
if (Date.now() > 0) {
output = require("./a.js").default;
}
12 changes: 12 additions & 0 deletions packages/core/integration-tests/test/scope-hoisting.js
Expand Up @@ -397,6 +397,18 @@ describe('scope hoisting', function () {
assert.equal(output, 15);
});

it('supports re-exporting all exports and overriding individual exports', async function () {
let b = await bundle(
path.join(
__dirname,
'/integration/scope-hoisting/es6/re-export-all-override/index.js',
),
);

let output = await run(b);
assert.strictEqual(output, 'fooBfooCC');
});

it('can import from a different bundle via a re-export (1)', async function () {
let b = await bundle(
path.join(
Expand Down
90 changes: 46 additions & 44 deletions packages/packagers/js/src/ScopeHoistingPackager.js
Expand Up @@ -996,50 +996,9 @@ ${code}
this.usedHelpers.add('$parcel$defineInteropFlag');
}

// Find the used exports of this module. This is based on the used symbols of
// incoming dependencies rather than the asset's own used exports so that we include
// re-exported symbols rather than only symbols declared in this asset.
let incomingDeps = this.bundleGraph.getIncomingDependencies(asset);
let usedExports = [...asset.symbols.exportSymbols()].filter(symbol => {
if (symbol === '*') {
return false;
}

// If we need default interop, then all symbols are needed because the `default`
// symbol really maps to the whole namespace.
if (defaultInterop) {
return true;
}

let unused = incomingDeps.every(d => {
let symbols = nullthrows(this.bundleGraph.getUsedSymbols(d));
return !symbols.has(symbol) && !symbols.has('*');
});
return !unused;
});

if (usedExports.length > 0) {
// Insert $parcel$export calls for each of the used exports. This creates a getter/setter
// for the symbol so that when the value changes the object property also changes. This is
// required to simulate ESM live bindings. It's easier to do it this way rather than inserting
// additional assignments after each mutation of the original binding.
prepend += `\n${usedExports
.map(exp => {
let resolved = this.getSymbolResolution(asset, asset, exp);
let get = this.buildFunctionExpression([], resolved);
let set = asset.meta.hasCJSExports
? ', ' + this.buildFunctionExpression(['v'], `${resolved} = v`)
: '';
return `$parcel$export($${assetId}$exports, ${JSON.stringify(
exp,
)}, ${get}${set});`;
})
.join('\n')}\n`;
this.usedHelpers.add('$parcel$export');
prependLineCount += 1 + usedExports.length;
}

// Find wildcard re-export dependencies, and make sure their exports are also included in ours.
// Find wildcard re-export dependencies, and make sure their exports are also included in
// ours. Importantly, add them before the asset's own exports so that wildcard exports get
// correctly overwritten by own exports of the same name.
for (let dep of deps) {
let resolved = this.bundleGraph.getResolvedAsset(dep, this.bundle);
if (dep.isOptional || this.bundleGraph.isDependencySkipped(dep)) {
Expand Down Expand Up @@ -1105,6 +1064,49 @@ ${code}
}
}
}

// Find the used exports of this module. This is based on the used symbols of
// incoming dependencies rather than the asset's own used exports so that we include
// re-exported symbols rather than only symbols declared in this asset.
let incomingDeps = this.bundleGraph.getIncomingDependencies(asset);
let usedExports = [...asset.symbols.exportSymbols()].filter(symbol => {
if (symbol === '*') {
return false;
}

// If we need default interop, then all symbols are needed because the `default`
// symbol really maps to the whole namespace.
if (defaultInterop) {
return true;
}

let unused = incomingDeps.every(d => {
let symbols = nullthrows(this.bundleGraph.getUsedSymbols(d));
return !symbols.has(symbol) && !symbols.has('*');
});
return !unused;
});

if (usedExports.length > 0) {
// Insert $parcel$export calls for each of the used exports. This creates a getter/setter
// for the symbol so that when the value changes the object property also changes. This is
// required to simulate ESM live bindings. It's easier to do it this way rather than inserting
// additional assignments after each mutation of the original binding.
prepend += `\n${usedExports
.map(exp => {
let resolved = this.getSymbolResolution(asset, asset, exp);
let get = this.buildFunctionExpression([], resolved);
let set = asset.meta.hasCJSExports
? ', ' + this.buildFunctionExpression(['v'], `${resolved} = v`)
: '';
return `$parcel$export($${assetId}$exports, ${JSON.stringify(
exp,
)}, ${get}${set});`;
})
.join('\n')}\n`;
this.usedHelpers.add('$parcel$export');
prependLineCount += 1 + usedExports.length;
}
}

return [prepend, prependLineCount, append];
Expand Down

0 comments on commit 60b8ebc

Please sign in to comment.