Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix overriding single export of a export * #8653

Merged
merged 2 commits into from Nov 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -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