Skip to content

Commit

Permalink
Avoid non-identifier export names in non-entry library bundles (#9587)
Browse files Browse the repository at this point in the history
  • Loading branch information
devongovett committed Mar 18, 2024
1 parent 8cfc9c7 commit 15b78e1
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 0 deletions.
51 changes: 51 additions & 0 deletions packages/core/integration-tests/test/library-bundler.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import assert from 'assert';
import path from 'path';
import {
bundle,
run,
runBundle,
overlayFS,
outputFS,
Expand Down Expand Up @@ -367,4 +368,54 @@ describe('library bundler', function () {
assert.equal(cjs.foo(), 'foo');
assert.equal(cjs.bar(), 2);
});

it('should export CJS namespaces as default', async function () {
await fsFixture(overlayFS, dir)`
yarn.lock:
.parcelrc:
{
"extends": "@parcel/config-default",
"bundler": "@parcel/bundler-library"
}
package.json:
{
"module": "dist/module.js",
"engines": { "node": "*" }
}
index.js:
import ns from './foo.js';
export function test() {
return ns['foo-bar'];
}
foo.js:
exports['foo-bar'] = 'foo';
`;

let b = await bundle(path.join(dir, '/index.js'), {
inputFS: overlayFS,
mode: 'production',
});

assertBundles(b, [
{
assets: ['index.js'],
},
{
type: 'js',
assets: ['foo.js'],
},
]);

let res = await run(b);
assert.equal(res.test(), 'foo');

// foo.js should only export default, to avoid non-identifier symbols.
let foo: any = await runBundle(b, b.getBundles()[1]);
assert.deepEqual(Object.keys(foo), ['default']);
assert.deepEqual(foo.default, {'foo-bar': 'foo'});
});
});
9 changes: 9 additions & 0 deletions packages/packagers/js/src/ScopeHoistingPackager.js
Original file line number Diff line number Diff line change
Expand Up @@ -354,13 +354,22 @@ export class ScopeHoistingPackager {
// TODO: handle ESM exports of wrapped entry assets...
let entry = this.bundle.getMainEntry();
if (entry && !this.wrappedAssets.has(entry.id)) {
let hasNamespace = entry.symbols.hasExportSymbol('*');

for (let {
asset,
exportAs,
symbol,
exportSymbol,
} of this.bundleGraph.getExportedSymbols(entry)) {
if (typeof symbol === 'string') {
// If the module has a namespace (e.g. commonjs), and this is not an entry, only export the namespace
// as default, without individual exports. This mirrors the importing logic in addExternal, avoiding
// extra unused exports and potential for non-identifier export names.
if (hasNamespace && this.isAsyncBundle && exportAs !== '*') {
continue;
}

let symbols = this.exportedSymbols.get(
symbol === '*' ? nullthrows(entry.symbols.get('*')?.local) : symbol,
)?.exportAs;
Expand Down

0 comments on commit 15b78e1

Please sign in to comment.