Add multiple entrypoint support to the dependency traversal logic

Reviewed By: davidaurelio

Differential Revision: D7180692

fbshipit-source-id: 65b1992abbf10e753b6895623824ff90d7cadc24
This commit is contained in:
Rafael Oleza 2018-03-19 10:04:43 -07:00 committed by Facebook Github Bot
parent 5e9cf9ec4b
commit 45123d822b
6 changed files with 110 additions and 53 deletions

View File

@ -12,7 +12,7 @@
const {
initialTraverseDependencies,
reorderDependencies,
reorderGraph,
traverseDependencies,
} = require('./traverseDependencies');
const {EventEmitter} = require('events');
@ -60,13 +60,15 @@ class DeltaCalculator extends EventEmitter {
this._options = options;
this._dependencyGraph = dependencyGraph;
const entryFilePath = this._dependencyGraph.getAbsolutePath(
this._options.entryFile,
);
// The traverse dependencies logic supports multiple entry points, but
// currently metro only supports to pass a single entry point when bundling.
const entryPoints = [
this._dependencyGraph.getAbsolutePath(this._options.entryFile),
];
this._graph = {
dependencies: new Map(),
entryFile: entryFilePath,
entryPoints,
};
this._dependencyGraph
@ -87,7 +89,7 @@ class DeltaCalculator extends EventEmitter {
// Clean up all the cache data structures to deallocate memory.
this._graph = {
dependencies: new Map(),
entryFile: this._options.entryFile,
entryPoints: [this._options.entryFile],
};
this._modifiedFiles = new Set();
this._deletedFiles = new Set();
@ -148,10 +150,7 @@ class DeltaCalculator extends EventEmitter {
// Return all the modules if the client requested a reset delta.
if (reset) {
this._graph.dependencies = reorderDependencies(
this._graph.dependencies.get(this._graph.entryFile),
this._graph.dependencies,
);
reorderGraph(this._graph);
return {
modified: this._graph.dependencies,
@ -202,7 +201,7 @@ class DeltaCalculator extends EventEmitter {
const {added} = await initialTraverseDependencies(
{
dependencies: new Map(),
entryFile: path,
entryPoints: [path],
},
this._dependencyGraph,
transformOptionsForBlacklist,

View File

@ -15,7 +15,6 @@ jest.mock('../traverseDependencies');
const {
initialTraverseDependencies,
reorderDependencies,
traverseDependencies,
} = require('../traverseDependencies');
@ -198,8 +197,6 @@ describe('DeltaCalculator', () => {
});
it('should return a full delta when passing reset=true', async () => {
reorderDependencies.mockImplementation((_, edges) => edges);
await deltaCalculator.getDelta({reset: false});
const result = await deltaCalculator.getDelta({reset: true});

View File

@ -59,6 +59,8 @@ Object {
"path": "/baz",
},
},
"entryFile": "/bundle",
"entryPoints": Array [
"/bundle",
],
}
`;

View File

@ -62,7 +62,7 @@ describe('traverseDependencies', function() {
const graph = {
dependencies: new Map(),
entryFile: entryPath,
entryPoints: [entryPath],
};
const {added} = await traverseDependencies.initialTraverseDependencies(
@ -5073,7 +5073,7 @@ describe('traverseDependencies', function() {
return traverseDependencies.initialTraverseDependencies(
{
dependencies: new Map(),
entryFile: '/root/index.js',
entryPoints: ['/root/index.js'],
},
dependencyGraph,
emptyTransformOptions,

View File

@ -12,6 +12,7 @@
const {
initialTraverseDependencies,
reorderGraph,
traverseDependencies,
} = require('../traverseDependencies');
@ -172,7 +173,7 @@ beforeEach(async () => {
graph = {
dependencies: new Map(),
entryFile: '/bundle',
entryPoints: ['/bundle'],
};
});
@ -377,6 +378,33 @@ describe('edge cases', () => {
]);
});
it('should traverse a graph from multiple entry points', async () => {
entryModule = Actions.createFile('/bundle-2');
Actions.addDependency('/bundle-2', '/bundle-2-foo');
Actions.addDependency('/bundle-2', '/bundle-2-bar');
Actions.addDependency('/bundle-2', '/bar');
files = new Set();
graph = {
dependencies: new Map(),
entryPoints: ['/bundle', '/bundle-2'],
};
await initialTraverseDependencies(graph, dependencyGraph, {});
expect([...graph.dependencies.keys()]).toEqual([
'/bundle',
'/foo',
'/bar',
'/baz',
'/bundle-2',
'/bundle-2-foo',
'/bundle-2-bar',
]);
});
it('should traverse the dependency tree in a deterministic order', async () => {
// Mocks the shallow dependency call, always resolving the module in
// `slowPath` after the module in `fastPath`.
@ -416,7 +444,7 @@ describe('edge cases', () => {
async function assertOrder() {
graph = {
dependencies: new Map(),
entryFile: '/bundle',
entryPoints: ['/bundle'],
};
expect(
@ -483,3 +511,33 @@ describe('edge cases', () => {
expect(moduleFoo.read).toHaveBeenCalledWith({inlineRequires: true});
});
});
describe('reorderGraph', () => {
it('should reorder any unordered graph in DFS order', async () => {
const graph = {
dependencies: new Map([
['/2', {dependencies: new Map(), path: '/2'}],
[
'/0',
{path: '/0', dependencies: new Map([['/1', '/1'], ['/2', '/2']])},
],
['/1', {dependencies: new Map([['/2', '/2']]), path: '/1'}],
['/3', {dependencies: new Map(), path: '/3'}],
['/b', {dependencies: new Map([['/3', '/3']]), path: '/b'}],
['/a', {dependencies: new Map([['/0', '/0']]), path: '/a'}],
]),
entryPoints: ['/a', '/b'],
};
reorderGraph(graph);
expect([...graph.dependencies.keys()]).toEqual([
'/a',
'/0',
'/1',
'/2',
'/b',
'/3',
]);
});
});

View File

@ -35,7 +35,7 @@ export type DependencyEdges = Map<string, DependencyEdge>;
export type Graph = {|
dependencies: DependencyEdges,
entryFile: string,
entryPoints: $ReadOnlyArray<string>,
|};
type Result = {added: Map<string, DependencyEdge>, deleted: Set<string>};
@ -67,7 +67,7 @@ type Delta = {
* since the last traversal.
*/
async function traverseDependencies(
paths: Array<string>,
paths: $ReadOnlyArray<string>,
dependencyGraph: DependencyGraph,
transformOptions: JSTransformerOptions,
graph: Graph,
@ -139,29 +139,23 @@ async function initialTraverseDependencies(
transformOptions: JSTransformerOptions,
onProgress?: (numProcessed: number, total: number) => mixed = () => {},
): Promise<Result> {
const edge = createEdge(
dependencyGraph.getModuleForPath(graph.entryFile),
graph,
graph.entryPoints.forEach(entryPoint =>
createEdge(dependencyGraph.getModuleForPath(entryPoint), graph),
);
const delta = {
added: new Map([[edge.path, edge]]),
modified: new Map(),
deleted: new Set(),
};
await traverseDependenciesForSingleFile(
edge,
await traverseDependencies(
graph.entryPoints,
dependencyGraph,
transformOptions,
graph,
delta,
onProgress,
);
reorderGraph(graph);
return {
added: reorderDependencies(edge, delta.added),
deleted: delta.deleted,
added: graph.dependencies,
deleted: new Set(),
};
}
@ -391,30 +385,37 @@ function resolveDependencies(
/**
* Retraverse the dependency graph in DFS order to reorder the modules and
* guarantee the same order between runs.
* guarantee the same order between runs. This method mutates the passed graph.
*/
function reorderGraph(graph: Graph) {
const parent = {
dependencies: new Map(graph.entryPoints.map(e => [e, e])),
};
const dependencies = reorderDependencies(parent, graph.dependencies);
graph.dependencies = dependencies;
}
function reorderDependencies(
edge: ?DependencyEdge,
edge: {|dependencies: Map<string, string>|} | DependencyEdge,
dependencies: Map<string, DependencyEdge>,
orderedDependencies?: Map<string, DependencyEdge> = new Map(),
): Map<string, DependencyEdge> {
if (
!edge ||
!dependencies.has(edge.path) ||
orderedDependencies.has(edge.path)
) {
return orderedDependencies;
if (edge.path) {
if (orderedDependencies.has(edge.path)) {
return orderedDependencies;
}
orderedDependencies.set(edge.path, edge);
}
orderedDependencies.set(edge.path, edge);
edge.dependencies.forEach(path =>
reorderDependencies(
dependencies.get(path),
dependencies,
orderedDependencies,
),
);
edge.dependencies.forEach(path => {
const dep = dependencies.get(path);
if (dep) {
reorderDependencies(dep, dependencies, orderedDependencies);
}
});
return orderedDependencies;
}
@ -422,5 +423,5 @@ function reorderDependencies(
module.exports = {
initialTraverseDependencies,
traverseDependencies,
reorderDependencies,
reorderGraph,
};