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,
};
});
Resolver.load = jest.fn().mockImplementation(opts => Promise.resolve(new Resolver(opts)));
fs.statSync.mockImplementation(function() {
return {

View File

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

View File

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

View File

@ -50,13 +50,18 @@ type Options = {
class Resolver {
_depGraphPromise: Promise<DependencyGraph>;
_depGraph: DependencyGraph;
_minifyCode: MinifyCode;
_polyfillModuleNames: Array<string>;
constructor(opts: Options) {
this._depGraphPromise = DependencyGraph.load({
constructor(opts: Options, depGraph: DependencyGraph) {
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'],
assetExts: opts.assetExts,
cache: opts.cache,
@ -86,19 +91,7 @@ class Resolver {
useWatchman: true,
watch: opts.watch || false,
});
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);
},
);
return new Resolver(opts, depGraph);
}
getShallowDependencies(
@ -120,13 +113,13 @@ class Resolver {
getModuleId: mixed,
): Promise<ResolutionResponse> {
const {platform, recursive = true} = options;
return this._depGraphPromise.then(depGraph => depGraph.getDependencies({
return this._depGraph.getDependencies({
entryPath,
platform,
transformOptions,
recursive,
onProgress,
})).then(resolutionResponse => {
}).then(resolutionResponse => {
this._getPolyfillDependencies().reverse().forEach(
polyfill => resolutionResponse.prependDependency(polyfill)
);
@ -252,8 +245,8 @@ class Resolver {
return this._minifyCode(path, code, map);
}
getDependencyGraph(): Promise<DependencyGraph> {
return this._depGraphPromise;
getDependencyGraph(): DependencyGraph {
return this._depGraph;
}
}

View File

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

View File

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

View File

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