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 {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<string, Module>,
+modified: Map<string, DependencyEdge>,
+deleted: Set<string>,
+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,
};

View File

@ -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<Module>,
modules: Array<DependencyEdge>,
transformOptions: JSTransformerOptions,
dependencyEdges: DependencyEdges,
): Promise<DeltaEntries> {
@ -429,15 +434,34 @@ class DeltaTransformer extends EventEmitter {
);
}
async _transformModule(
async _createEdgeFromScript(
module: Module,
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,
): 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<string>,
+map: Array<MetroSourceMapSegmentTuple>,
+source: string,
}> {
return await module.read(
removeInlineRequiresBlacklistFromOptions(module.path, transformOptions),
);
}
_onFileChange = () => {
this.emit('change');
};

View File

@ -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']);
});

View File

@ -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',

View File

@ -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});
});
});

View File

@ -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<string, string>,
inverseDependencies: Set<string>,
path: string,
output: {
code: string,
map: Array<MetroSourceMapSegmentTuple>,
source: string,
type: DependencyType,
},
|};
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
@ -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<Result> {
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<Set<string>> {
): Promise<Map<string, DependencyEdge>> {
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<Set<string>>,
dependencies: Array<Map<string, DependencyEdge>>,
edges: DependencyEdges,
): Array<Set<string>> {
const flatDependencies = flatten(dependencies);
): Array<Map<string, DependencyEdge>> {
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<string>,
dependencies: Map<string, DependencyEdge>,
edges: DependencyEdges,
orderedDependencies?: Set<string> = new Set(),
): Set<string> {
orderedDependencies?: Map<string, DependencyEdge> = new Map(),
): Map<string, DependencyEdge> {
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<T>(input: Iterable<Iterable<T>>): Set<T> {
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 = {
initialTraverseDependencies,
traverseDependencies,

View File

@ -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);

View File

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