packager: Resolver: make the Resolver construction a Promise

Reviewed By: cpojer

Differential Revision: D4681614

fbshipit-source-id: 5da558280edf300f67042e72c65b272e49351871
This commit is contained in:
Jean Lauliac 2017-03-10 03:46:25 -08:00 committed by Facebook Github Bot
parent 5177a55314
commit 1030aabbc4
7 changed files with 121 additions and 101 deletions

View File

@ -86,6 +86,7 @@ describe('Bundler', function() {
getModuleSystemDependencies, getModuleSystemDependencies,
}; };
}); });
Resolver.load = jest.fn().mockImplementation(opts => Promise.resolve(new Resolver(opts)));
fs.statSync.mockImplementation(function() { fs.statSync.mockImplementation(function() {
return { return {

View File

@ -108,7 +108,7 @@ class Bundler {
_getModuleId: (opts: Module) => number; _getModuleId: (opts: Module) => number;
_cache: Cache; _cache: Cache;
_transformer: Transformer; _transformer: Transformer;
_resolver: Resolver; _resolverPromise: Promise<Resolver>;
_projectRoots: Array<string>; _projectRoots: Array<string>;
_assetServer: AssetServer; _assetServer: AssetServer;
_getTransformOptions: void | GetTransformOptions; _getTransformOptions: void | GetTransformOptions;
@ -169,7 +169,7 @@ class Bundler {
return transformCacheKey + getCacheKey(src, filename, options); return transformCacheKey + getCacheKey(src, filename, options);
}; };
this._resolver = new Resolver({ this._resolverPromise = Resolver.load({
assetExts: opts.assetExts, assetExts: opts.assetExts,
blacklistRE: opts.blacklistRE, blacklistRE: opts.blacklistRE,
cache: this._cache, cache: this._cache,
@ -204,9 +204,9 @@ class Bundler {
this._transformer.kill(); this._transformer.kill();
return Promise.all([ return Promise.all([
this._cache.end(), this._cache.end(),
this.getResolver().getDependencyGraph().then(dependencyGraph => { this._resolverPromise.then(
dependencyGraph.getWatcher().end(); resolver => resolver.getDependencyGraph().getWatcher().end(),
}), ),
]); ]);
} }
@ -215,15 +215,15 @@ class Bundler {
minify: boolean, minify: boolean,
unbundle: boolean, unbundle: boolean,
sourceMapUrl: string, sourceMapUrl: string,
}) { }): Promise<Bundle> {
const {dev, minify, unbundle} = options; const {dev, minify, unbundle} = options;
const moduleSystemDeps = return this._resolverPromise.then(
this._resolver.getModuleSystemDependencies({dev, unbundle}); resolver => resolver.getModuleSystemDependencies({dev, unbundle}),
return this._bundle({ ).then(moduleSystemDeps => this._bundle({
...options, ...options,
bundle: new Bundle({dev, minify, sourceMapUrl: options.sourceMapUrl}), bundle: new Bundle({dev, minify, sourceMapUrl: options.sourceMapUrl}),
moduleSystemDeps, moduleSystemDeps,
}); }));
} }
_sourceHMRURL(platform: ?string, hmrpath: string) { _sourceHMRURL(platform: ?string, hmrpath: string) {
@ -338,11 +338,11 @@ class Bundler {
response: ResolutionResponse, response: ResolutionResponse,
modulesByName: {[name: string]: Module}, modulesByName: {[name: string]: Module},
}) => }) =>
Promise.all( this._resolverPromise.then(resolver => Promise.all(
transformedModules.map(({module, transformed}) => transformedModules.map(({module, transformed}) =>
finalBundle.addModule(this._resolver, response, module, transformed) finalBundle.addModule(resolver, response, module, transformed)
) )
).then(() => { )).then(() => {
const runBeforeMainModuleIds = Array.isArray(runBeforeMainModule) const runBeforeMainModuleIds = Array.isArray(runBeforeMainModule)
? runBeforeMainModule ? runBeforeMainModule
.map(name => modulesByName[name]) .map(name => modulesByName[name])
@ -415,7 +415,9 @@ class Bundler {
}); });
} }
return Promise.resolve(resolutionResponse).then(response => { return Promise.all(
[this._resolverPromise, resolutionResponse],
).then(([resolver, response]) => {
bundle.setRamGroups(response.transformOptions.transform.ramGroups); bundle.setRamGroups(response.transformOptions.transform.ramGroups);
log(createActionEndEntry(transformingFilesLogEntry)); log(createActionEndEntry(transformingFilesLogEntry));
@ -425,7 +427,7 @@ class Bundler {
let entryFilePath; let entryFilePath;
if (response.dependencies.length > 1) { // skip HMR requests if (response.dependencies.length > 1) { // skip HMR requests
const numModuleSystemDependencies = const numModuleSystemDependencies =
this._resolver.getModuleSystemDependencies({dev, unbundle}).length; resolver.getModuleSystemDependencies({dev, unbundle}).length;
const dependencyIndex = const dependencyIndex =
(response.numPrependedDependencies || 0) + numModuleSystemDependencies; (response.numPrependedDependencies || 0) + numModuleSystemDependencies;
@ -501,12 +503,14 @@ class Bundler {
transform: transformSpecificOptions, transform: transformSpecificOptions,
}; };
return this._resolver.getShallowDependencies(entryFile, transformOptions); return this._resolverPromise.then(
resolver => resolver.getShallowDependencies(entryFile, transformOptions),
);
}); });
} }
getModuleForPath(entryFile: string) { getModuleForPath(entryFile: string): Promise<Module> {
return this._resolver.getModuleForPath(entryFile); return this._resolverPromise.then(resolver => resolver.getModuleForPath(entryFile));
} }
getDependencies({ getDependencies({
@ -547,13 +551,13 @@ class Bundler {
transform: transformSpecificOptions, transform: transformSpecificOptions,
}; };
return this._resolver.getDependencies( return this._resolverPromise.then(resolver => resolver.getDependencies(
entryFile, entryFile,
{dev, platform, recursive}, {dev, platform, recursive},
transformOptions, transformOptions,
onProgress, onProgress,
isolateModuleIDs ? createModuleIdFactory() : this._getModuleId, isolateModuleIDs ? createModuleIdFactory() : this._getModuleId,
); ));
}); });
} }
@ -771,8 +775,8 @@ class Bundler {
}); });
} }
getResolver() { getResolver(): Promise<Resolver> {
return this._resolver; return this._resolverPromise;
} }
} }

