Skip to content

Commit

Permalink
[Symbol Propagation] Non-deterministic bundle hashes (#8212)
Browse files Browse the repository at this point in the history
  • Loading branch information
gorakong committed Jul 27, 2022
1 parent 411d4c2 commit ad5b777
Show file tree
Hide file tree
Showing 14 changed files with 151 additions and 2 deletions.
11 changes: 11 additions & 0 deletions packages/core/core/src/requests/AssetGraphRequest.js
Expand Up @@ -260,6 +260,10 @@ export class AssetGraphBuilder {
}

propagateSymbols() {
// Keep track of dependencies that have changes to their used symbols,
// so we can sort them after propagation.
let changedDeps = new Set<DependencyNode>();

// Propagate the requested symbols down from the root to the leaves
this.propagateSymbolsDown((assetNode, incomingDeps, outgoingDeps) => {
if (!assetNode.value.symbols) return;
Expand Down Expand Up @@ -568,6 +572,7 @@ export class AssetGraphBuilder {
}

if (!equalSet(incomingDepUsedSymbolsUpOld, incomingDep.usedSymbolsUp)) {
changedDeps.add(incomingDep);
incomingDep.usedSymbolsUpDirtyUp = true;
}

Expand Down Expand Up @@ -595,6 +600,12 @@ export class AssetGraphBuilder {
}
return errors;
});
// Sort usedSymbolsUp so they are a consistent order across builds.
// This ensures a consistent ordering of these symbols when packaging.
// See https://github.com/parcel-bundler/parcel/pull/8212
for (let dep of changedDeps) {
dep.usedSymbolsUp = new Set([...dep.usedSymbolsUp].sort());
}
}

propagateSymbolsDown(
Expand Down
@@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<script type="module" src="./index.js"></script>
</body>
</html>
@@ -0,0 +1,7 @@
import {foo,bar} from './library';

function other() {
return foo+bar;
}

export {other};
@@ -0,0 +1,7 @@
import {baz, bag} from '../utils';

function bar() {
return baz+bag;
}

export {bar};
@@ -0,0 +1,7 @@
import {bag} from '../utils';

function foo() {
return bag;
}

export {foo};
@@ -0,0 +1,2 @@
export {foo} from './foo';
export {bar} from './bar';
@@ -0,0 +1,3 @@
{
"sideEffects": false
}
@@ -0,0 +1,7 @@
export function bag() {
return 'bag';
};

export function baz() {
return bag() + 'baz';
}
@@ -0,0 +1,2 @@
export * from './bag';
export * from './empty';
@@ -0,0 +1,3 @@
{
"sideEffects": false
}
12 changes: 10 additions & 2 deletions packages/core/integration-tests/test/react-refresh.js
Expand Up @@ -37,10 +37,14 @@ if (MessageChannel) {

let b,
root,
randoms = {};
randoms,
subscription,
window = {};

beforeEach(async () => {
({b, root, randoms} = await setup(path.join(testDir, 'index.html')));
({b, root, randoms, subscription, window} = await setup(
path.join(testDir, 'index.html'),
));
});

it('retains state in functional components', async function () {
Expand All @@ -62,6 +66,10 @@ if (MessageChannel) {
assert.equal(randoms.fooNum, fooNum);
assert.equal(fooText, 'OtherFunctional');
});

afterEach(async () => {
await cleanup({subscription, window});
});
});

describe('synchronous', () => {
Expand Down
86 changes: 86 additions & 0 deletions packages/core/integration-tests/test/scope-hoisting.js
Expand Up @@ -2,6 +2,7 @@ import assert from 'assert';
import path from 'path';
import nullthrows from 'nullthrows';
import {normalizePath} from '@parcel/utils';
import {createWorkerFarm} from '@parcel/core';
import {md} from '@parcel/diagnostic';
import {
assertBundles,
Expand Down Expand Up @@ -5532,4 +5533,89 @@ describe('scope hoisting', function () {
let output = await run(b);
assert.strictEqual(output, 'foo');
});

it('produce the same bundle hash regardless of transformation order', async function () {
let testDir = path.join(
__dirname,
'integration/scope-hoisting/es6/non-deterministic-bundle-hashes',
);

const waitHandler = (fileToDelay, fileToWaitFor) => {
const waitMap = new Map();

function wait(filePath) {
if (waitMap.has(filePath)) {
return Promise.resolve();
}
return new Promise(resolve => {
waitMap.set(filePath, resolve);
});
}
// a set of filepaths that have been read
function seen(filePath) {
// check map of things we're waiting for to resolved promises
let promisesToResolve = waitMap.get(filePath);
if (promisesToResolve) {
// if we find any, we call it
promisesToResolve();
}
waitMap.set(filePath, null);
}

return {
get(target, prop) {
let original = Reflect.get(...arguments);
if (prop === 'readFile') {
return async function (...args) {
if (args[0].includes(fileToDelay)) {
await wait(fileToWaitFor);
}
let result = await original.apply(this, args);
seen(path.basename(args[0]));
return result;
};
}
return original;
},
};
};

let workerFarm = createWorkerFarm({
maxConcurrentWorkers: 0,
});

let slowFooFS = new Proxy(overlayFS, waitHandler('foo.js', 'bar.js'));

try {
let b = await bundle(path.join(testDir, 'index.html'), {
inputFS: slowFooFS,
outputFS: slowFooFS,
shouldDisableCache: true,
workerFarm,
});

let bundleHashDelayFoo = b
.getBundles()
.find(b => b.filePath.endsWith('.js') && b.filePath.includes('index'))
.filePath.split('.')[1];

let slowBarFS = new Proxy(overlayFS, waitHandler('bar.js', 'foo.js'));

let b2 = await bundle(path.join(testDir, 'index.html'), {
inputFS: slowBarFS,
outputFS: slowBarFS,
shouldDisableCache: true,
workerFarm,
});

let bundleHashDelayBar = b2
.getBundles()
.find(b => b.filePath.endsWith('.js') && b.filePath.includes('index'))
.filePath.split('.')[1];

assert.strictEqual(bundleHashDelayFoo, bundleHashDelayBar);
} finally {
await workerFarm.end();
}
});
});

0 comments on commit ad5b777

Please sign in to comment.