From 5177471397514e42b4ed23fac1ad2800cf5277d3 Mon Sep 17 00:00:00 2001 From: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> Date: Tue, 22 Nov 2022 10:59:53 +0100 Subject: [PATCH 1/2] Add test --- .../scope-hoisting/es6/re-export-all-override/a.js | 3 +++ .../scope-hoisting/es6/re-export-all-override/b.js | 8 ++++++++ .../scope-hoisting/es6/re-export-all-override/c.js | 5 +++++ .../es6/re-export-all-override/index.js | 3 +++ .../core/integration-tests/test/scope-hoisting.js | 12 ++++++++++++ 5 files changed, 31 insertions(+) create mode 100644 packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-all-override/a.js create mode 100644 packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-all-override/b.js create mode 100644 packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-all-override/c.js create mode 100644 packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-all-override/index.js diff --git a/packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-all-override/a.js b/packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-all-override/a.js new file mode 100644 index 00000000000..7973586a0f6 --- /dev/null +++ b/packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-all-override/a.js @@ -0,0 +1,3 @@ +import { foo, c } from "./b.js"; + +export default foo() + c; diff --git a/packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-all-override/b.js b/packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-all-override/b.js new file mode 100644 index 00000000000..48594e8d61d --- /dev/null +++ b/packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-all-override/b.js @@ -0,0 +1,8 @@ +import { foo as old } from "./c"; +export * from "./c"; + +function foo() { + return "fooB" + old(); +} + +export { foo }; diff --git a/packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-all-override/c.js b/packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-all-override/c.js new file mode 100644 index 00000000000..9f3df1dbd3f --- /dev/null +++ b/packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-all-override/c.js @@ -0,0 +1,5 @@ +export function foo() { + return "fooC"; +} + +export const c = "C"; diff --git a/packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-all-override/index.js b/packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-all-override/index.js new file mode 100644 index 00000000000..63e96797b22 --- /dev/null +++ b/packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-all-override/index.js @@ -0,0 +1,3 @@ +if (Date.now() > 0) { + output = require("./a.js").default; +} diff --git a/packages/core/integration-tests/test/scope-hoisting.js b/packages/core/integration-tests/test/scope-hoisting.js index ec15fdaaeae..985d5b672c7 100644 --- a/packages/core/integration-tests/test/scope-hoisting.js +++ b/packages/core/integration-tests/test/scope-hoisting.js @@ -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( From c683775cbdf9f70d58efdc8b4f97f7d8c166051e Mon Sep 17 00:00:00 2001 From: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> Date: Sun, 27 Nov 2022 18:06:19 +0100 Subject: [PATCH 2/2] Fix? --- .../packagers/js/src/ScopeHoistingPackager.js | 90 ++++++++++--------- 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/packages/packagers/js/src/ScopeHoistingPackager.js b/packages/packagers/js/src/ScopeHoistingPackager.js index 9e0d1755b39..f145a70a335 100644 --- a/packages/packagers/js/src/ScopeHoistingPackager.js +++ b/packages/packagers/js/src/ScopeHoistingPackager.js @@ -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)) { @@ -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];