From a5307e986e9d212253fbb6b0ef4fbfd62b15e3e5 Mon Sep 17 00:00:00 2001 From: Rafael Oleza Date: Mon, 19 Feb 2018 06:41:47 -0800 Subject: [PATCH] Make metro rely much less in the ModuleCache system Reviewed By: jeanlauliac Differential Revision: D6949157 fbshipit-source-id: cb5ef25aa582935f7c624375392be7fceefd87b6 --- .../metro/src/DeltaBundler/DeltaCalculator.js | 29 +--- .../src/DeltaBundler/DeltaTransformer.js | 92 +++++------ .../__tests__/DeltaCalculator-test.js | 59 ++++--- .../traverseDependencies-integration-test.js | 11 +- .../__tests__/traverseDependencies-test.js | 126 +++++++++------ .../src/DeltaBundler/traverseDependencies.js | 147 ++++++++++++------ .../metro/src/node-haste/DependencyGraph.js | 11 ++ packages/metro/src/node-haste/types.js | 1 + 8 files changed, 283 insertions(+), 193 deletions(-) diff --git a/packages/metro/src/DeltaBundler/DeltaCalculator.js b/packages/metro/src/DeltaBundler/DeltaCalculator.js index bc3694d6..58ca935a 100644 --- a/packages/metro/src/DeltaBundler/DeltaCalculator.js +++ b/packages/metro/src/DeltaBundler/DeltaCalculator.js @@ -19,16 +19,15 @@ const {EventEmitter} = require('events'); import type Bundler from '../Bundler'; import type {Options as JSTransformerOptions} from '../JSTransformer/worker'; import type DependencyGraph from '../node-haste/DependencyGraph'; -import type Module from '../node-haste/Module'; import type {BundleOptions} from '../shared/types.flow'; export type DeltaResult = {| - +modified: Map, + +modified: Map, +deleted: Set, +reset: boolean, |}; -import type {DependencyEdges} from './traverseDependencies'; +import type {DependencyEdge, DependencyEdges} from './traverseDependencies'; /** * This class is in charge of calculating the delta of changed modules that @@ -181,7 +180,7 @@ class DeltaCalculator extends EventEmitter { new Map(), ); - return [path, ...added]; + return Array.from(added.keys()); }, ); @@ -240,10 +239,6 @@ class DeltaCalculator extends EventEmitter { this._options.entryFile, ); - const modified = new Map([ - [path, this._dependencyGraph.getModuleForPath(path)], - ]); - const {added} = await initialTraverseDependencies( path, this._dependencyGraph, @@ -252,12 +247,8 @@ class DeltaCalculator extends EventEmitter { this._options.onProgress || undefined, ); - for (const path of added) { - modified.set(path, this._dependencyGraph.getModuleForPath(path)); - } - return { - modified, + modified: added, deleted: new Set(), reset: true, }; @@ -291,18 +282,8 @@ class DeltaCalculator extends EventEmitter { this._options.onProgress || undefined, ); - const modified = new Map(); - - for (const path of modifiedDependencies) { - modified.set(path, this._dependencyGraph.getModuleForPath(path)); - } - - for (const path of added) { - modified.set(path, this._dependencyGraph.getModuleForPath(path)); - } - return { - modified, + modified: added, deleted, reset: false, }; diff --git a/packages/metro/src/DeltaBundler/DeltaTransformer.js b/packages/metro/src/DeltaBundler/DeltaTransformer.js index f3b79f15..b3e8aced 100644 --- a/packages/metro/src/DeltaBundler/DeltaTransformer.js +++ b/packages/metro/src/DeltaBundler/DeltaTransformer.js @@ -17,7 +17,6 @@ const createModuleIdFactory = require('../lib/createModuleIdFactory'); const defaults = require('../defaults'); const getPreludeCode = require('../lib/getPreludeCode'); const nullthrows = require('fbjs/lib/nullthrows'); -const removeInlineRequiresBlacklistFromOptions = require('../lib/removeInlineRequiresBlacklistFromOptions'); const {EventEmitter} = require('events'); @@ -26,7 +25,7 @@ import type {Options as JSTransformerOptions} from '../JSTransformer/worker'; import type DependencyGraph from '../node-haste/DependencyGraph'; import type Module from '../node-haste/Module'; import type {Options as BundleOptions, MainOptions} from './'; -import type {DependencyEdges} from './traverseDependencies'; +import type {DependencyEdge, DependencyEdges} from './traverseDependencies'; import type {MetroSourceMapSegmentTuple} from 'metro-source-map'; export type DeltaEntryType = @@ -342,8 +341,14 @@ class DeltaTransformer extends EventEmitter { }), ); + const edges = await Promise.all( + modules.map(module => + this._createEdgeFromScript(module, transformOptions), + ), + ); + const transformedModules = await this._transformModules( - modules, + edges, transformOptions, dependencyEdges, ); @@ -416,7 +421,7 @@ class DeltaTransformer extends EventEmitter { } async _transformModules( - modules: Array, + modules: Array, transformOptions: JSTransformerOptions, dependencyEdges: DependencyEdges, ): Promise { @@ -429,15 +434,34 @@ class DeltaTransformer extends EventEmitter { ); } - async _transformModule( + async _createEdgeFromScript( module: Module, transformOptions: JSTransformerOptions, + ): Promise { + const result = await module.read(transformOptions); + + const edge = { + dependencies: new Map(), + inverseDependencies: new Set(), + path: module.path, + output: { + code: result.code, + map: result.map, + source: result.source, + type: 'script', + }, + }; + + return edge; + } + + async _transformModule( + edge: DependencyEdge, + transformOptions: JSTransformerOptions, dependencyEdges: DependencyEdges, ): Promise<[number, ?DeltaEntry]> { - const name = module.getName(); - const metadata = await this._getMetadata(module, transformOptions); + const name = this._dependencyGraph.getHasteName(edge.path); - const edge = dependencyEdges.get(module.path); const dependencyPairs = edge ? edge.dependencies : new Map(); let wrappedCode; @@ -445,26 +469,30 @@ class DeltaTransformer extends EventEmitter { // Get the absolute path of each of the module dependencies from the // dependency edges. The module dependencies ensure correct order, while // the dependency edges do not ensure the same order between rebuilds. - const dependencies = metadata.dependencies.map(dependency => + const dependencies = Array.from(edge.dependencies.keys()).map(dependency => nullthrows(dependencyPairs.get(dependency)), ); - if (!module.isPolyfill()) { + if (edge.output.type !== 'script') { wrappedCode = this._addDependencyMap({ - code: metadata.code, + code: edge.output.code, dependencies, name, - path: module.path, + path: edge.path, }); } else { - wrappedCode = metadata.code; + wrappedCode = edge.output.code; } const {code, map} = transformOptions.minify - ? await this._bundler.minifyModule(module.path, wrappedCode, metadata.map) - : {code: wrappedCode, map: metadata.map}; + ? await this._bundler.minifyModule( + edge.path, + wrappedCode, + edge.output.map, + ) + : {code: wrappedCode, map: edge.output.map}; - const id = this._getModuleId(module.path); + const id = this._getModuleId(edge.path); return [ id, @@ -473,9 +501,9 @@ class DeltaTransformer extends EventEmitter { id, map, name, - source: metadata.source, - path: module.path, - type: this._getModuleType(module), + source: edge.output.source, + path: edge.path, + type: edge.output.type, }, ]; } @@ -509,32 +537,6 @@ class DeltaTransformer extends EventEmitter { return addParamsToDefineCall(code, ...params); } - _getModuleType(module: Module): DeltaEntryType { - if (module.isAsset()) { - return 'asset'; - } - - if (module.isPolyfill()) { - return 'script'; - } - - return 'module'; - } - - async _getMetadata( - module: Module, - transformOptions: JSTransformerOptions, - ): Promise<{ - +code: string, - +dependencies: Array, - +map: Array, - +source: string, - }> { - return await module.read( - removeInlineRequiresBlacklistFromOptions(module.path, transformOptions), - ); - } - _onFileChange = () => { this.emit('change'); }; diff --git a/packages/metro/src/DeltaBundler/__tests__/DeltaCalculator-test.js b/packages/metro/src/DeltaBundler/__tests__/DeltaCalculator-test.js index cd3cec70..5ba4847d 100644 --- a/packages/metro/src/DeltaBundler/__tests__/DeltaCalculator-test.js +++ b/packages/metro/src/DeltaBundler/__tests__/DeltaCalculator-test.js @@ -30,6 +30,11 @@ describe('DeltaCalculator', () => { const moduleBar = createModule({path: '/bar', name: 'bar'}); const moduleBaz = createModule({path: '/baz', name: 'baz'}); + let edgeModule; + let edgeFoo; + let edgeBar; + let edgeBaz; + let deltaCalculator; let fileWatcher; let mockedDependencies; @@ -85,13 +90,23 @@ describe('DeltaCalculator', () => { initialTraverseDependencies.mockImplementationOnce( async (path, dg, opt, edges) => { - edges.set('/bundle', entryModule); - edges.set('/foo', {...moduleFoo, inverseDependencies: ['/bundle']}); - edges.set('/bar', {...moduleBar, inverseDependencies: ['/bundle']}); - edges.set('/baz', {...moduleBaz, inverseDependencies: ['/bundle']}); + edgeModule = {...entryModule}; + edgeFoo = {...moduleFoo, inverseDependencies: ['/bundle']}; + edgeBar = {...moduleBar, inverseDependencies: ['/bundle']}; + edgeBaz = {...moduleBaz, inverseDependencies: ['/bundle']}; + + edges.set('/bundle', edgeModule); + edges.set('/foo', edgeFoo); + edges.set('/bar', edgeBar); + edges.set('/baz', edgeBaz); return { - added: new Set(['/bundle', '/foo', '/bar', '/baz']), + added: new Map([ + ['/bundle', edgeModule], + ['/foo', edgeFoo], + ['/bar', edgeBar], + ['/baz', edgeBaz], + ]), deleted: new Set(), }; }, @@ -136,9 +151,9 @@ describe('DeltaCalculator', () => { expect(result).toEqual({ modified: new Map([ ['/bundle', entryModule], - ['/foo', moduleFoo], - ['/bar', moduleBar], - ['/baz', moduleBaz], + ['/foo', edgeFoo], + ['/bar', edgeBar], + ['/baz', edgeBaz], ]), deleted: new Set(), reset: true, @@ -164,7 +179,7 @@ describe('DeltaCalculator', () => { traverseDependencies.mockReturnValue( Promise.resolve({ - added: new Set(), + added: new Map([['/foo', edgeFoo]]), deleted: new Set(), }), ); @@ -172,7 +187,7 @@ describe('DeltaCalculator', () => { const result = await deltaCalculator.getDelta(); expect(result).toEqual({ - modified: new Map([['/foo', moduleFoo]]), + modified: new Map([['/foo', edgeFoo]]), deleted: new Set(), reset: false, }); @@ -188,7 +203,7 @@ describe('DeltaCalculator', () => { traverseDependencies.mockReturnValue( Promise.resolve({ - added: new Set(), + added: new Map([['/foo', edgeFoo]]), deleted: new Set(['/baz']), }), ); @@ -196,7 +211,7 @@ describe('DeltaCalculator', () => { const result = await deltaCalculator.getDelta(); expect(result).toEqual({ - modified: new Map([['/foo', moduleFoo]]), + modified: new Map([['/foo', edgeFoo]]), deleted: new Set(['/baz']), reset: false, }); @@ -211,19 +226,22 @@ describe('DeltaCalculator', () => { fileWatcher.emit('change', {eventsQueue: [{filePath: '/foo'}]}); const moduleQux = createModule({path: '/qux', name: 'qux'}); + const edgeQux = {...moduleQux, inverseDependencies: []}; mockedDependencies.push(moduleQux); - traverseDependencies.mockReturnValue( - Promise.resolve({ - added: new Set([moduleQux.path]), + traverseDependencies.mockImplementation(async (path, dg, opt, edges) => { + edges.set('/qux', edgeQux); + + return { + added: new Map([['/foo', edgeFoo], ['/qux', edgeQux]]), deleted: new Set(['/bar', '/baz']), - }), - ); + }; + }); const result = await deltaCalculator.getDelta(); expect(result).toEqual({ - modified: new Map([['/foo', moduleFoo], ['/qux', moduleQux]]), + modified: new Map([['/foo', edgeFoo], ['/qux', edgeQux]]), deleted: new Set(['/bar', '/baz']), reset: false, }); @@ -280,17 +298,18 @@ describe('DeltaCalculator', () => { traverseDependencies.mockReturnValue( Promise.resolve({ - added: new Set(), + added: new Map([['/bundle', edgeModule]]), deleted: new Set(['/foo']), }), ); expect(await deltaCalculator.getDelta()).toEqual({ - modified: new Map([['/bundle', entryModule]]), + modified: new Map([['/bundle', edgeModule]]), deleted: new Set(['/foo']), reset: false, }); + expect(traverseDependencies).toHaveBeenCalledTimes(1); expect(traverseDependencies.mock.calls[0][0]).toEqual(['/bundle']); }); diff --git a/packages/metro/src/DeltaBundler/__tests__/traverseDependencies-integration-test.js b/packages/metro/src/DeltaBundler/__tests__/traverseDependencies-integration-test.js index b5e48ad2..1006d5e3 100644 --- a/packages/metro/src/DeltaBundler/__tests__/traverseDependencies-integration-test.js +++ b/packages/metro/src/DeltaBundler/__tests__/traverseDependencies-integration-test.js @@ -69,11 +69,11 @@ describe('traverseDependencies', function() { ); const dependencies = recursive - ? added + ? [...added.values()].map(edge => edge.path) : edges.get(entryPath).dependencies.values(); return await Promise.all( - [entryPath, ...dependencies].map(async path => { + [...dependencies].map(async path => { const dep = dgraph.getModuleForPath(path); const moduleDependencies = await dep.getDependencies(); @@ -224,13 +224,6 @@ describe('traverseDependencies', function() { false, ); expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['a'], - isAsset: false, - isPolyfill: false, - }, { id: 'a', path: '/root/a.js', diff --git a/packages/metro/src/DeltaBundler/__tests__/traverseDependencies-test.js b/packages/metro/src/DeltaBundler/__tests__/traverseDependencies-test.js index 1d287117..ec74d79c 100644 --- a/packages/metro/src/DeltaBundler/__tests__/traverseDependencies-test.js +++ b/packages/metro/src/DeltaBundler/__tests__/traverseDependencies-test.js @@ -31,16 +31,36 @@ function deferred(value) { return {promise, resolve: () => resolve(value)}; } -function createModule({path, name, isAsset}) { +function createModule({path, name, isAsset, isPolyfill}) { return { path, name, - async getName() { - return name; - }, isAsset() { return !!isAsset; }, + isPolyfill() { + return !!isPolyfill; + }, + async read() { + const deps = mockedDependencyTree.get(path); + const dependencies = deps ? deps.map(dep => dep.name) : []; + + return { + code: '// code', + map: [], + source: '// source', + dependencies, + }; + }, + }; +} + +function getPaths({added, deleted}) { + const addedPaths = [...added.values()].map(edge => edge.path); + + return { + added: new Set(addedPaths), + deleted, }; } @@ -58,10 +78,6 @@ beforeEach(async () => { getModuleForPath(path) { return Array.from(mockedDependencies).find(dep => dep.path === path); }, - async getShallowDependencies(path) { - const deps = mockedDependencyTree.get(path); - return deps ? await Promise.all(deps.map(dep => dep.getName())) : []; - }, resolveDependency(module, relativePath) { const deps = mockedDependencyTree.get(module.path); const dependency = deps.filter(dep => dep.name === relativePath)[0]; @@ -83,8 +99,8 @@ it('should do the initial traversal correctly', async () => { edges, ); - expect(result).toEqual({ - added: new Set(['/foo', '/bar', '/baz']), + expect(getPaths(result)).toEqual({ + added: new Set(['/bundle', '/foo', '/bar', '/baz']), deleted: new Set(), }); }); @@ -94,9 +110,11 @@ it('should return an empty result when there are no changes', async () => { await initialTraverseDependencies('/bundle', dependencyGraph, {}, edges); expect( - await traverseDependencies(['/bundle'], dependencyGraph, {}, edges), + getPaths( + await traverseDependencies(['/bundle'], dependencyGraph, {}, edges), + ), ).toEqual({ - added: new Set(), + added: new Set(['/bundle']), deleted: new Set(), }); }); @@ -109,9 +127,9 @@ it('should return a removed dependency', async () => { mockedDependencyTree.set(moduleFoo.path, [moduleBaz]); expect( - await traverseDependencies(['/foo'], dependencyGraph, {}, edges), + getPaths(await traverseDependencies(['/foo'], dependencyGraph, {}, edges)), ).toEqual({ - added: new Set(), + added: new Set(['/foo']), deleted: new Set(['/bar']), }); }); @@ -126,9 +144,9 @@ it('should return added/removed dependencies', async () => { mockedDependencies.add(moduleQux); expect( - await traverseDependencies(['/foo'], dependencyGraph, {}, edges), + getPaths(await traverseDependencies(['/foo'], dependencyGraph, {}, edges)), ).toEqual({ - added: new Set(['/qux']), + added: new Set(['/foo', '/qux']), deleted: new Set(['/bar', '/baz']), }); }); @@ -159,19 +177,20 @@ describe('edge cases', () => { const moduleQux = createModule({path: '/qux', name: 'qux'}); mockedDependencyTree.set(moduleFoo.path, [moduleQux, moduleBar]); mockedDependencies.add(moduleQux); - mockedDependencies.delete(moduleBaz); // Call traverseDependencies with /foo, /qux and /baz, simulating that the // user has modified the 3 files. expect( - await traverseDependencies( - ['/foo', '/qux', '/baz'], - dependencyGraph, - {}, - edges, + getPaths( + await traverseDependencies( + ['/foo', '/qux', '/baz'], + dependencyGraph, + {}, + edges, + ), ), ).toEqual({ - added: new Set(['/qux']), + added: new Set(['/foo', '/qux']), deleted: new Set(['/baz']), }); }); @@ -193,9 +212,11 @@ describe('edge cases', () => { // Call traverseDependencies with /foo, /qux and /baz, simulating that the // user has modified the 3 files. expect( - await traverseDependencies(['/bundle'], dependencyGraph, {}, edges), + getPaths( + await traverseDependencies(['/bundle'], dependencyGraph, {}, edges), + ), ).toEqual({ - added: new Set(['/foo-renamed']), + added: new Set(['/bundle', '/foo-renamed']), deleted: new Set(['/foo']), }); }); @@ -205,13 +226,19 @@ describe('edge cases', () => { await initialTraverseDependencies('/bundle', dependencyGraph, {}, edges); mockedDependencyTree.set(moduleFoo.path, [moduleBar]); - mockedDependencies.delete(moduleBaz); // Modify /baz, rename it to /qux and modify it again. expect( - await traverseDependencies(['/baz', '/foo'], dependencyGraph, {}, edges), + getPaths( + await traverseDependencies( + ['/baz', '/foo'], + dependencyGraph, + {}, + edges, + ), + ), ).toEqual({ - added: new Set(), + added: new Set(['/foo']), deleted: new Set(['/baz']), }); }); @@ -227,9 +254,11 @@ describe('edge cases', () => { // Modify /baz, rename it to /qux and modify it again. expect( - await traverseDependencies(['/foo'], dependencyGraph, {}, edges), + getPaths( + await traverseDependencies(['/foo'], dependencyGraph, {}, edges), + ), ).toEqual({ - added: new Set(['/baz-moved']), + added: new Set(['/foo', '/baz-moved']), deleted: new Set(['/baz']), }); }); @@ -273,14 +302,16 @@ describe('edge cases', () => { async function assertOrder() { expect( Array.from( - (await initialTraverseDependencies( - '/bundle', - dependencyGraph, - {}, - new Map(), - )).added, + getPaths( + await initialTraverseDependencies( + '/bundle', + dependencyGraph, + {}, + new Map(), + ), + ).added, ), - ).toEqual(['/foo', '/baz', '/bar']); + ).toEqual(['/bundle', '/foo', '/baz', '/bar']); } // Create a dependency tree where moduleBaz has two inverse dependencies. @@ -300,7 +331,10 @@ describe('edge cases', () => { }); it('should simplify inlineRequires transform option', async () => { - jest.spyOn(dependencyGraph, 'getShallowDependencies'); + jest.spyOn(entryModule, 'read'); + jest.spyOn(moduleFoo, 'read'); + jest.spyOn(moduleBar, 'read'); + jest.spyOn(moduleBaz, 'read'); const edges = new Map(); const transformOptions = { @@ -318,14 +352,12 @@ describe('edge cases', () => { edges, ); - expect(dependencyGraph.getShallowDependencies.mock.calls).toEqual([ - ['/bundle', {inlineRequires: true}], - ['/foo', {inlineRequires: true}], - ['/bar', {inlineRequires: true}], - ['/baz', {inlineRequires: false}], - ]); + expect(entryModule.read).toHaveBeenCalledWith({inlineRequires: true}); + expect(moduleFoo.read).toHaveBeenCalledWith({inlineRequires: true}); + expect(moduleBar.read).toHaveBeenCalledWith({inlineRequires: true}); + expect(moduleBaz.read).toHaveBeenCalledWith({inlineRequires: false}); - dependencyGraph.getShallowDependencies.mockClear(); + moduleFoo.read.mockClear(); await traverseDependencies( ['/foo'], @@ -334,8 +366,6 @@ describe('edge cases', () => { edges, ); - expect(dependencyGraph.getShallowDependencies.mock.calls).toEqual([ - ['/foo', {inlineRequires: true}], - ]); + expect(moduleFoo.read).toHaveBeenCalledWith({inlineRequires: true}); }); }); diff --git a/packages/metro/src/DeltaBundler/traverseDependencies.js b/packages/metro/src/DeltaBundler/traverseDependencies.js index f92fbdfe..e277a5ca 100644 --- a/packages/metro/src/DeltaBundler/traverseDependencies.js +++ b/packages/metro/src/DeltaBundler/traverseDependencies.js @@ -14,16 +14,26 @@ const removeInlineRequiresBlacklistFromOptions = require('../lib/removeInlineReq import type {Options as JSTransformerOptions} from '../JSTransformer/worker'; import type DependencyGraph from '../node-haste/DependencyGraph'; +import type Module from '../node-haste/Module'; +import type {MetroSourceMapSegmentTuple} from 'metro-source-map'; + +export type DependencyType = 'module' | 'script' | 'asset'; export type DependencyEdge = {| dependencies: Map, inverseDependencies: Set, path: string, + output: { + code: string, + map: Array, + source: string, + type: DependencyType, + }, |}; export type DependencyEdges = Map; -type Result = {added: Set, deleted: Set}; +type Result = {added: Map, deleted: Set}; /** * Dependency Traversal logic for the Delta Bundler. This method calculates @@ -58,12 +68,12 @@ async function traverseDependencies( ), ); - const added = new Set(); + const added = new Map(); const deleted = new Set(); for (const change of changes) { - for (const path of change.added) { - added.add(path); + for (const [path, edge] of change.added) { + added.set(path, edge); } for (const path of change.deleted) { // If a path has been marked both as added and deleted, it means that this @@ -90,8 +100,8 @@ async function initialTraverseDependencies( transformOptions: JSTransformerOptions, edges: DependencyEdges, onProgress?: (numProcessed: number, total: number) => mixed = () => {}, -) { - createEdge(path, edges); +): Promise { + createEdge(dependencyGraph.getModuleForPath(path), edges); return await traverseDependenciesForSingleFile( path, @@ -113,13 +123,18 @@ async function traverseDependenciesForSingleFile( // If the passed edge does not exist does not exist in the graph, ignore it. if (!edge) { - return {added: new Set(), deleted: new Set()}; + return {added: new Map(), deleted: new Set()}; } - const shallow = await dependencyGraph.getShallowDependencies( - path, - removeInlineRequiresBlacklistFromOptions(path, transformOptions), - ); + const result = await dependencyGraph + .getModuleForPath(path) + .read(removeInlineRequiresBlacklistFromOptions(path, transformOptions)); + + edge.output.code = result.code; + edge.output.map = result.map; + edge.output.source = result.source; + + const shallow = result.dependencies; // Get the absolute path of all sub-dependencies (some of them could have been // moved but maintain the same relative path). @@ -132,8 +147,6 @@ async function traverseDependenciesForSingleFile( const previousDependencies = new Set(edge.dependencies.values()); - const nonNullEdge = edge; - let numProcessed = 0; let total = 1; onProgress(numProcessed, total); @@ -141,7 +154,7 @@ async function traverseDependenciesForSingleFile( const deleted = Array.from(edge.dependencies.entries()) .map(([relativePath, absolutePath]) => { if (!currentDependencies.has(absolutePath)) { - return removeDependency(nonNullEdge, relativePath, edges); + return removeDependency(edge, relativePath, edges); } else { return undefined; } @@ -151,15 +164,15 @@ async function traverseDependenciesForSingleFile( // Check all the module dependencies and start traversing the tree from each // added and removed dependency, to get all the modules that have to be added // and removed from the dependency graph. - const added = await Promise.all( + const addedDependencies = await Promise.all( Array.from(currentDependencies).map( async ([absolutePath, relativePath]) => { if (previousDependencies.has(absolutePath)) { - return new Set(); + return new Map(); } return await addDependency( - nonNullEdge, + edge, relativePath, dependencyGraph, transformOptions, @@ -177,11 +190,13 @@ async function traverseDependenciesForSingleFile( ), ); + const added = [new Map([[edge.path, edge]])].concat(addedDependencies); + numProcessed++; onProgress(numProcessed, total); return { - added: flatten(reorderDependencies(added, edges)), + added: flattenMap(reorderDependencies(added, edges)), deleted: flatten(deleted), }; } @@ -194,7 +209,7 @@ async function addDependency( edges: DependencyEdges, onDependencyAdd: () => mixed, onDependencyAdded: () => mixed, -): Promise> { +): Promise> { const parentModule = dependencyGraph.getModuleForPath(parentEdge.path); const module = dependencyGraph.resolveDependency( parentModule, @@ -205,35 +220,36 @@ async function addDependency( // Update the parent edge to keep track of the new dependency. parentEdge.dependencies.set(relativePath, module.path); - let dependencyEdge = edges.get(module.path); + const existingEdge = edges.get(module.path); // The new dependency was already in the graph, we don't need to do anything. - if (dependencyEdge) { - dependencyEdge.inverseDependencies.add(parentEdge.path); + if (existingEdge) { + existingEdge.inverseDependencies.add(parentEdge.path); - return new Set(); + return new Map(); } onDependencyAdd(); // Create the new edge and traverse all its subdependencies, looking for new // subdependencies recursively. - dependencyEdge = createEdge(module.path, edges); - dependencyEdge.inverseDependencies.add(parentEdge.path); + const edge = createEdge(module, edges); + edge.inverseDependencies.add(parentEdge.path); - const addedDependencies = new Set([dependencyEdge.path]); + const addedDependencies = new Map([[edge.path, edge]]); - const shallowDeps = await dependencyGraph.getShallowDependencies( - dependencyEdge.path, - removeInlineRequiresBlacklistFromOptions(module.path, transformOptions), + const result = await module.read( + removeInlineRequiresBlacklistFromOptions(edge.path, transformOptions), ); - const nonNullDependencyEdge = dependencyEdge; + edge.output.code = result.code; + edge.output.map = result.map; + edge.output.source = result.source; const added = await Promise.all( - shallowDeps.map(dep => + result.dependencies.map(dep => addDependency( - nonNullDependencyEdge, + edge, dep, dependencyGraph, transformOptions, @@ -244,8 +260,8 @@ async function addDependency( ), ); - for (const newDependency of flatten(added)) { - addedDependencies.add(newDependency); + for (const [newDepPath, newDepEdge] of flattenMap(added)) { + addedDependencies.set(newDepPath, newDepEdge); } onDependencyAdded(); @@ -294,17 +310,35 @@ function removeDependency( return removedDependencies; } -function createEdge(path: string, edges: DependencyEdges): DependencyEdge { +function createEdge(module: Module, edges: DependencyEdges): DependencyEdge { const edge = { dependencies: new Map(), inverseDependencies: new Set(), - path, + path: module.path, + output: { + code: '', + map: [], + source: '', + type: getType(module), + }, }; - edges.set(path, edge); + edges.set(module.path, edge); return edge; } +function getType(module: Module): DependencyType { + if (module.isAsset()) { + return 'asset'; + } + + if (module.isPolyfill()) { + return 'script'; + } + + return 'module'; +} + function destroyEdge(edge: DependencyEdge, edges: DependencyEdges) { edges.delete(edge.path); } @@ -347,29 +381,36 @@ function resolveDependencies( * guarantee the same order between runs. */ function reorderDependencies( - dependencies: Array>, + dependencies: Array>, edges: DependencyEdges, -): Array> { - const flatDependencies = flatten(dependencies); +): Array> { + const flatDependencies = flattenMap(dependencies); - return dependencies.map(dependencies => - reorderDependency(Array.from(dependencies)[0], flatDependencies, edges), - ); + return dependencies.map(dependencies => { + if (dependencies.size === 0) { + return new Map(); + } + return reorderDependency( + Array.from(dependencies)[0][0], + flatDependencies, + edges, + ); + }); } function reorderDependency( path: string, - dependencies: Set, + dependencies: Map, edges: DependencyEdges, - orderedDependencies?: Set = new Set(), -): Set { + orderedDependencies?: Map = new Map(), +): Map { const edge = edges.get(path); if (!edge || !dependencies.has(path) || orderedDependencies.has(path)) { return orderedDependencies; } - orderedDependencies.add(path); + orderedDependencies.set(path, edge); edge.dependencies.forEach(path => reorderDependency(path, dependencies, edges, orderedDependencies), @@ -390,6 +431,18 @@ function flatten(input: Iterable>): Set { return output; } +function flattenMap(input: Iterable>): Map { + const output = new Map(); + + for (const items of input) { + for (const [key, value] of items.entries()) { + output.set(key, value); + } + } + + return output; +} + module.exports = { initialTraverseDependencies, traverseDependencies, diff --git a/packages/metro/src/node-haste/DependencyGraph.js b/packages/metro/src/node-haste/DependencyGraph.js index f80865c7..55a527ec 100644 --- a/packages/metro/src/node-haste/DependencyGraph.js +++ b/packages/metro/src/node-haste/DependencyGraph.js @@ -22,6 +22,7 @@ const fs = require('fs'); const isAbsolutePath = require('absolute-path'); const parsePlatformFilePath = require('./lib/parsePlatformFilePath'); const path = require('path'); +const toLocalPath = require('../node-haste/lib/toLocalPath'); const util = require('util'); const {ModuleResolver} = require('./DependencyGraph/ModuleResolution'); @@ -274,6 +275,16 @@ class DependencyGraph extends EventEmitter { return platform; } + getHasteName(filePath: string): string { + const hasteName = this._hasteFS.getModuleName(filePath); + + if (hasteName) { + return hasteName; + } + + return toLocalPath(this._opts.projectRoots, filePath); + } + getAbsolutePath(filePath: string) { if (isAbsolutePath(filePath)) { return path.resolve(filePath); diff --git a/packages/metro/src/node-haste/types.js b/packages/metro/src/node-haste/types.js index 07fd4457..540cbd68 100644 --- a/packages/metro/src/node-haste/types.js +++ b/packages/metro/src/node-haste/types.js @@ -14,5 +14,6 @@ export type HasteFS = { exists(filePath: string): boolean, getAllFiles(): Array, + getModuleName(filePath: string): ?string, matchFiles(pattern: RegExp | string): Array, };