Make metro rely much less in the ModuleCache system

Reviewed By: jeanlauliac

Differential Revision: D6949157

fbshipit-source-id: cb5ef25aa582935f7c624375392be7fceefd87b6
This commit is contained in:
Rafael Oleza 2018-02-19 06:41:47 -08:00 committed by Facebook Github Bot
parent 03c3b3d219
commit a5307e986e
8 changed files with 283 additions and 193 deletions

View File

@ -19,16 +19,15 @@ const {EventEmitter} = require('events');
import type Bundler from '../Bundler'; import type Bundler from '../Bundler';
import type {Options as JSTransformerOptions} from '../JSTransformer/worker'; import type {Options as JSTransformerOptions} from '../JSTransformer/worker';
import type DependencyGraph from '../node-haste/DependencyGraph'; import type DependencyGraph from '../node-haste/DependencyGraph';
import type Module from '../node-haste/Module';
import type {BundleOptions} from '../shared/types.flow'; import type {BundleOptions} from '../shared/types.flow';
export type DeltaResult = {| export type DeltaResult = {|
+modified: Map<string, Module>, +modified: Map<string, DependencyEdge>,
+deleted: Set<string>, +deleted: Set<string>,
+reset: boolean, +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 * This class is in charge of calculating the delta of changed modules that
@ -181,7 +180,7 @@ class DeltaCalculator extends EventEmitter {
new Map(), new Map(),
); );
return [path, ...added]; return Array.from(added.keys());
}, },
); );
@ -240,10 +239,6 @@ class DeltaCalculator extends EventEmitter {
this._options.entryFile, this._options.entryFile,
); );
const modified = new Map([
[path, this._dependencyGraph.getModuleForPath(path)],
]);
const {added} = await initialTraverseDependencies( const {added} = await initialTraverseDependencies(
path, path,
this._dependencyGraph, this._dependencyGraph,
@ -252,12 +247,8 @@ class DeltaCalculator extends EventEmitter {
this._options.onProgress || undefined, this._options.onProgress || undefined,
); );
for (const path of added) {
modified.set(path, this._dependencyGraph.getModuleForPath(path));
}
return { return {
modified, modified: added,
deleted: new Set(), deleted: new Set(),
reset: true, reset: true,
}; };
@ -291,18 +282,8 @@ class DeltaCalculator extends EventEmitter {
this._options.onProgress || undefined, 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 { return {
modified, modified: added,
deleted, deleted,
reset: false, reset: false,
}; };

View File

@ -17,7 +17,6 @@ const createModuleIdFactory = require('../lib/createModuleIdFactory');
const defaults = require('../defaults'); const defaults = require('../defaults');
const getPreludeCode = require('../lib/getPreludeCode'); const getPreludeCode = require('../lib/getPreludeCode');
const nullthrows = require('fbjs/lib/nullthrows'); const nullthrows = require('fbjs/lib/nullthrows');
const removeInlineRequiresBlacklistFromOptions = require('../lib/removeInlineRequiresBlacklistFromOptions');
const {EventEmitter} = require('events'); 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 DependencyGraph from '../node-haste/DependencyGraph';
import type Module from '../node-haste/Module'; import type Module from '../node-haste/Module';
import type {Options as BundleOptions, MainOptions} from './'; 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'; import type {MetroSourceMapSegmentTuple} from 'metro-source-map';
export type DeltaEntryType = 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( const transformedModules = await this._transformModules(
modules, edges,
transformOptions, transformOptions,
dependencyEdges, dependencyEdges,
); );
@ -416,7 +421,7 @@ class DeltaTransformer extends EventEmitter {
} }
async _transformModules( async _transformModules(
modules: Array<Module>, modules: Array<DependencyEdge>,
transformOptions: JSTransformerOptions, transformOptions: JSTransformerOptions,
dependencyEdges: DependencyEdges, dependencyEdges: DependencyEdges,
): Promise<DeltaEntries> { ): Promise<DeltaEntries> {
@ -429,15 +434,34 @@ class DeltaTransformer extends EventEmitter {
); );
} }
async _transformModule( async _createEdgeFromScript(
module: Module, module: Module,
transformOptions: JSTransformerOptions, transformOptions: JSTransformerOptions,
): Promise<DependencyEdge> {
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, dependencyEdges: DependencyEdges,
): Promise<[number, ?DeltaEntry]> { ): Promise<[number, ?DeltaEntry]> {
const name = module.getName(); const name = this._dependencyGraph.getHasteName(edge.path);
const metadata = await this._getMetadata(module, transformOptions);
const edge = dependencyEdges.get(module.path);
const dependencyPairs = edge ? edge.dependencies : new Map(); const dependencyPairs = edge ? edge.dependencies : new Map();
let wrappedCode; let wrappedCode;
@ -445,26 +469,30 @@ class DeltaTransformer extends EventEmitter {
// Get the absolute path of each of the module dependencies from the // Get the absolute path of each of the module dependencies from the
// dependency edges. The module dependencies ensure correct order, while // dependency edges. The module dependencies ensure correct order, while
// the dependency edges do not ensure the same order between rebuilds. // 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)), nullthrows(dependencyPairs.get(dependency)),
); );
if (!module.isPolyfill()) { if (edge.output.type !== 'script') {
wrappedCode = this._addDependencyMap({ wrappedCode = this._addDependencyMap({
code: metadata.code, code: edge.output.code,
dependencies, dependencies,
name, name,
path: module.path, path: edge.path,
}); });
} else { } else {
wrappedCode = metadata.code; wrappedCode = edge.output.code;
} }
const {code, map} = transformOptions.minify const {code, map} = transformOptions.minify
? await this._bundler.minifyModule(module.path, wrappedCode, metadata.map) ? await this._bundler.minifyModule(
: {code: wrappedCode, map: metadata.map}; 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 [ return [
id, id,
@ -473,9 +501,9 @@ class DeltaTransformer extends EventEmitter {
id, id,
map, map,
name, name,
source: metadata.source, source: edge.output.source,
path: module.path, path: edge.path,
type: this._getModuleType(module), type: edge.output.type,
}, },
]; ];
} }
@ -509,32 +537,6 @@ class DeltaTransformer extends EventEmitter {
return addParamsToDefineCall(code, ...params); 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<string>,
+map: Array<MetroSourceMapSegmentTuple>,
+source: string,
}> {
return await module.read(
removeInlineRequiresBlacklistFromOptions(module.path, transformOptions),
);
}
_onFileChange = () => { _onFileChange = () => {
this.emit('change'); this.emit('change');
}; };

