Skip to content

Commit 2175e1b

Browse files
authoredOct 12, 2021
Mark '*' as used when the reexport is only decided at runtime (#7049)
1 parent 4312b91 commit 2175e1b

File tree

32 files changed

+186
-5
lines changed

32 files changed

+186
-5
lines changed
 

‎packages/core/core/src/requests/AssetGraphRequest.js

+51-3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import invariant from 'assert';
2424
import nullthrows from 'nullthrows';
2525
import {PromiseQueue} from '@parcel/utils';
2626
import {hashString} from '@parcel/hash';
27+
import logger from '@parcel/logger';
2728
import ThrowableDiagnostic, {md} from '@parcel/diagnostic';
2829
import {BundleBehavior, Priority} from '../types';
2930
import AssetGraph from '../AssetGraph';
@@ -401,6 +402,26 @@ export class AssetGraphBuilder {
401402
}
402403
});
403404

405+
const logFallbackNamespaceInsertion = (
406+
assetNode,
407+
symbol,
408+
depNode1,
409+
depNode2,
410+
) => {
411+
if (this.options.logLevel === 'verbose') {
412+
logger.warn({
413+
message: `${fromProjectPathRelative(
414+
assetNode.value.filePath,
415+
)} reexports "${symbol}", which could be resolved either to the dependency "${
416+
depNode1.value.specifier
417+
}" or "${
418+
depNode2.value.specifier
419+
}" at runtime. Adding a namespace object to fall back on.`,
420+
origin: '@parcel/core',
421+
});
422+
}
423+
};
424+
404425
// Because namespace reexports introduce ambiguity, go up the graph from the leaves to the
405426
// root and remove requested symbols that aren't actually exported
406427
this.propagateSymbolsUp((assetNode, incomingDeps, outgoingDeps) => {
@@ -424,7 +445,9 @@ export class AssetGraphBuilder {
424445
}
425446
}
426447

427-
let reexportedSymbols = new Set<Symbol>();
448+
// the symbols that are reexport (not used in `asset`) -> the corresponding outgoingDep(s)
449+
// There could be multiple dependencies with non-statically analyzable exports
450+
let reexportedSymbols = new Map<Symbol, DependencyNode>();
428451
for (let outgoingDep of outgoingDeps) {
429452
let outgoingDepSymbols = outgoingDep.value.symbols;
430453
if (!outgoingDepSymbols) continue;
@@ -441,7 +464,20 @@ export class AssetGraphBuilder {
441464
}
442465

443466
if (outgoingDepSymbols.get('*')?.local === '*') {
444-
outgoingDep.usedSymbolsUp.forEach(s => reexportedSymbols.add(s));
467+
outgoingDep.usedSymbolsUp.forEach(s => {
468+
// If the symbol could come from multiple assets at runtime, assetNode's
469+
// namespace will be needed at runtime to perform the lookup on.
470+
if (reexportedSymbols.has(s) && !assetNode.usedSymbols.has('*')) {
471+
logFallbackNamespaceInsertion(
472+
assetNode,
473+
s,
474+
nullthrows(reexportedSymbols.get(s)),
475+
outgoingDep,
476+
);
477+
assetNode.usedSymbols.add('*');
478+
}
479+
reexportedSymbols.set(s, outgoingDep);
480+
});
445481
}
446482

447483
for (let s of outgoingDep.usedSymbolsUp) {
@@ -458,7 +494,19 @@ export class AssetGraphBuilder {
458494

459495
let reexported = assetSymbolsInverse?.get(local);
460496
if (reexported != null) {
461-
reexported.forEach(s => reexportedSymbols.add(s));
497+
reexported.forEach(s => {
498+
// see same code above
499+
if (reexportedSymbols.has(s) && !assetNode.usedSymbols.has('*')) {
500+
logFallbackNamespaceInsertion(
501+
assetNode,
502+
s,
503+
nullthrows(reexportedSymbols.get(s)),
504+
outgoingDep,
505+
);
506+
assetNode.usedSymbols.add('*');
507+
}
508+
reexportedSymbols.set(s, outgoingDep);
509+
});
462510
}
463511
}
464512
}

‎packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-all-fallback-1/empty.js

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import { mergeWith } from "./lib";
2+
output = mergeWith;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./other";
2+
export * from "./empty";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = 2;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as mergeWith } from "./mergeWith";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = { a: 1 };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = { b: 2 };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { b } from "./lib";
2+
3+
output = b;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./a";
2+
export * from "./b";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { FOO } from './re-exports';
2+
3+
sideEffectNoop(FOO)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import {doStuff} from './stuff';
2+
3+
output = doStuff();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
//@flow
2+
3+
export const FOO = 'FOO';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//@flow
2+
3+
import {FOO, BAR, type MyString} from "../re-exports";
4+
5+
const myFunc = (): MyString => FOO + BAR;
6+
7+
let res = myFunc();
8+
9+
export const fooBar = (): MyString => res;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"dependencies": {
3+
"flow-bin": "0.157.0"
4+
},
5+
"sideEffects": [
6+
"entry.js"
7+
]
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
//@flow
2+
3+
export * from './foo';
4+
export * from './rest';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
//@flow
2+
3+
export const BAR = 'BAR';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
//@flow
2+
3+
export type Hello = 1 | 2;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
//@flow
2+
3+
export * from './bar';
4+
export * from './myString';
5+
export * from './hello';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
//@flow
2+
3+
export type MyString = string;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//@flow
2+
3+
import {fooBar} from './other'
4+
import {FOO, BAR, type MyString} from './re-exports'
5+
import("./async");
6+
7+
const res = (): MyString => fooBar() + "!";
8+
// $FlowFixMe
9+
sideEffectNoop(res());
10+
11+
export const doStuff = res;

‎packages/core/integration-tests/test/integration/scope-hoisting/es6/re-export-all-fallback-3/yarn.lock

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = { a: 1 };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = { b: 2 };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = { c: 3 };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = { d: 4 };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import { b } from "./lib";
2+
import { d } from "./lib2";
3+
4+
output = `${b} ${d}`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./lib1";
2+
export * from "./lib2";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./a";
2+
export * from "./b";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./c";
2+
export * from "./d";

‎packages/core/integration-tests/test/scope-hoisting.js

+48-1
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,53 @@ describe('scope hoisting', function() {
261261
assert.equal(output, 6);
262262
});
263263

264+
it('supports re-exporting all when falling back to namespace at runtime 1', async function() {
265+
let b = await bundle(
266+
path.join(
267+
__dirname,
268+
'/integration/scope-hoisting/es6/re-export-all-fallback-1/index.js',
269+
),
270+
);
271+
272+
let output = await run(b);
273+
assert.strictEqual(output, 2);
274+
});
275+
276+
it('supports re-exporting all when falling back to namespace at runtime 2', async function() {
277+
let b = await bundle(
278+
path.join(
279+
__dirname,
280+
'/integration/scope-hoisting/es6/re-export-all-fallback-2/index.js',
281+
),
282+
);
283+
284+
let output = await run(b);
285+
assert.strictEqual(output, 2);
286+
});
287+
288+
it('supports re-exporting all when falling back to namespace at runtime 3', async function() {
289+
let b = await bundle(
290+
path.join(
291+
__dirname,
292+
'integration/scope-hoisting/es6/re-export-all-fallback-3/entry.js',
293+
),
294+
);
295+
let output = await run(b);
296+
assert.strictEqual(output, 'FOOBAR!');
297+
});
298+
299+
it('supports nested re-exporting all when falling back to namespace at runtime', async function() {
300+
let b = await bundle(
301+
path.join(
302+
__dirname,
303+
'/integration/scope-hoisting/es6/re-export-all-fallback-nested/index.js',
304+
),
305+
);
306+
307+
let output = await run(b);
308+
assert.strictEqual(output, '2 4');
309+
});
310+
264311
it('supports re-exporting all exports from an external module', async function() {
265312
let b = await bundle(
266313
path.join(
@@ -3293,7 +3340,7 @@ describe('scope hoisting', function() {
32933340
},
32943341
});
32953342

3296-
assert.deepEqual(calls, ['esm', 'commonjs']);
3343+
assert.deepEqual(calls, ['esm', 'commonjs', 'index']);
32973344
assert.deepEqual(output, 'Message 1');
32983345
});
32993346

‎packages/packagers/js/src/ScopeHoistingPackager.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -978,7 +978,10 @@ ${code}
978978
if (
979979
isWrapped ||
980980
resolved.meta.staticExports === false ||
981-
nullthrows(this.bundleGraph.getUsedSymbols(resolved)).has('*')
981+
nullthrows(this.bundleGraph.getUsedSymbols(resolved)).has('*') ||
982+
// an empty asset
983+
(!resolved.meta.hasCJSExports &&
984+
resolved.symbols.hasExportSymbol('*'))
982985
) {
983986
let obj = this.getSymbolResolution(asset, resolved, '*', dep);
984987
append += `$parcel$exportWildcard($${assetId}$exports, ${obj});\n`;
@@ -1007,6 +1010,7 @@ ${code}
10071010
prepend += `$parcel$export($${assetId}$exports, ${JSON.stringify(
10081011
symbol,
10091012
)}, ${get}${set});\n`;
1013+
this.usedHelpers.add('$parcel$export');
10101014
prependLineCount++;
10111015
}
10121016
}

0 commit comments

Comments
 (0)
Please sign in to comment.