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

View File

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

View File

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

View File

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

View File

@ -12,6 +12,7 @@
const { const {
initialTraverseDependencies, initialTraverseDependencies,
reorderGraph,
traverseDependencies, traverseDependencies,
} = require('../traverseDependencies'); } = require('../traverseDependencies');
@ -172,7 +173,7 @@ beforeEach(async () => {
graph = { graph = {
dependencies: new Map(), 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 () => { it('should traverse the dependency tree in a deterministic order', async () => {
// Mocks the shallow dependency call, always resolving the module in // Mocks the shallow dependency call, always resolving the module in
// `slowPath` after the module in `fastPath`. // `slowPath` after the module in `fastPath`.
@ -416,7 +444,7 @@ describe('edge cases', () => {
async function assertOrder() { async function assertOrder() {
graph = { graph = {
dependencies: new Map(), dependencies: new Map(),
entryFile: '/bundle', entryPoints: ['/bundle'],
}; };
expect( expect(
@ -483,3 +511,33 @@ describe('edge cases', () => {
expect(moduleFoo.read).toHaveBeenCalledWith({inlineRequires: true}); 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 = {| export type Graph = {|
dependencies: DependencyEdges, dependencies: DependencyEdges,
entryFile: string, entryPoints: $ReadOnlyArray<string>,
|}; |};
type Result = {added: Map<string, DependencyEdge>, deleted: Set<string>}; type Result = {added: Map<string, DependencyEdge>, deleted: Set<string>};
@ -67,7 +67,7 @@ type Delta = {
* since the last traversal. * since the last traversal.
*/ */
async function traverseDependencies( async function traverseDependencies(
paths: Array<string>, paths: $ReadOnlyArray<string>,
dependencyGraph: DependencyGraph, dependencyGraph: DependencyGraph,
transformOptions: JSTransformerOptions, transformOptions: JSTransformerOptions,
graph: Graph, graph: Graph,
@ -139,29 +139,23 @@ async function initialTraverseDependencies(
transformOptions: JSTransformerOptions, transformOptions: JSTransformerOptions,
onProgress?: (numProcessed: number, total: number) => mixed = () => {}, onProgress?: (numProcessed: number, total: number) => mixed = () => {},
): Promise<Result> { ): Promise<Result> {
const edge = createEdge( graph.entryPoints.forEach(entryPoint =>
dependencyGraph.getModuleForPath(graph.entryFile), createEdge(dependencyGraph.getModuleForPath(entryPoint), graph),
graph,
); );
const delta = { await traverseDependencies(
added: new Map([[edge.path, edge]]), graph.entryPoints,
modified: new Map(),
deleted: new Set(),
};
await traverseDependenciesForSingleFile(
edge,
dependencyGraph, dependencyGraph,
transformOptions, transformOptions,
graph, graph,
delta,
onProgress, onProgress,
); );
reorderGraph(graph);
return { return {
added: reorderDependencies(edge, delta.added), added: graph.dependencies,
deleted: delta.deleted, deleted: new Set(),
}; };
} }
@ -391,30 +385,37 @@ function resolveDependencies(
/** /**
* Retraverse the dependency graph in DFS order to reorder the modules and * 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( function reorderDependencies(
edge: ?DependencyEdge, edge: {|dependencies: Map<string, string>|} | DependencyEdge,
dependencies: Map<string, DependencyEdge>, dependencies: Map<string, DependencyEdge>,
orderedDependencies?: Map<string, DependencyEdge> = new Map(), orderedDependencies?: Map<string, DependencyEdge> = new Map(),
): Map<string, DependencyEdge> { ): Map<string, DependencyEdge> {
if ( if (edge.path) {
!edge || if (orderedDependencies.has(edge.path)) {
!dependencies.has(edge.path) ||
orderedDependencies.has(edge.path)
) {
return orderedDependencies; return orderedDependencies;
} }
orderedDependencies.set(edge.path, edge); orderedDependencies.set(edge.path, edge);
}
edge.dependencies.forEach(path => edge.dependencies.forEach(path => {
reorderDependencies( const dep = dependencies.get(path);
dependencies.get(path), if (dep) {
dependencies, reorderDependencies(dep, dependencies, orderedDependencies);
orderedDependencies, }
), });
);
return orderedDependencies; return orderedDependencies;
} }
@ -422,5 +423,5 @@ function reorderDependencies(
module.exports = { module.exports = {
initialTraverseDependencies, initialTraverseDependencies,
traverseDependencies, traverseDependencies,
reorderDependencies, reorderGraph,
}; };