View File

@ -30,6 +30,11 @@ describe('DeltaCalculator', () => {
const moduleBar = createModule({path: '/bar', name: 'bar'}); const moduleBar = createModule({path: '/bar', name: 'bar'});
const moduleBaz = createModule({path: '/baz', name: 'baz'}); const moduleBaz = createModule({path: '/baz', name: 'baz'});
let edgeModule;
let edgeFoo;
let edgeBar;
let edgeBaz;
let deltaCalculator; let deltaCalculator;
let fileWatcher; let fileWatcher;
let mockedDependencies; let mockedDependencies;
@ -85,13 +90,23 @@ describe('DeltaCalculator', () => {
initialTraverseDependencies.mockImplementationOnce( initialTraverseDependencies.mockImplementationOnce(
async (path, dg, opt, edges) => { async (path, dg, opt, edges) => {
edges.set('/bundle', entryModule); edgeModule = {...entryModule};
edges.set('/foo', {...moduleFoo, inverseDependencies: ['/bundle']}); edgeFoo = {...moduleFoo, inverseDependencies: ['/bundle']};
edges.set('/bar', {...moduleBar, inverseDependencies: ['/bundle']}); edgeBar = {...moduleBar, inverseDependencies: ['/bundle']};
edges.set('/baz', {...moduleBaz, inverseDependencies: ['/bundle']}); edgeBaz = {...moduleBaz, inverseDependencies: ['/bundle']};
edges.set('/bundle', edgeModule);
edges.set('/foo', edgeFoo);
edges.set('/bar', edgeBar);
edges.set('/baz', edgeBaz);
return { return {
added: new Set(['/bundle', '/foo', '/bar', '/baz']), added: new Map([
['/bundle', edgeModule],
['/foo', edgeFoo],
['/bar', edgeBar],
['/baz', edgeBaz],
]),
deleted: new Set(), deleted: new Set(),
}; };
}, },
@ -136,9 +151,9 @@ describe('DeltaCalculator', () => {
expect(result).toEqual({ expect(result).toEqual({
modified: new Map([ modified: new Map([
['/bundle', entryModule], ['/bundle', entryModule],
['/foo', moduleFoo], ['/foo', edgeFoo],
['/bar', moduleBar], ['/bar', edgeBar],
['/baz', moduleBaz], ['/baz', edgeBaz],
]), ]),
deleted: new Set(), deleted: new Set(),
reset: true, reset: true,
@ -164,7 +179,7 @@ describe('DeltaCalculator', () => {
traverseDependencies.mockReturnValue( traverseDependencies.mockReturnValue(
Promise.resolve({ Promise.resolve({
added: new Set(), added: new Map([['/foo', edgeFoo]]),
deleted: new Set(), deleted: new Set(),
}), }),
); );
@ -172,7 +187,7 @@ describe('DeltaCalculator', () => {
const result = await deltaCalculator.getDelta(); const result = await deltaCalculator.getDelta();
expect(result).toEqual({ expect(result).toEqual({
modified: new Map([['/foo', moduleFoo]]), modified: new Map([['/foo', edgeFoo]]),
deleted: new Set(), deleted: new Set(),
reset: false, reset: false,
}); });
@ -188,7 +203,7 @@ describe('DeltaCalculator', () => {
traverseDependencies.mockReturnValue( traverseDependencies.mockReturnValue(
Promise.resolve({ Promise.resolve({
added: new Set(), added: new Map([['/foo', edgeFoo]]),
deleted: new Set(['/baz']), deleted: new Set(['/baz']),
}), }),
); );
@ -196,7 +211,7 @@ describe('DeltaCalculator', () => {
const result = await deltaCalculator.getDelta(); const result = await deltaCalculator.getDelta();
expect(result).toEqual({ expect(result).toEqual({
modified: new Map([['/foo', moduleFoo]]), modified: new Map([['/foo', edgeFoo]]),
deleted: new Set(['/baz']), deleted: new Set(['/baz']),
reset: false, reset: false,
}); });
@ -211,19 +226,22 @@ describe('DeltaCalculator', () => {
fileWatcher.emit('change', {eventsQueue: [{filePath: '/foo'}]}); fileWatcher.emit('change', {eventsQueue: [{filePath: '/foo'}]});
const moduleQux = createModule({path: '/qux', name: 'qux'}); const moduleQux = createModule({path: '/qux', name: 'qux'});
const edgeQux = {...moduleQux, inverseDependencies: []};
mockedDependencies.push(moduleQux); mockedDependencies.push(moduleQux);
traverseDependencies.mockReturnValue( traverseDependencies.mockImplementation(async (path, dg, opt, edges) => {
Promise.resolve({ edges.set('/qux', edgeQux);
added: new Set([moduleQux.path]),
return {
added: new Map([['/foo', edgeFoo], ['/qux', edgeQux]]),
deleted: new Set(['/bar', '/baz']), deleted: new Set(['/bar', '/baz']),
}), };
); });
const result = await deltaCalculator.getDelta(); const result = await deltaCalculator.getDelta();
expect(result).toEqual({ expect(result).toEqual({
modified: new Map([['/foo', moduleFoo], ['/qux', moduleQux]]), modified: new Map([['/foo', edgeFoo], ['/qux', edgeQux]]),
deleted: new Set(['/bar', '/baz']), deleted: new Set(['/bar', '/baz']),
reset: false, reset: false,
}); });
@ -280,17 +298,18 @@ describe('DeltaCalculator', () => {
traverseDependencies.mockReturnValue( traverseDependencies.mockReturnValue(
Promise.resolve({ Promise.resolve({
added: new Set(), added: new Map([['/bundle', edgeModule]]),
deleted: new Set(['/foo']), deleted: new Set(['/foo']),
}), }),
); );
expect(await deltaCalculator.getDelta()).toEqual({ expect(await deltaCalculator.getDelta()).toEqual({
modified: new Map([['/bundle', entryModule]]), modified: new Map([['/bundle', edgeModule]]),
deleted: new Set(['/foo']), deleted: new Set(['/foo']),
reset: false, reset: false,
}); });
expect(traverseDependencies).toHaveBeenCalledTimes(1);
expect(traverseDependencies.mock.calls[0][0]).toEqual(['/bundle']); expect(traverseDependencies.mock.calls[0][0]).toEqual(['/bundle']);
}); });

View File

@ -69,11 +69,11 @@ describe('traverseDependencies', function() {
); );
const dependencies = recursive const dependencies = recursive
? added ? [...added.values()].map(edge => edge.path)
: edges.get(entryPath).dependencies.values(); : edges.get(entryPath).dependencies.values();
return await Promise.all( return await Promise.all(
[entryPath, ...dependencies].map(async path => { [...dependencies].map(async path => {
const dep = dgraph.getModuleForPath(path); const dep = dgraph.getModuleForPath(path);
const moduleDependencies = await dep.getDependencies(); const moduleDependencies = await dep.getDependencies();
@ -224,13 +224,6 @@ describe('traverseDependencies', function() {
false, false,
); );
expect(deps).toEqual([ expect(deps).toEqual([
{
id: 'index',
path: '/root/index.js',
dependencies: ['a'],
isAsset: false,
isPolyfill: false,
},
{ {
id: 'a', id: 'a',
path: '/root/a.js', path: '/root/a.js',

View File

@ -31,16 +31,36 @@ function deferred(value) {
return {promise, resolve: () => resolve(value)}; return {promise, resolve: () => resolve(value)};
} }
function createModule({path, name, isAsset}) { function createModule({path, name, isAsset, isPolyfill}) {
return { return {
path, path,
name, name,
async getName() {
return name;
},
isAsset() { isAsset() {
return !!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) { getModuleForPath(path) {
return Array.from(mockedDependencies).find(dep => dep.path === 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) { resolveDependency(module, relativePath) {
const deps = mockedDependencyTree.get(module.path); const deps = mockedDependencyTree.get(module.path);
const dependency = deps.filter(dep => dep.name === relativePath)[0]; const dependency = deps.filter(dep => dep.name === relativePath)[0];
@ -83,8 +99,8 @@ it('should do the initial traversal correctly', async () => {
edges, edges,
); );
expect(result).toEqual({ expect(getPaths(result)).toEqual({
added: new Set(['/foo', '/bar', '/baz']), added: new Set(['/bundle', '/foo', '/bar', '/baz']),
deleted: new Set(), deleted: new Set(),
}); });
}); });
@ -94,9 +110,11 @@ it('should return an empty result when there are no changes', async () => {
await initialTraverseDependencies('/bundle', dependencyGraph, {}, edges); await initialTraverseDependencies('/bundle', dependencyGraph, {}, edges);
expect( expect(
await traverseDependencies(['/bundle'], dependencyGraph, {}, edges), getPaths(
await traverseDependencies(['/bundle'], dependencyGraph, {}, edges),
),
).toEqual({ ).toEqual({
added: new Set(), added: new Set(['/bundle']),
deleted: new Set(), deleted: new Set(),
}); });
}); });
@ -109,9 +127,9 @@ it('should return a removed dependency', async () => {
mockedDependencyTree.set(moduleFoo.path, [moduleBaz]); mockedDependencyTree.set(moduleFoo.path, [moduleBaz]);
expect( expect(
await traverseDependencies(['/foo'], dependencyGraph, {}, edges), getPaths(await traverseDependencies(['/foo'], dependencyGraph, {}, edges)),
).toEqual({ ).toEqual({
added: new Set(), added: new Set(['/foo']),
deleted: new Set(['/bar']), deleted: new Set(['/bar']),
}); });
}); });
@ -126,9 +144,9 @@ it('should return added/removed dependencies', async () => {
mockedDependencies.add(moduleQux); mockedDependencies.add(moduleQux);
expect( expect(
await traverseDependencies(['/foo'], dependencyGraph, {}, edges), getPaths(await traverseDependencies(['/foo'], dependencyGraph, {}, edges)),
).toEqual({ ).toEqual({
added: new Set(['/qux']), added: new Set(['/foo', '/qux']),
deleted: new Set(['/bar', '/baz']), deleted: new Set(['/bar', '/baz']),
}); });
}); });
@ -159,19 +177,20 @@ describe('edge cases', () => {
const moduleQux = createModule({path: '/qux', name: 'qux'}); const moduleQux = createModule({path: '/qux', name: 'qux'});
mockedDependencyTree.set(moduleFoo.path, [moduleQux, moduleBar]); mockedDependencyTree.set(moduleFoo.path, [moduleQux, moduleBar]);
mockedDependencies.add(moduleQux); mockedDependencies.add(moduleQux);
mockedDependencies.delete(moduleBaz);
// Call traverseDependencies with /foo, /qux and /baz, simulating that the // Call traverseDependencies with /foo, /qux and /baz, simulating that the
// user has modified the 3 files. // user has modified the 3 files.
expect( expect(
await traverseDependencies( getPaths(
['/foo', '/qux', '/baz'], await traverseDependencies(
dependencyGraph, ['/foo', '/qux', '/baz'],
{}, dependencyGraph,
edges, {},
edges,
),
), ),
).toEqual({ ).toEqual({
added: new Set(['/qux']), added: new Set(['/foo', '/qux']),
deleted: new Set(['/baz']), deleted: new Set(['/baz']),
}); });
}); });
@ -193,9 +212,11 @@ describe('edge cases', () => {
// Call traverseDependencies with /foo, /qux and /baz, simulating that the // Call traverseDependencies with /foo, /qux and /baz, simulating that the
// user has modified the 3 files. // user has modified the 3 files.
expect( expect(
await traverseDependencies(['/bundle'], dependencyGraph, {}, edges), getPaths(
await traverseDependencies(['/bundle'], dependencyGraph, {}, edges),
),
).toEqual({ ).toEqual({
added: new Set(['/foo-renamed']), added: new Set(['/bundle', '/foo-renamed']),
deleted: new Set(['/foo']), deleted: new Set(['/foo']),
}); });
}); });
@ -205,13 +226,19 @@ describe('edge cases', () => {
await initialTraverseDependencies('/bundle', dependencyGraph, {}, edges); await initialTraverseDependencies('/bundle', dependencyGraph, {}, edges);
mockedDependencyTree.set(moduleFoo.path, [moduleBar]); mockedDependencyTree.set(moduleFoo.path, [moduleBar]);
mockedDependencies.delete(moduleBaz);
// Modify /baz, rename it to /qux and modify it again. // Modify /baz, rename it to /qux and modify it again.
expect( expect(
await traverseDependencies(['/baz', '/foo'], dependencyGraph, {}, edges), getPaths(
await traverseDependencies(
['/baz', '/foo'],
dependencyGraph,
{},
edges,
),
),
).toEqual({ ).toEqual({
added: new Set(), added: new Set(['/foo']),
deleted: new Set(['/baz']), deleted: new Set(['/baz']),
}); });
}); });
@ -227,9 +254,11 @@ describe('edge cases', () => {
// Modify /baz, rename it to /qux and modify it again. // Modify /baz, rename it to /qux and modify it again.
expect( expect(
await traverseDependencies(['/foo'], dependencyGraph, {}, edges), getPaths(
await traverseDependencies(['/foo'], dependencyGraph, {}, edges),
),
).toEqual({ ).toEqual({
added: new Set(['/baz-moved']), added: new Set(['/foo', '/baz-moved']),
deleted: new Set(['/baz']), deleted: new Set(['/baz']),
}); });
}); });
@ -273,14 +302,16 @@ describe('edge cases', () => {
async function assertOrder() { async function assertOrder() {
expect( expect(
Array.from( Array.from(
(await initialTraverseDependencies( getPaths(
'/bundle', await initialTraverseDependencies(
dependencyGraph, '/bundle',
{}, dependencyGraph,
new Map(), {},
)).added, new Map(),
),
).added,
), ),
).toEqual(['/foo', '/baz', '/bar']); ).toEqual(['/bundle', '/foo', '/baz', '/bar']);
} }
// Create a dependency tree where moduleBaz has two inverse dependencies. // Create a dependency tree where moduleBaz has two inverse dependencies.
@ -300,7 +331,10 @@ describe('edge cases', () => {
}); });
it('should simplify inlineRequires transform option', async () => { 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 edges = new Map();
const transformOptions = { const transformOptions = {
@ -318,14 +352,12 @@ describe('edge cases', () => {
edges, edges,
); );
expect(dependencyGraph.getShallowDependencies.mock.calls).toEqual([ expect(entryModule.read).toHaveBeenCalledWith({inlineRequires: true});
['/bundle', {inlineRequires: true}], expect(moduleFoo.read).toHaveBeenCalledWith({inlineRequires: true});
['/foo', {inlineRequires: true}], expect(moduleBar.read).toHaveBeenCalledWith({inlineRequires: true});
['/bar', {inlineRequires: true}], expect(moduleBaz.read).toHaveBeenCalledWith({inlineRequires: false});
['/baz', {inlineRequires: false}],
]);
dependencyGraph.getShallowDependencies.mockClear(); moduleFoo.read.mockClear();
await traverseDependencies( await traverseDependencies(
['/foo'], ['/foo'],
@ -334,8 +366,6 @@ describe('edge cases', () => {
edges, edges,
); );
expect(dependencyGraph.getShallowDependencies.mock.calls).toEqual([ expect(moduleFoo.read).toHaveBeenCalledWith({inlineRequires: true});
['/foo', {inlineRequires: true}],
]);
}); });
}); });

View File

@ -14,16 +14,26 @@ const removeInlineRequiresBlacklistFromOptions = require('../lib/removeInlineReq
import type {Options as JSTransformerOptions} from '../JSTransformer/worker'; import type {Options as JSTransformerOptions} from '../JSTransformer/worker';
import type DependencyGraph from '../node-haste/DependencyGraph'; 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 = {| export type DependencyEdge = {|
dependencies: Map<string, string>, dependencies: Map<string, string>,
inverseDependencies: Set<string>, inverseDependencies: Set<string>,
path: string, path: string,
output: {
code: string,
map: Array<MetroSourceMapSegmentTuple>,
source: string,
type: DependencyType,
},
|}; |};
export type DependencyEdges = Map<string, DependencyEdge>; export type DependencyEdges = Map<string, DependencyEdge>;
type Result = {added: Set<string>, deleted: Set<string>}; type Result = {added: Map<string, DependencyEdge>, deleted: Set<string>};
/** /**
* Dependency Traversal logic for the Delta Bundler. This method calculates * 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(); const deleted = new Set();
for (const change of changes) { for (const change of changes) {
for (const path of change.added) { for (const [path, edge] of change.added) {
added.add(path); added.set(path, edge);
} }
for (const path of change.deleted) { for (const path of change.deleted) {
// If a path has been marked both as added and deleted, it means that this // If a path has been marked both as added and deleted, it means that this
@ -90,8 +100,8 @@ async function initialTraverseDependencies(
transformOptions: JSTransformerOptions, transformOptions: JSTransformerOptions,
edges: DependencyEdges, edges: DependencyEdges,
onProgress?: (numProcessed: number, total: number) => mixed = () => {}, onProgress?: (numProcessed: number, total: number) => mixed = () => {},
) { ): Promise<Result> {
createEdge(path, edges); createEdge(dependencyGraph.getModuleForPath(path), edges);
return await traverseDependenciesForSingleFile( return await traverseDependenciesForSingleFile(
path, path,
@ -113,13 +123,18 @@ async function traverseDependenciesForSingleFile(
// If the passed edge does not exist does not exist in the graph, ignore it. // If the passed edge does not exist does not exist in the graph, ignore it.
if (!edge) { if (!edge) {
return {added: new Set(), deleted: new Set()}; return {added: new Map(), deleted: new Set()};
} }
const shallow = await dependencyGraph.getShallowDependencies( const result = await dependencyGraph
path, .getModuleForPath(path)
removeInlineRequiresBlacklistFromOptions(path, transformOptions), .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 // Get the absolute path of all sub-dependencies (some of them could have been
// moved but maintain the same relative path). // moved but maintain the same relative path).
@ -132,8 +147,6 @@ async function traverseDependenciesForSingleFile(
const previousDependencies = new Set(edge.dependencies.values()); const previousDependencies = new Set(edge.dependencies.values());
const nonNullEdge = edge;
let numProcessed = 0; let numProcessed = 0;
let total = 1; let total = 1;
onProgress(numProcessed, total); onProgress(numProcessed, total);
@ -141,7 +154,7 @@ async function traverseDependenciesForSingleFile(
const deleted = Array.from(edge.dependencies.entries()) const deleted = Array.from(edge.dependencies.entries())
.map(([relativePath, absolutePath]) => { .map(([relativePath, absolutePath]) => {
if (!currentDependencies.has(absolutePath)) { if (!currentDependencies.has(absolutePath)) {
return removeDependency(nonNullEdge, relativePath, edges); return removeDependency(edge, relativePath, edges);
} else { } else {
return undefined; return undefined;
} }
@ -151,15 +164,15 @@ async function traverseDependenciesForSingleFile(
// Check all the module dependencies and start traversing the tree from each // 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 // added and removed dependency, to get all the modules that have to be added
// and removed from the dependency graph. // and removed from the dependency graph.
const added = await Promise.all( const addedDependencies = await Promise.all(
Array.from(currentDependencies).map( Array.from(currentDependencies).map(
async ([absolutePath, relativePath]) => { async ([absolutePath, relativePath]) => {
if (previousDependencies.has(absolutePath)) { if (previousDependencies.has(absolutePath)) {
return new Set(); return new Map();
} }
return await addDependency( return await addDependency(
nonNullEdge, edge,
relativePath, relativePath,
dependencyGraph, dependencyGraph,
transformOptions, transformOptions,
@ -177,11 +190,13 @@ async function traverseDependenciesForSingleFile(
), ),
); );
const added = [new Map([[edge.path, edge]])].concat(addedDependencies);
numProcessed++; numProcessed++;
onProgress(numProcessed, total); onProgress(numProcessed, total);
return { return {
added: flatten(reorderDependencies(added, edges)), added: flattenMap(reorderDependencies(added, edges)),
deleted: flatten(deleted), deleted: flatten(deleted),
}; };
} }
@ -194,7 +209,7 @@ async function addDependency(
edges: DependencyEdges, edges: DependencyEdges,
onDependencyAdd: () => mixed, onDependencyAdd: () => mixed,
onDependencyAdded: () => mixed, onDependencyAdded: () => mixed,
): Promise<Set<string>> { ): Promise<Map<string, DependencyEdge>> {
const parentModule = dependencyGraph.getModuleForPath(parentEdge.path); const parentModule = dependencyGraph.getModuleForPath(parentEdge.path);
const module = dependencyGraph.resolveDependency( const module = dependencyGraph.resolveDependency(
parentModule, parentModule,
@ -205,35 +220,36 @@ async function addDependency(
// Update the parent edge to keep track of the new dependency. // Update the parent edge to keep track of the new dependency.
parentEdge.dependencies.set(relativePath, module.path); 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. // The new dependency was already in the graph, we don't need to do anything.
if (dependencyEdge) { if (existingEdge) {
dependencyEdge.inverseDependencies.add(parentEdge.path); existingEdge.inverseDependencies.add(parentEdge.path);
return new Set(); return new Map();
} }
onDependencyAdd(); onDependencyAdd();
// Create the new edge and traverse all its subdependencies, looking for new // Create the new edge and traverse all its subdependencies, looking for new
// subdependencies recursively. // subdependencies recursively.
dependencyEdge = createEdge(module.path, edges); const edge = createEdge(module, edges);
dependencyEdge.inverseDependencies.add(parentEdge.path); edge.inverseDependencies.add(parentEdge.path);
const addedDependencies = new Set([dependencyEdge.path]); const addedDependencies = new Map([[edge.path, edge]]);
const shallowDeps = await dependencyGraph.getShallowDependencies( const result = await module.read(
dependencyEdge.path, removeInlineRequiresBlacklistFromOptions(edge.path, transformOptions),
removeInlineRequiresBlacklistFromOptions(module.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( const added = await Promise.all(
shallowDeps.map(dep => result.dependencies.map(dep =>
addDependency( addDependency(
nonNullDependencyEdge, edge,
dep, dep,
dependencyGraph, dependencyGraph,
transformOptions, transformOptions,
@ -244,8 +260,8 @@ async function addDependency(
), ),
); );
for (const newDependency of flatten(added)) { for (const [newDepPath, newDepEdge] of flattenMap(added)) {
addedDependencies.add(newDependency); addedDependencies.set(newDepPath, newDepEdge);
} }
onDependencyAdded(); onDependencyAdded();
@ -294,17 +310,35 @@ function removeDependency(
return removedDependencies; return removedDependencies;
} }
function createEdge(path: string, edges: DependencyEdges): DependencyEdge { function createEdge(module: Module, edges: DependencyEdges): DependencyEdge {
const edge = { const edge = {
dependencies: new Map(), dependencies: new Map(),
inverseDependencies: new Set(), 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; 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) { function destroyEdge(edge: DependencyEdge, edges: DependencyEdges) {
edges.delete(edge.path); edges.delete(edge.path);
} }
@ -347,29 +381,36 @@ function resolveDependencies(
* guarantee the same order between runs. * guarantee the same order between runs.
*/ */
function reorderDependencies( function reorderDependencies(
dependencies: Array<Set<string>>, dependencies: Array<Map<string, DependencyEdge>>,
edges: DependencyEdges, edges: DependencyEdges,
): Array<Set<string>> { ): Array<Map<string, DependencyEdge>> {
const flatDependencies = flatten(dependencies); const flatDependencies = flattenMap(dependencies);
return dependencies.map(dependencies => return dependencies.map(dependencies => {
reorderDependency(Array.from(dependencies)[0], flatDependencies, edges), if (dependencies.size === 0) {
); return new Map();
}
return reorderDependency(
Array.from(dependencies)[0][0],
flatDependencies,
edges,
);
});
} }
function reorderDependency( function reorderDependency(
path: string, path: string,
dependencies: Set<string>, dependencies: Map<string, DependencyEdge>,
edges: DependencyEdges, edges: DependencyEdges,
orderedDependencies?: Set<string> = new Set(), orderedDependencies?: Map<string, DependencyEdge> = new Map(),
): Set<string> { ): Map<string, DependencyEdge> {
const edge = edges.get(path); const edge = edges.get(path);
if (!edge || !dependencies.has(path) || orderedDependencies.has(path)) { if (!edge || !dependencies.has(path) || orderedDependencies.has(path)) {
return orderedDependencies; return orderedDependencies;
} }
orderedDependencies.add(path); orderedDependencies.set(path, edge);
edge.dependencies.forEach(path => edge.dependencies.forEach(path =>
reorderDependency(path, dependencies, edges, orderedDependencies), reorderDependency(path, dependencies, edges, orderedDependencies),
@ -390,6 +431,18 @@ function flatten<T>(input: Iterable<Iterable<T>>): Set<T> {
return output; return output;
} }
function flattenMap<K, V>(input: Iterable<Map<K, V>>): Map<K, V> {
const output = new Map();
for (const items of input) {
for (const [key, value] of items.entries()) {
output.set(key, value);
}
}
return output;
}
module.exports = { module.exports = {
initialTraverseDependencies, initialTraverseDependencies,
traverseDependencies, traverseDependencies,

View File

@ -22,6 +22,7 @@ const fs = require('fs');
const isAbsolutePath = require('absolute-path'); const isAbsolutePath = require('absolute-path');
const parsePlatformFilePath = require('./lib/parsePlatformFilePath'); const parsePlatformFilePath = require('./lib/parsePlatformFilePath');
const path = require('path'); const path = require('path');
const toLocalPath = require('../node-haste/lib/toLocalPath');
const util = require('util'); const util = require('util');
const {ModuleResolver} = require('./DependencyGraph/ModuleResolution'); const {ModuleResolver} = require('./DependencyGraph/ModuleResolution');
@ -274,6 +275,16 @@ class DependencyGraph extends EventEmitter {
return platform; 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) { getAbsolutePath(filePath: string) {
if (isAbsolutePath(filePath)) { if (isAbsolutePath(filePath)) {
return path.resolve(filePath); return path.resolve(filePath);

View File

@ -14,5 +14,6 @@
export type HasteFS = { export type HasteFS = {
exists(filePath: string): boolean, exists(filePath: string): boolean,
getAllFiles(): Array<string>, getAllFiles(): Array<string>,
getModuleName(filePath: string): ?string,
matchFiles(pattern: RegExp | string): Array<string>, matchFiles(pattern: RegExp | string): Array<string>,
}; };