View File

@ -39,7 +39,9 @@ describe('Resolver', function() {
return polyfill; return polyfill;
}); });
DependencyGraph.load = jest.fn().mockImplementation(opts => Promise.resolve(new DependencyGraph(opts))); DependencyGraph.load = jest.fn().mockImplementation(
opts => Promise.resolve(new DependencyGraph(opts)),
);
DependencyGraph.replacePatterns = require.requireActual('../../node-haste/lib/replacePatterns'); DependencyGraph.replacePatterns = require.requireActual('../../node-haste/lib/replacePatterns');
DependencyGraph.prototype.createPolyfill = jest.fn(); DependencyGraph.prototype.createPolyfill = jest.fn();
DependencyGraph.prototype.getDependencies = jest.fn(); DependencyGraph.prototype.getDependencies = jest.fn();
@ -101,8 +103,8 @@ describe('Resolver', function() {
DependencyGraph.prototype.getDependencies.mockImplementation( DependencyGraph.prototype.getDependencies.mockImplementation(
() => Promise.reject()); () => Promise.reject());
return new Resolver({projectRoot: '/root'}) return Resolver.load({projectRoot: '/root'})
.getDependencies(entry, {platform}, transformOptions) .then(r => r.getDependencies(entry, {platform}, transformOptions))
.catch(() => { .catch(() => {
expect(DependencyGraph.prototype.getDependencies).toBeCalledWith({ expect(DependencyGraph.prototype.getDependencies).toBeCalledWith({
entryPath: entry, entryPath: entry,
@ -114,19 +116,23 @@ describe('Resolver', function() {
}); });
it('passes custom platforms to the dependency graph', function() { it('passes custom platforms to the dependency graph', function() {
new Resolver({ // eslint-disable-line no-new expect.assertions(1);
return Resolver.load({ // eslint-disable-line no-new
projectRoot: '/root', projectRoot: '/root',
platforms: ['ios', 'windows', 'vr'], platforms: ['ios', 'windows', 'vr'],
}).then(() => {
const platforms = DependencyGraph.mock.calls[0][0].platforms;
expect(Array.from(platforms)).toEqual(['ios', 'windows', 'vr']);
}); });
const platforms = DependencyGraph.mock.calls[0][0].platforms;
expect(Array.from(platforms)).toEqual(['ios', 'windows', 'vr']);
}); });
it('should get dependencies with polyfills', function() { it('should get dependencies with polyfills', function() {
expect.assertions(5);
var module = createModule('index'); var module = createModule('index');
var deps = [module]; var deps = [module];
var depResolver = new Resolver({ var depResolverPromise = Resolver.load({
projectRoot: '/root', projectRoot: '/root',
}); });
@ -144,14 +150,14 @@ describe('Resolver', function() {
}; };
DependencyGraph.prototype.createPolyfill.mockReturnValueOnce(polyfill); DependencyGraph.prototype.createPolyfill.mockReturnValueOnce(polyfill);
return depResolver return depResolverPromise
.getDependencies( .then(r => r.getDependencies(
'/root/index.js', '/root/index.js',
{dev: false}, {dev: false},
undefined, undefined,
undefined, undefined,
createGetModuleId() createGetModuleId()
).then(function(result) { )).then(function(result) {
expect(result.mainModuleId).toEqual('index'); expect(result.mainModuleId).toEqual('index');
expect(result.dependencies[result.dependencies.length - 1]).toBe(module); expect(result.dependencies[result.dependencies.length - 1]).toBe(module);
@ -256,10 +262,12 @@ describe('Resolver', function() {
}); });
it('should pass in more polyfills', function() { it('should pass in more polyfills', function() {
expect.assertions(2);
var module = createModule('index'); var module = createModule('index');
var deps = [module]; var deps = [module];
var depResolver = new Resolver({ var depResolverPromise = Resolver.load({
projectRoot: '/root', projectRoot: '/root',
polyfillModuleNames: ['some module'], polyfillModuleNames: ['some module'],
}); });
@ -271,14 +279,14 @@ describe('Resolver', function() {
})); }));
}); });
return depResolver return depResolverPromise
.getDependencies( .then(r => r.getDependencies(
'/root/index.js', '/root/index.js',
{dev: false}, {dev: false},
undefined, undefined,
undefined, undefined,
createGetModuleId() createGetModuleId()
).then(result => { )).then(result => {
expect(result.mainModuleId).toEqual('index'); expect(result.mainModuleId).toEqual('index');
const calls = const calls =
DependencyGraph.prototype.createPolyfill.mock.calls[result.dependencies.length - 2]; DependencyGraph.prototype.createPolyfill.mock.calls[result.dependencies.length - 2];
@ -305,13 +313,14 @@ describe('Resolver', function() {
describe('wrapModule', function() { describe('wrapModule', function() {
let depResolver; let depResolver;
beforeEach(() => { beforeEach(() => {
depResolver = new Resolver({ return Resolver.load({
depResolver,
projectRoot: '/root', projectRoot: '/root',
}); }).then(r => { depResolver = r; });
}); });
it('should resolve modules', function() { it('should resolve modules', function() {
expect.assertions(1);
/*eslint-disable */ /*eslint-disable */
var code = [ var code = [
// require // require
@ -378,6 +387,8 @@ describe('Resolver', function() {
}); });
it('should add module transport names as fourth argument to `__d`', () => { it('should add module transport names as fourth argument to `__d`', () => {
expect.assertions(1);
const module = createModule('test module'); const module = createModule('test module');
const code = 'arbitrary(code)' const code = 'arbitrary(code)'
const resolutionResponse = new ResolutionResponseMock({ const resolutionResponse = new ResolutionResponseMock({
@ -400,6 +411,7 @@ describe('Resolver', function() {
}); });
it('should pass through passed-in source maps', () => { it('should pass through passed-in source maps', () => {
expect.assertions(1);
const module = createModule('test module'); const module = createModule('test module');
const resolutionResponse = new ResolutionResponseMock({ const resolutionResponse = new ResolutionResponseMock({
dependencies: [module], dependencies: [module],
@ -416,23 +428,25 @@ describe('Resolver', function() {
}); });
it('should resolve polyfills', function () { it('should resolve polyfills', function () {
const depResolver = new Resolver({ expect.assertions(1);
return Resolver.load({
projectRoot: '/root', projectRoot: '/root',
}); }).then(depResolver => {;
const polyfill = createPolyfill('test polyfill', []); const polyfill = createPolyfill('test polyfill', []);
const code = [ const code = [
'global.fetch = () => 1;',
].join('');
return depResolver.wrapModule({
module: polyfill,
code
}).then(({code: processedCode}) => {
expect(processedCode).toEqual([
'(function(global) {',
'global.fetch = () => 1;', 'global.fetch = () => 1;',
'\n})' + ].join('');
"(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this);", return depResolver.wrapModule({
].join('')); module: polyfill,
code
}).then(({code: processedCode}) => {
expect(processedCode).toEqual([
'(function(global) {',
'global.fetch = () => 1;',
'\n})' +
"(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this);",
].join(''));
});
}); });
}); });
@ -442,15 +456,18 @@ describe('Resolver', function() {
let depResolver, module, resolutionResponse; let depResolver, module, resolutionResponse;
beforeEach(() => { beforeEach(() => {
depResolver = new Resolver({projectRoot: '/root'}); return Resolver.load({projectRoot: '/root'}).then(r => {
module = createJsonModule(id); depResolver = r;
resolutionResponse = new ResolutionResponseMock({ module = createJsonModule(id);
dependencies: [module], resolutionResponse = new ResolutionResponseMock({
mainModuleId: id, dependencies: [module],
mainModuleId: id,
});
}); });
}); });
it('should prefix JSON files with `module.exports=`', () => { it('should prefix JSON files with `module.exports=`', () => {
expect.assertions(1);
return depResolver return depResolver
.wrapModule({resolutionResponse, module, name: id, code, dev: false}) .wrapModule({resolutionResponse, module, name: id, code, dev: false})
.then(({code: processedCode}) => .then(({code: processedCode}) =>
@ -469,10 +486,6 @@ describe('Resolver', function() {
beforeEach(() => { beforeEach(() => {
minifyCode = jest.fn((filename, code, map) => minifyCode = jest.fn((filename, code, map) =>
Promise.resolve({code, map})); Promise.resolve({code, map}));
depResolver = new Resolver({
projectRoot: '/root',
minifyCode,
});
module = createModule(id); module = createModule(id);
module.path = '/arbitrary/path.js'; module.path = '/arbitrary/path.js';
resolutionResponse = new ResolutionResponseMock({ resolutionResponse = new ResolutionResponseMock({
@ -480,9 +493,14 @@ describe('Resolver', function() {
mainModuleId: id, mainModuleId: id,
}); });
sourceMap = {version: 3, sources: ['input'], mappings: 'whatever'}; sourceMap = {version: 3, sources: ['input'], mappings: 'whatever'};
return Resolver.load({
projectRoot: '/root',
minifyCode,
}).then(r => { depResolver = r; });
}); });
it('should invoke the minifier with the wrapped code', () => { it('should invoke the minifier with the wrapped code', () => {
expect.assertions(1);
const wrappedCode = const wrappedCode =
`__d(/* ${id} */function(global, require, module, exports) {${ `__d(/* ${id} */function(global, require, module, exports) {${
code}\n}, ${resolutionResponse.getModuleId(module)});` code}\n}, ${resolutionResponse.getModuleId(module)});`
@ -501,6 +519,7 @@ describe('Resolver', function() {
}); });
it('should use minified code', () => { it('should use minified code', () => {
expect.assertions(2);
const minifiedCode = 'minified(code)'; const minifiedCode = 'minified(code)';
const minifiedMap = {version: 3, file: ['minified']}; const minifiedMap = {version: 3, file: ['minified']};
minifyCode.mockReturnValue(Promise.resolve({code: minifiedCode, map: minifiedMap})); minifyCode.mockReturnValue(Promise.resolve({code: minifiedCode, map: minifiedMap}));

View File

@ -50,13 +50,18 @@ type Options = {
class Resolver { class Resolver {
_depGraphPromise: Promise<DependencyGraph>;
_depGraph: DependencyGraph; _depGraph: DependencyGraph;
_minifyCode: MinifyCode; _minifyCode: MinifyCode;
_polyfillModuleNames: Array<string>; _polyfillModuleNames: Array<string>;
constructor(opts: Options) { constructor(opts: Options, depGraph: DependencyGraph) {
this._depGraphPromise = DependencyGraph.load({ this._minifyCode = opts.minifyCode;
this._polyfillModuleNames = opts.polyfillModuleNames || [];
this._depGraph = depGraph;
}
static async load(opts: Options): Promise<Resolver> {
const depGraph = await DependencyGraph.load({
assetDependencies: ['react-native/Libraries/Image/AssetRegistry'], assetDependencies: ['react-native/Libraries/Image/AssetRegistry'],
assetExts: opts.assetExts, assetExts: opts.assetExts,
cache: opts.cache, cache: opts.cache,
@ -86,19 +91,7 @@ class Resolver {
useWatchman: true, useWatchman: true,
watch: opts.watch || false, watch: opts.watch || false,
}); });
return new Resolver(opts, depGraph);
this._minifyCode = opts.minifyCode;
this._polyfillModuleNames = opts.polyfillModuleNames || [];
this._depGraphPromise.then(
depGraph => { this._depGraph = depGraph; },
err => {
console.error(err.message + '\n' + err.stack);
// FIXME(jeanlauliac): we shall never exit the process by ourselves,
// packager may be used in a server application or the like.
process.exit(1);
},
);
} }
getShallowDependencies( getShallowDependencies(
@ -120,13 +113,13 @@ class Resolver {
getModuleId: mixed, getModuleId: mixed,
): Promise<ResolutionResponse> { ): Promise<ResolutionResponse> {
const {platform, recursive = true} = options; const {platform, recursive = true} = options;
return this._depGraphPromise.then(depGraph => depGraph.getDependencies({ return this._depGraph.getDependencies({
entryPath, entryPath,
platform, platform,
transformOptions, transformOptions,
recursive, recursive,
onProgress, onProgress,
})).then(resolutionResponse => { }).then(resolutionResponse => {
this._getPolyfillDependencies().reverse().forEach( this._getPolyfillDependencies().reverse().forEach(
polyfill => resolutionResponse.prependDependency(polyfill) polyfill => resolutionResponse.prependDependency(polyfill)
); );
@ -252,8 +245,8 @@ class Resolver {
return this._minifyCode(path, code, map); return this._minifyCode(path, code, map);
} }
getDependencyGraph(): Promise<DependencyGraph> { getDependencyGraph(): DependencyGraph {
return this._depGraphPromise; return this._depGraph;
} }
} }

View File

@ -79,12 +79,12 @@ describe('processRequest', () => {
Bundler.prototype.invalidateFile = invalidatorFunc; Bundler.prototype.invalidateFile = invalidatorFunc;
Bundler.prototype.getResolver = Bundler.prototype.getResolver =
jest.fn().mockReturnValue({ jest.fn().mockReturnValue(Promise.resolve({
getDependencyGraph: jest.fn().mockReturnValue(Promise.resolve({ getDependencyGraph: jest.fn().mockReturnValue({
getHasteMap: jest.fn().mockReturnValue({on: jest.fn()}), getHasteMap: jest.fn().mockReturnValue({on: jest.fn()}),
load: jest.fn(() => Promise.resolve()), load: jest.fn(() => Promise.resolve()),
})), }),
}); }));
server = new Server(options); server = new Server(options);
requestHandler = server.processRequest.bind(server); requestHandler = server.processRequest.bind(server);

View File

@ -251,8 +251,8 @@ class Server {
this._bundler = new Bundler(bundlerOpts); this._bundler = new Bundler(bundlerOpts);
// changes to the haste map can affect resolution of files in the bundle // changes to the haste map can affect resolution of files in the bundle
this._bundler.getResolver().getDependencyGraph().then(dependencyGraph => { this._bundler.getResolver().then(resolver => {
dependencyGraph.getWatcher().on( resolver.getDependencyGraph().getWatcher().on(
'change', 'change',
({eventsQueue}) => eventsQueue.forEach(processFileChange), ({eventsQueue}) => eventsQueue.forEach(processFileChange),
); );
@ -306,7 +306,7 @@ class Server {
entryFile: string, entryFile: string,
platform?: string, platform?: string,
}): Promise<Bundle> { }): Promise<Bundle> {
return this._bundler.getResolver().getDependencyGraph().then(() => { return this._bundler.getResolver().then(() => {
if (!options.platform) { if (!options.platform) {
options.platform = getPlatformExtension(options.entryFile); options.platform = getPlatformExtension(options.entryFile);
} }
@ -319,13 +319,14 @@ class Server {
bundleDeps.set(bundle, { bundleDeps.set(bundle, {
files: new Map( files: new Map(
nonVirtual nonVirtual
.map(({sourcePath, meta = {dependencies: []}}) => .map(({sourcePath, meta}) =>
[sourcePath, meta.dependencies]) [sourcePath, meta != null ? meta.dependencies : []])
), ),
idToIndex: new Map(modules.map(({id}, i) => [id, i])), idToIndex: new Map(modules.map(({id}, i) => [id, i])),
dependencyPairs: new Map( dependencyPairs: new Map(
nonVirtual nonVirtual
.filter(({meta}) => meta && meta.dependencyPairs) .filter(({meta}) => meta && meta.dependencyPairs)
/* $FlowFixMe: the filter above ensures `dependencyPairs` is not null. */
.map(m => [m.sourcePath, m.meta.dependencyPairs]) .map(m => [m.sourcePath, m.meta.dependencyPairs])
), ),
outdated: new Set(), outdated: new Set(),
@ -361,7 +362,7 @@ class Server {
}); });
} }
getModuleForPath(entryFile: string): Module { getModuleForPath(entryFile: string): Promise<Module> {
return this._bundler.getModuleForPath(entryFile); return this._bundler.getModuleForPath(entryFile);
} }
@ -602,8 +603,6 @@ class Server {
debug('Attempt to update existing bundle'); debug('Attempt to update existing bundle');
const changedModules =
Array.from(outdated, this.getModuleForPath, this);
// $FlowFixMe(>=0.37.0) // $FlowFixMe(>=0.37.0)
deps.outdated = new Set(); deps.outdated = new Set();
@ -615,11 +614,14 @@ class Server {
// specific response we can compute a non recursive one which // specific response we can compute a non recursive one which
// is the least we need and improve performance. // is the least we need and improve performance.
const bundlePromise = this._bundles[optionsJson] = const bundlePromise = this._bundles[optionsJson] =
this.getDependencies({ Promise.all([
platform, dev, hot, minify, this.getDependencies({
entryFile: options.entryFile, platform, dev, hot, minify,
recursive: false, entryFile: options.entryFile,
}).then(response => { recursive: false,
}),
Promise.all(Array.from(outdated, this.getModuleForPath, this)),
]).then(([response, changedModules]) => {
debug('Update bundle: rebuild shallow bundle'); debug('Update bundle: rebuild shallow bundle');
changedModules.forEach(m => { changedModules.forEach(m => {
@ -969,7 +971,7 @@ class Server {
} }
} }
function contentsEqual(array: Array<mixed>, set: Set<mixed>): boolean { function contentsEqual<T>(array: Array<T>, set: Set<T>): boolean {
return array.length === set.size && array.every(set.has, set); return array.length === set.size && array.every(set.has, set);
} }

View File

@ -17,6 +17,7 @@ import type {MixedSourceMap} from './SourceMap';
type SourceMapOrMappings = MixedSourceMap | Array<RawMapping>; type SourceMapOrMappings = MixedSourceMap | Array<RawMapping>;
type Metadata = { type Metadata = {
dependencies?: ?Array<string>,
dependencyPairs?: Array<[mixed, {path: string}]>, dependencyPairs?: Array<[mixed, {path: string}]>,
preloaded?: boolean, preloaded?: boolean,
}; };