Remove ModuleTransport/ResolutionResponse modules and lots of logic from Resolver/ResolutionRequest

Reviewed By: davidaurelio

Differential Revision: D6284728

fbshipit-source-id: 53d27cd65b8c96ed6d6872eb16a9b5d1e14db85e
This commit is contained in:
Rafael Oleza 2017-11-13 16:23:55 -08:00 committed by Facebook Github Bot
parent 860dcc4867
commit e1b5fe79fa
11 changed files with 13 additions and 687 deletions

View File

@ -18,7 +18,6 @@ const debug = require('debug')('Metro:Bundler');
const fs = require('fs');
const Transformer = require('../JSTransformer');
const Resolver = require('../Resolver');
const ModuleTransport = require('../lib/ModuleTransport');
const path = require('path');
const defaults = require('../defaults');
const createModuleIdFactory = require('../lib/createModuleIdFactory');
@ -240,12 +239,6 @@ class Bundler {
);
}
getModuleForPath(entryFile: string): Promise<Module> {
return this._resolverPromise.then(resolver =>
resolver.getModuleForPath(entryFile),
);
}
async generateAssetObjAndCode(
module: Module,
assetPlugins: Array<string>,

View File

@ -15,7 +15,6 @@
const Generator = require('./Generator');
const SourceMap = require('source-map');
import type ModuleTransport from '../../lib/ModuleTransport';
import type {MappingsMap, RawMappings} from '../../lib/SourceMap';
import type {RawMapping as BabelRawMapping} from 'babel-generator';

View File

@ -322,20 +322,22 @@ class DeltaTransformer extends EventEmitter {
}
async _getAppend(dependencyEdges: DependencyEdges): Promise<DeltaEntries> {
const dependencyGraph = this._resolver.getDependencyGraph();
// Get the absolute path of the entry file, in order to be able to get the
// actual correspondant module (and its moduleId) to be able to add the
// correct require(); call at the very end of the bundle.
const absPath = this._resolver
.getDependencyGraph()
.getAbsolutePath(this._bundleOptions.entryFile);
const entryPointModule = this._resolver.getModuleForPath(absPath);
const absPath = dependencyGraph.getAbsolutePath(
this._bundleOptions.entryFile,
);
const entryPointModule = dependencyGraph.getModuleForPath(absPath);
// First, get the modules correspondant to all the module names defined in
// the `runBeforeMainModule` config variable. Then, append the entry point
// module so the last thing that gets required is the entry point.
const append = new Map(
this._bundleOptions.runBeforeMainModule
.map(path => this._resolver.getModuleForPath(path))
.map(path => dependencyGraph.getModuleForPath(path))
.concat(entryPointModule)
.filter(module => dependencyEdges.has(module.path))
.map(this._getModuleId)

View File

@ -96,94 +96,6 @@ describe('Resolver', function() {
return polyfill;
}
describe('getDependencies', function() {
it('forwards transform options to the dependency graph', function() {
expect.assertions(1);
const transformOptions = {arbitrary: 'options'};
const platform = 'ios';
const entry = '/root/index.js';
DependencyGraph.prototype.getDependencies.mockImplementation(() =>
Promise.reject(),
);
return Resolver.load({projectRoot: '/root'})
.then(r => r.getDependencies(entry, {platform}, transformOptions))
.catch(() => {
expect(DependencyGraph.prototype.getDependencies).toBeCalledWith({
entryPath: entry,
platform,
options: transformOptions,
recursive: true,
});
});
});
it('passes custom platforms to the dependency graph', function() {
expect.assertions(1);
return Resolver.load({
projectRoot: '/root',
platforms: ['ios', 'windows', 'vr'],
}).then(() => {
const platforms = DependencyGraph.mock.calls[0][0].platforms;
expect(Array.from(platforms)).toEqual(['ios', 'windows', 'vr']);
});
});
it('should pass in more polyfills when prependPolyfills is true', function() {
expect.assertions(3);
var module = createModule('index');
var deps = [module];
var depResolverPromise = Resolver.load({
getPolyfills: () => ['custom-polyfill-1', 'custom-polyfill-2'],
projectRoot: '/root',
});
DependencyGraph.prototype.getDependencies.mockImplementation(function() {
return Promise.resolve(
new ResolutionResponseMock({
dependencies: deps,
mainModuleId: 'index',
}),
);
});
return depResolverPromise
.then(r =>
r.getDependencies(
'/root/index.js',
{dev: false, prependPolyfills: true},
undefined,
undefined,
createGetModuleId(),
),
)
.then(result => {
expect(result.mainModuleId).toEqual('index');
const calls = DependencyGraph.prototype.createPolyfill.mock.calls;
const callPolyfill1 = calls[result.dependencies.length - 3];
const callPolyfill2 = calls[result.dependencies.length - 2];
expect(callPolyfill1).toEqual([
{
file: 'custom-polyfill-1',
id: 'custom-polyfill-1',
dependencies: [],
},
]);
expect(callPolyfill2).toEqual([
{
file: 'custom-polyfill-2',
id: 'custom-polyfill-2',
dependencies: ['custom-polyfill-1'],
},
]);
});
});
});
describe('wrapModule', function() {
let depResolver;
beforeEach(() => {

View File

@ -23,7 +23,6 @@ const {
} = require('../Bundler/source-map');
const pathJoin = require('path').join;
import type ResolutionResponse from '../node-haste/DependencyGraph/ResolutionResponse';
import type Module, {HasteImpl, TransformCode} from '../node-haste/Module';
import type {MappingsMap, CompactRawMappings} from '../lib/SourceMap';
import type {PostMinifyProcess} from '../Bundler';
@ -107,52 +106,6 @@ class Resolver {
return new Resolver(opts, depGraph);
}
getShallowDependencies(
entryFile: string,
transformOptions: JSTransformerOptions,
): Promise<Array<string>> {
return this._depGraph.getShallowDependencies(entryFile, transformOptions);
}
getModuleForPath(entryFile: string): Module {
return this._depGraph.getModuleForPath(entryFile);
}
async getDependencies<T: ContainsTransformerOptions>(
entryPath: string,
options: {
platform: ?string,
recursive?: boolean,
prependPolyfills: boolean,
},
bundlingOptions: T,
onProgress?: ?(finishedModules: number, totalModules: number) => mixed,
getModuleId: mixed,
): Promise<ResolutionResponse<Module, T>> {
const {platform, recursive = true, prependPolyfills} = options;
const resolutionResponse: ResolutionResponse<
Module,
T,
> = await this._depGraph.getDependencies({
entryPath,
platform,
options: bundlingOptions,
recursive,
onProgress,
});
if (prependPolyfills) {
this._getPolyfillDependencies(platform)
.reverse()
.forEach(polyfill => resolutionResponse.prependDependency(polyfill));
}
/* $FlowFixMe: monkey patching */
resolutionResponse.getModuleId = getModuleId;
return resolutionResponse.finalize();
}
getModuleSystemDependencies({dev = true}: {dev?: boolean}): Array<Module> {
const prelude = dev
? pathJoin(__dirname, 'polyfills/prelude_dev.js')
@ -169,20 +122,6 @@ class Resolver {
);
}
_getPolyfillDependencies(platform: ?string): Array<Module> {
const polyfillModuleNames = this._getPolyfills({platform}).concat(
this._polyfillModuleNames,
);
return polyfillModuleNames.map((polyfillModuleName, idx) =>
this._depGraph.createPolyfill({
file: polyfillModuleName,
id: polyfillModuleName,
dependencies: polyfillModuleNames.slice(0, idx),
}),
);
}
resolveRequires(
module: Module,
getModuleId: ({path: string}) => number,

View File

@ -1,79 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
'use strict';
import type {RawMapping} from '../Bundler/source-map';
import type Module from '../node-haste/Module';
import type {SourceMap} from './SourceMap';
export type SourceMapOrMappings = SourceMap | Array<RawMapping>;
type Metadata = {
dependencies?: ?Array<string>,
dependencyPairs?: Array<[string, Module]>,
preloaded: ?boolean,
dependencyOffsets?: ?Array<number>,
};
class ModuleTransport {
name: string;
id: number;
code: string;
sourceCode: string;
sourcePath: string;
virtual: boolean;
meta: ?Metadata;
polyfill: boolean;
map: ?SourceMapOrMappings;
constructor(data: {
name: string,
id: number,
code: string,
sourceCode: string,
sourcePath: string,
virtual?: boolean,
meta?: ?Metadata,
polyfill?: boolean,
map?: ?SourceMapOrMappings,
}) {
this.name = data.name;
assertExists(data, 'id');
this.id = data.id;
assertExists(data, 'code');
this.code = data.code;
assertExists(data, 'sourceCode');
this.sourceCode = data.sourceCode;
assertExists(data, 'sourcePath');
this.sourcePath = data.sourcePath;
this.virtual = !!data.virtual;
this.meta = data.meta;
this.polyfill = !!data.polyfill;
this.map = data.map;
Object.freeze(this);
}
}
module.exports = ModuleTransport;
function assertExists(obj, field) {
if (obj[field] == null) {
throw new Error('Modules must have `' + field + '`');
}
}

View File

@ -19,7 +19,6 @@ const JestHasteMap = require('jest-haste-map');
const Module = require('./Module');
const ModuleCache = require('./ModuleCache');
const ResolutionRequest = require('./DependencyGraph/ResolutionRequest');
const ResolutionResponse = require('./DependencyGraph/ResolutionResponse');
const fs = require('fs');
const isAbsolutePath = require('absolute-path');
@ -233,10 +232,6 @@ class DependencyGraph extends EventEmitter {
return this._moduleCache.getModule(entryFile);
}
getAllModules() {
return Promise.resolve(this._moduleCache.getAllModules());
}
resolveDependency(
fromModule: Module,
toModuleName: string,
@ -253,45 +248,6 @@ class DependencyGraph extends EventEmitter {
return req.resolveDependency(fromModule, toModuleName);
}
getDependencies<T: {+transformer: JSTransformerOptions}>({
entryPath,
options,
platform,
onProgress,
recursive = true,
}: {
entryPath: string,
options: T,
platform: ?string,
onProgress?: ?(finishedModules: number, totalModules: number) => mixed,
recursive: boolean,
}): Promise<ResolutionResponse<Module, T>> {
platform = this._getRequestPlatform(entryPath, platform);
const absPath = this.getAbsolutePath(entryPath);
const req = new ResolutionRequest({
moduleResolver: this._moduleResolver,
entryPath: absPath,
helpers: this._helpers,
platform: platform != null ? platform : null,
moduleCache: this._moduleCache,
});
const response = new ResolutionResponse(options);
return req
.getOrderedDependencies({
response,
transformOptions: options.transformer,
onProgress,
recursive,
})
.then(() => response);
}
matchFilesByPattern(pattern: RegExp) {
return Promise.resolve(this._hasteFS.matchFiles(pattern));
}
_doesFileExist = (filePath: string): boolean => {
return this._hasteFS.exists(filePath);
};

View File

@ -12,23 +12,19 @@
'use strict';
const AsyncTaskGroup = require('../lib/AsyncTaskGroup');
const MapWithDefaults = require('../lib/MapWithDefaults');
const ModuleResolution = require('./ModuleResolution');
const debug = require('debug')('Metro:DependencyGraph');
const isAbsolutePath = require('absolute-path');
const path = require('path');
const {DuplicateHasteCandidatesError} = require('jest-haste-map').ModuleMap;
import type DependencyGraphHelpers from './DependencyGraphHelpers';
import type ResolutionResponse from './ResolutionResponse';
import type {Options as TransformWorkerOptions} from '../../JSTransformer/worker';
import type {ReadResult, CachedReadResult} from '../Module';
import type {ModuleResolver} from './ModuleResolution';
const {UnableToResolveError, isRelativeImport} = ModuleResolution;
const {isRelativeImport} = ModuleResolution;
export type Packageish = {
isHaste(): boolean,
@ -126,239 +122,6 @@ class ResolutionRequest<TModule: Moduleish, TPackage: Packageish> {
}
}
resolveModuleDependencies(
module: TModule,
dependencyNames: $ReadOnlyArray<string>,
): [$ReadOnlyArray<string>, $ReadOnlyArray<TModule>] {
const dependencies = dependencyNames.map(name =>
this.resolveDependency(module, name),
);
return [dependencyNames, dependencies];
}
getOrderedDependencies<T>({
response,
transformOptions,
onProgress,
recursive = true,
}: {
response: ResolutionResponse<TModule, T>,
transformOptions: TransformWorkerOptions,
onProgress?: ?(finishedModules: number, totalModules: number) => mixed,
recursive: boolean,
}): Promise<void> {
const entry = this._options.moduleCache.getModule(this._options.entryPath);
response.pushDependency(entry);
let totalModules = 1;
let finishedModules = 0;
let preprocessedModuleCount = 1;
if (recursive) {
this._preprocessPotentialDependencies(transformOptions, entry, count => {
if (count + 1 <= preprocessedModuleCount) {
return;
}
preprocessedModuleCount = count + 1;
if (onProgress != null) {
onProgress(finishedModules, preprocessedModuleCount);
}
});
}
const resolveDependencies = (module: TModule) =>
Promise.resolve().then(() => {
const cached = module.readCached(transformOptions);
if (cached.result != null) {
return this.resolveModuleDependencies(
module,
cached.result.dependencies,
);
}
return module
.readFresh(transformOptions)
.then(({dependencies}) =>
this.resolveModuleDependencies(module, dependencies),
);
});
const collectedDependencies: MapWithDefaults<
TModule,
Promise<Array<TModule>>,
> = new MapWithDefaults(module => collect(module));
const crawlDependencies = (mod, [depNames, dependencies]) => {
const filteredPairs = [];
dependencies.forEach((modDep, i) => {
const name = depNames[i];
if (modDep == null) {
debug(
'WARNING: Cannot find required module `%s` from module `%s`',
name,
mod.path,
);
return false;
}
return filteredPairs.push([name, modDep]);
});
response.setResolvedDependencyPairs(mod, filteredPairs);
const dependencyModules = filteredPairs.map(([, m]) => m);
const newDependencies = dependencyModules.filter(
m => !collectedDependencies.has(m),
);
if (onProgress) {
finishedModules += 1;
totalModules += newDependencies.length;
onProgress(
finishedModules,
Math.max(totalModules, preprocessedModuleCount),
);
}
if (recursive) {
// doesn't block the return of this function invocation, but defers
// the resulution of collectionsInProgress.done.then(...)
dependencyModules.forEach(dependency =>
collectedDependencies.get(dependency),
);
}
return dependencyModules;
};
const collectionsInProgress = new AsyncTaskGroup();
function collect(module) {
collectionsInProgress.start(module);
const result = resolveDependencies(module).then(deps =>
crawlDependencies(module, deps),
);
const end = () => collectionsInProgress.end(module);
result.then(end, end);
return result;
}
function resolveKeyWithPromise(
[key: TModule, promise: Promise<Array<TModule>>],
): Promise<[TModule, Array<TModule>]> {
return promise.then(value => [key, value]);
}
return Promise.all([
// kicks off recursive dependency discovery, but doesn't block until it's
// done
collectedDependencies.get(entry),
// resolves when there are no more modules resolving dependencies
collectionsInProgress.done,
])
.then(([rootDependencies]) => {
return Promise.all(
Array.from(collectedDependencies, resolveKeyWithPromise),
).then(moduleToDependenciesPairs => [
rootDependencies,
new MapWithDefaults(() => [], moduleToDependenciesPairs),
]);
})
.then(([rootDependencies, moduleDependencies]) => {
// serialize dependencies, and make sure that every single one is only
// included once
const seen = new Set([entry]);
function traverse(dependencies) {
dependencies.forEach(dependency => {
if (seen.has(dependency)) {
return;
}
seen.add(dependency);
response.pushDependency(dependency);
traverse(moduleDependencies.get(dependency));
});
}
traverse(rootDependencies);
});
}
/**
* This synchronously look at all the specified modules and recursively kicks
* off global cache fetching or transforming (via `readFresh`). This is a hack
* that workaround the current structure, because we could do better. First
* off, the algorithm that resolves dependencies recursively should be
* synchronous itself until it cannot progress anymore (and needs to call
* `readFresh`), so that this algo would be integrated into it.
*/
_preprocessPotentialDependencies(
transformOptions: TransformWorkerOptions,
module: TModule,
onProgress: (moduleCount: number) => mixed,
): void {
const visitedModulePaths = new Set();
const pendingBatches = [
this.preprocessModule(transformOptions, module, visitedModulePaths),
];
onProgress(visitedModulePaths.size);
while (pendingBatches.length > 0) {
const dependencyModules = pendingBatches.pop();
while (dependencyModules.length > 0) {
const dependencyModule = dependencyModules.pop();
const deps = this.preprocessModule(
transformOptions,
dependencyModule,
visitedModulePaths,
);
pendingBatches.push(deps);
onProgress(visitedModulePaths.size);
}
}
}
preprocessModule(
transformOptions: TransformWorkerOptions,
module: TModule,
visitedModulePaths: Set<string>,
): Array<TModule> {
const cached = module.readCached(transformOptions);
if (cached.result == null) {
module.readFresh(transformOptions).catch(error => {
/* ignore errors, they'll be handled later if the dependency is actually
* not obsolete, and required from somewhere */
});
}
const dependencies =
cached.result != null
? cached.result.dependencies
: cached.outdatedDependencies;
return this.tryResolveModuleDependencies(
module,
dependencies,
visitedModulePaths,
);
}
tryResolveModuleDependencies(
module: TModule,
dependencyNames: $ReadOnlyArray<string>,
visitedModulePaths: Set<string>,
): Array<TModule> {
const result = [];
for (let i = 0; i < dependencyNames.length; ++i) {
try {
const depModule = this.resolveDependency(module, dependencyNames[i]);
if (!visitedModulePaths.has(depModule.path)) {
visitedModulePaths.add(depModule.path);
result.push(depModule);
}
} catch (error) {
if (!(error instanceof UnableToResolveError)) {
throw error;
}
}
}
return result;
}
_resetResolutionCache() {
this._immediateResolutionCache = Object.create(null);
}

View File

@ -1,126 +0,0 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
'use strict';
const NO_OPTIONS = {};
class ResolutionResponse<TModule: {hash(): string}, TOptions> {
dependencies: Array<TModule>;
mainModuleId: ?(number | string);
mocks: mixed;
numPrependedDependencies: number;
options: TOptions;
// This is monkey-patched from Resolver.
getModuleId: ?() => number;
_mappings: {[hash: string]: Array<[string, TModule]>, __proto__: null};
_finalized: boolean;
_mainModule: ?TModule;
constructor(options: TOptions) {
this.dependencies = [];
this.mainModuleId = null;
this.mocks = null;
this.numPrependedDependencies = 0;
this.options = options;
this._mappings = Object.create(null);
this._finalized = false;
}
copy(properties: {
dependencies?: Array<TModule>,
mainModuleId?: number,
mocks?: mixed,
}): ResolutionResponse<TModule, TOptions> {
const {
dependencies = this.dependencies,
mainModuleId = this.mainModuleId,
mocks = this.mocks,
} = properties;
const numPrependedDependencies =
dependencies === this.dependencies ? this.numPrependedDependencies : 0;
/* $FlowFixMe: Flow doesn't like Object.assign on class-made objects. */
return Object.assign(new this.constructor(this.options), this, {
dependencies,
mainModuleId,
mocks,
numPrependedDependencies,
});
}
_assertNotFinalized() {
if (this._finalized) {
throw new Error('Attempted to mutate finalized response.');
}
}
_assertFinalized() {
if (!this._finalized) {
throw new Error('Attempted to access unfinalized response.');
}
}
finalize(): Promise<this> {
return Promise.resolve().then(() => {
/* $FlowFixMe: _mainModule is not initialized in the constructor. */
this.mainModuleId = this._mainModule.getName();
this._finalized = true;
return this;
});
}
pushDependency(module: TModule) {
this._assertNotFinalized();
if (this.dependencies.length === 0) {
this._mainModule = module;
}
this.dependencies.push(module);
}
prependDependency(module: TModule) {
this._assertNotFinalized();
this.dependencies.unshift(module);
this.numPrependedDependencies += 1;
}
setResolvedDependencyPairs(
module: TModule,
pairs: Array<[string, TModule]>,
options: {ignoreFinalized?: boolean} = NO_OPTIONS,
) {
if (!options.ignoreFinalized) {
this._assertNotFinalized();
}
const hash = module.hash();
if (this._mappings[hash] == null) {
this._mappings[hash] = pairs;
}
}
setMocks(mocks: mixed) {
this.mocks = mocks;
}
getResolvedDependencyPairs(
module: TModule,
): $ReadOnlyArray<[string, TModule]> {
this._assertFinalized();
return this._mappings[module.hash()] || [];
}
}
module.exports = ResolutionResponse;

View File

@ -1,37 +0,0 @@
/**
* Copyright (c) 2016-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
'use strict';
module.exports = class AsyncTaskGroup<TTaskHandle> {
_runningTasks: Set<TTaskHandle>;
_resolve: ?() => void;
done: Promise<void>;
constructor() {
this._runningTasks = new Set();
this._resolve = null;
this.done = new Promise(resolve => (this._resolve = resolve));
}
start(taskHandle: TTaskHandle) {
this._runningTasks.add(taskHandle);
}
end(taskHandle: TTaskHandle) {
const runningTasks = this._runningTasks;
if (runningTasks.delete(taskHandle) && runningTasks.size === 0) {
/* $FlowFixMe: could be null */
this._resolve();
}
}
};

View File

@ -10,7 +10,11 @@
* @format
*/
'use strict';
import type {SourceMapOrMappings} from '../lib/ModuleTransport';
import type {RawMapping} from '../Bundler/source-map';
import type {SourceMap} from '../lib/SourceMap';
type SourceMapOrMappings = SourceMap | Array<RawMapping>;
export type ModuleGroups = {|
groups: Map<number, Set<number>>,