diff --git a/packages/metro/src/DeltaBundler/DeltaCalculator.js b/packages/metro/src/DeltaBundler/DeltaCalculator.js index 052d45c8..c2505ee8 100644 --- a/packages/metro/src/DeltaBundler/DeltaCalculator.js +++ b/packages/metro/src/DeltaBundler/DeltaCalculator.js @@ -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, diff --git a/packages/metro/src/DeltaBundler/__tests__/DeltaCalculator-test.js b/packages/metro/src/DeltaBundler/__tests__/DeltaCalculator-test.js index 604a48cc..92b6b15c 100644 --- a/packages/metro/src/DeltaBundler/__tests__/DeltaCalculator-test.js +++ b/packages/metro/src/DeltaBundler/__tests__/DeltaCalculator-test.js @@ -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}); diff --git a/packages/metro/src/DeltaBundler/__tests__/__snapshots__/traverseDependencies-test.js.snap b/packages/metro/src/DeltaBundler/__tests__/__snapshots__/traverseDependencies-test.js.snap index b726d4b2..66e8ce04 100644 --- a/packages/metro/src/DeltaBundler/__tests__/__snapshots__/traverseDependencies-test.js.snap +++ b/packages/metro/src/DeltaBundler/__tests__/__snapshots__/traverseDependencies-test.js.snap @@ -59,6 +59,8 @@ Object { "path": "/baz", }, }, - "entryFile": "/bundle", + "entryPoints": Array [ + "/bundle", + ], } `; diff --git a/packages/metro/src/DeltaBundler/__tests__/traverseDependencies-integration-test.js b/packages/metro/src/DeltaBundler/__tests__/traverseDependencies-integration-test.js index 7f4901b8..56d41620 100644 --- a/packages/metro/src/DeltaBundler/__tests__/traverseDependencies-integration-test.js +++ b/packages/metro/src/DeltaBundler/__tests__/traverseDependencies-integration-test.js @@ -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, diff --git a/packages/metro/src/DeltaBundler/__tests__/traverseDependencies-test.js b/packages/metro/src/DeltaBundler/__tests__/traverseDependencies-test.js index 646cac47..c3987818 100644 --- a/packages/metro/src/DeltaBundler/__tests__/traverseDependencies-test.js +++ b/packages/metro/src/DeltaBundler/__tests__/traverseDependencies-test.js @@ -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', + ]); + }); +}); diff --git a/packages/metro/src/DeltaBundler/traverseDependencies.js b/packages/metro/src/DeltaBundler/traverseDependencies.js index 6057378f..08e50093 100644 --- a/packages/metro/src/DeltaBundler/traverseDependencies.js +++ b/packages/metro/src/DeltaBundler/traverseDependencies.js @@ -35,7 +35,7 @@ export type DependencyEdges = Map; export type Graph = {| dependencies: DependencyEdges, - entryFile: string, + entryPoints: $ReadOnlyArray, |}; type Result = {added: Map, deleted: Set}; @@ -67,7 +67,7 @@ type Delta = { * since the last traversal. */ async function traverseDependencies( - paths: Array, + paths: $ReadOnlyArray, dependencyGraph: DependencyGraph, transformOptions: JSTransformerOptions, graph: Graph, @@ -139,29 +139,23 @@ async function initialTraverseDependencies( transformOptions: JSTransformerOptions, onProgress?: (numProcessed: number, total: number) => mixed = () => {}, ): Promise { - 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|} | DependencyEdge, dependencies: Map, orderedDependencies?: Map = new Map(), ): Map { - 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, };