diff --git a/packages/metro-bundler/src/Bundler/__tests__/Bundler-test.js b/packages/metro-bundler/src/Bundler/__tests__/Bundler-test.js index 7288c295..f8b1639b 100644 --- a/packages/metro-bundler/src/Bundler/__tests__/Bundler-test.js +++ b/packages/metro-bundler/src/Bundler/__tests__/Bundler-test.js @@ -86,6 +86,7 @@ describe('Bundler', function() { getModuleSystemDependencies, }; }); + Resolver.load = jest.fn().mockImplementation(opts => Promise.resolve(new Resolver(opts))); fs.statSync.mockImplementation(function() { return { diff --git a/packages/metro-bundler/src/Bundler/index.js b/packages/metro-bundler/src/Bundler/index.js index 1ec11649..c7428fdf 100644 --- a/packages/metro-bundler/src/Bundler/index.js +++ b/packages/metro-bundler/src/Bundler/index.js @@ -108,7 +108,7 @@ class Bundler { _getModuleId: (opts: Module) => number; _cache: Cache; _transformer: Transformer; - _resolver: Resolver; + _resolverPromise: Promise; _projectRoots: Array; _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 { 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 { + 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 { + return this._resolverPromise; } } diff --git a/packages/metro-bundler/src/Resolver/__tests__/Resolver-test.js b/packages/metro-bundler/src/Resolver/__tests__/Resolver-test.js index 0bfa1751..a1579e31 100644 --- a/packages/metro-bundler/src/Resolver/__tests__/Resolver-test.js +++ b/packages/metro-bundler/src/Resolver/__tests__/Resolver-test.js @@ -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})); diff --git a/packages/metro-bundler/src/Resolver/index.js b/packages/metro-bundler/src/Resolver/index.js index 181b121b..30587c95 100644 --- a/packages/metro-bundler/src/Resolver/index.js +++ b/packages/metro-bundler/src/Resolver/index.js @@ -50,13 +50,18 @@ type Options = { class Resolver { - _depGraphPromise: Promise; _depGraph: DependencyGraph; _minifyCode: MinifyCode; _polyfillModuleNames: Array; - 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 { + 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 { 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 { - return this._depGraphPromise; + getDependencyGraph(): DependencyGraph { + return this._depGraph; } } diff --git a/packages/metro-bundler/src/Server/__tests__/Server-test.js b/packages/metro-bundler/src/Server/__tests__/Server-test.js index 2e6ddd8a..8223190b 100644 --- a/packages/metro-bundler/src/Server/__tests__/Server-test.js +++ b/packages/metro-bundler/src/Server/__tests__/Server-test.js @@ -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); diff --git a/packages/metro-bundler/src/Server/index.js b/packages/metro-bundler/src/Server/index.js index 46929210..66c84869 100644 --- a/packages/metro-bundler/src/Server/index.js +++ b/packages/metro-bundler/src/Server/index.js @@ -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 { - 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 { 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, set: Set): boolean { +function contentsEqual(array: Array, set: Set): boolean { return array.length === set.size && array.every(set.has, set); } diff --git a/packages/metro-bundler/src/lib/ModuleTransport.js b/packages/metro-bundler/src/lib/ModuleTransport.js index fefecb63..14133660 100644 --- a/packages/metro-bundler/src/lib/ModuleTransport.js +++ b/packages/metro-bundler/src/lib/ModuleTransport.js @@ -17,6 +17,7 @@ import type {MixedSourceMap} from './SourceMap'; type SourceMapOrMappings = MixedSourceMap | Array; type Metadata = { + dependencies?: ?Array, dependencyPairs?: Array<[mixed, {path: string}]>, preloaded?: boolean, };