From b3ad0544241081864b518fc81646ebc1b9f96013 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Mon, 5 Dec 2016 16:40:19 -0800 Subject: [PATCH] ModuleGraph: Also call back with module objects of entry points Summary: This changes the callback value of `Graph` function so that it also contains a separate property with `Module` instances of the entry points. Having references to the entry point modules allows to e.g. create require calls to them dynamically to kick of bundle execution. The commit also contains a refactoring of the `Graph` function and its helpers that reduces the need for extensive parameter passing and long nested functions. Reviewed By: cpojer Differential Revision: D4250772 fbshipit-source-id: 2edca77bbef2308d3176a62123b8cecd70e2c8c7 --- react-packager/src/ModuleGraph/Graph.js | 101 ++++++++++-------- .../src/ModuleGraph/__tests__/Graph-test.js | 48 ++++++++- react-packager/src/ModuleGraph/types.flow.js | 7 +- 3 files changed, 104 insertions(+), 52 deletions(-) diff --git a/react-packager/src/ModuleGraph/Graph.js b/react-packager/src/ModuleGraph/Graph.js index 21a84c29..27288c31 100644 --- a/react-packager/src/ModuleGraph/Graph.js +++ b/react-packager/src/ModuleGraph/Graph.js @@ -21,7 +21,6 @@ import type { File, GraphFn, LoadFn, - Module, ResolveFn, } from './types.flow'; @@ -70,17 +69,16 @@ exports.create = function create(resolve: ResolveFn, load: LoadFn): GraphFn { return; } - const modules: Map = new Map(); - modules.set(null, createParentModule()); - const loadQueue: LoadQueue = queue(seq( ({id, parent}, cb) => resolve(id, parent, platform, options || NO_OPTIONS, cb), memoize((file, cb) => load(file, {log, optimize}, cb)), ), Number.MAX_SAFE_INTEGER); + const {collect, loadModule} = createGraphHelpers(loadQueue, cwd, skip); + loadQueue.drain = () => { loadQueue.kill(); - callback(null, collect(null, modules)); + callback(null, collect()); }; loadQueue.error = error => { loadQueue.error = noop; @@ -90,7 +88,7 @@ exports.create = function create(resolve: ResolveFn, load: LoadFn): GraphFn { let i = 0; for (const entryPoint of entryPoints) { - loadModule(entryPoint, null, i++, loadQueue, modules, skip, cwd, callback); + loadModule(entryPoint, null, i++); } if (i === 0) { @@ -103,19 +101,50 @@ exports.create = function create(resolve: ResolveFn, load: LoadFn): GraphFn { return Graph; }; -function loadModule( - id: string, - parent: string | null, - parentDependencyIndex: number, - loadQueue: LoadQueue, - modules: Map, - skip?: Set, - cwd: string, -) { +function createGraphHelpers(loadQueue, cwd, skip) { + const modules = new Map([[null, createParentModule()]]); + + function collect( + path = null, + serialized = {entryModules: [], modules: []}, + seen = new Set(), + ) { + const module = modules.get(path); + if (module == null || seen.has(path)) { + return serialized; + } + + const {dependencies} = module; + if (path === null) { + serialized.entryModules = + dependencies.map(dep => nullthrows(modules.get(dep.path))); + } else { + serialized.modules.push(module); + seen.add(path); + } + + for (const dependency of dependencies) { + collect(dependency.path, serialized, seen); + } + + return serialized; + } + + function loadModule(id, parent, parentDepIndex) { + loadQueue.push( + {id, parent: parent != null ? parent : cwd}, + (error, file, dependencyIDs) => + onFileLoaded(error, file, dependencyIDs, id, parent, parentDepIndex), + ); + } + function onFileLoaded( - error?: ?Error, - file?: File, - dependencyIDs?: Array, + error, + file, + dependencyIDs, + id, + parent, + parentDependencyIndex, ) { if (error) { return; @@ -129,38 +158,16 @@ function loadModule( parentModule.dependencies[parentDependencyIndex] = {id, path}; if ((!skip || !skip.has(path)) && !modules.has(path)) { - const dependencies = Array(dependencyIDs.length); - modules.set(path, {dependencies, file: nullthrows(file)}); + const module = { + dependencies: Array(dependencyIDs.length), + file: nullthrows(file), + }; + modules.set(path, module); for (let i = 0; i < dependencyIDs.length; ++i) { - loadModule(dependencyIDs[i], path, i, loadQueue, modules, skip, cwd); + loadModule(dependencyIDs[i], path, i); } } } - loadQueue.push( - {id, parent: parent != null ? parent : cwd}, - onFileLoaded, - ); -} - -function collect( - path, - modules, - serialized = [], - seen: Set = new Set(), -): Array { - const module = modules.get(path); - if (!module || seen.has(path)) { return serialized; } - - if (path !== null) { - serialized.push(module); - seen.add(path); - } - - const {dependencies} = module; - for (var i = 0; i < dependencies.length; i++) { - collect(dependencies[i].path, modules, serialized, seen); - } - - return serialized; + return {collect, loadModule}; } diff --git a/react-packager/src/ModuleGraph/__tests__/Graph-test.js b/react-packager/src/ModuleGraph/__tests__/Graph-test.js index 5d468ef6..2fede7c4 100644 --- a/react-packager/src/ModuleGraph/__tests__/Graph-test.js +++ b/react-packager/src/ModuleGraph/__tests__/Graph-test.js @@ -239,7 +239,7 @@ describe('Graph:', () => { graph(['a'], anyPlatform, noOpts, (error, result) => { expect(error).toEqual(null); - expect(result).toEqual([ + expect(result.modules).toEqual([ createModule('a', ['b', 'e', 'h']), createModule('b', ['c', 'd']), createModule('c'), @@ -253,6 +253,46 @@ describe('Graph:', () => { }); }); + it('calls back with the resolved modules of the entry points', done => { + load.stub.reset(); + resolve.stub.reset(); + + load.stub.withArgs(idToPath('a')).yields(null, createFile('a'), ['b']); + load.stub.withArgs(idToPath('b')).yields(null, createFile('b'), []); + load.stub.withArgs(idToPath('c')).yields(null, createFile('c'), ['d']); + load.stub.withArgs(idToPath('d')).yields(null, createFile('d'), []); + + 'abcd'.split('') + .forEach(id => resolve.stub.withArgs(id).yields(null, idToPath(id))); + + graph(['a', 'c'], anyPlatform, noOpts, (error, result) => { + expect(result.entryModules).toEqual([ + createModule('a', ['b']), + createModule('c', ['d']), + ]); + done(); + }); + }); + + it('calls back with the resolved modules of the entry points if one entry point is a dependency of another', done => { + load.stub.reset(); + resolve.stub.reset(); + + load.stub.withArgs(idToPath('a')).yields(null, createFile('a'), ['b']); + load.stub.withArgs(idToPath('b')).yields(null, createFile('b'), []); + + 'ab'.split('') + .forEach(id => resolve.stub.withArgs(id).yields(null, idToPath(id))); + + graph(['a', 'b'], anyPlatform, noOpts, (error, result) => { + expect(result.entryModules).toEqual([ + createModule('a', ['b']), + createModule('b', []), + ]); + done(); + }); + }); + it('does not include dependencies more than once', done => { const ids = ['a', 'b', 'c', 'd']; ids.forEach(id => { @@ -266,7 +306,7 @@ describe('Graph:', () => { graph(['a', 'd', 'b'], anyPlatform, noOpts, (error, result) => { expect(error).toEqual(null); - expect(result).toEqual([ + expect(result.modules).toEqual([ createModule('a', ['b', 'c']), createModule('b'), createModule('c'), @@ -287,7 +327,7 @@ describe('Graph:', () => { .withArgs(idToPath('c')).yields(null, createFile('c'), ['a']); graph(['a'], anyPlatform, noOpts, (error, result) => { - expect(result).toEqual([ + expect(result.modules).toEqual([ createModule('a', ['b']), createModule('b', ['c']), createModule('c', ['a']), @@ -307,7 +347,7 @@ describe('Graph:', () => { const skip = new Set([idToPath('b'), idToPath('c')]); graph(['a'], anyPlatform, {skip}, (error, result) => { - expect(result).toEqual([ + expect(result.modules).toEqual([ createModule('a', ['b', 'c', 'd']), createModule('d', []), ]); diff --git a/react-packager/src/ModuleGraph/types.flow.js b/react-packager/src/ModuleGraph/types.flow.js index 705c328b..8bb652a8 100644 --- a/react-packager/src/ModuleGraph/types.flow.js +++ b/react-packager/src/ModuleGraph/types.flow.js @@ -56,9 +56,14 @@ export type GraphFn = ( entryPoints: Iterable, platform: string, options?: ?GraphOptions, - callback?: Callback>, + callback?: Callback, ) => void; +type GraphResult = { + entryModules: Array, + modules: Array, +}; + export type ResolveFn = ( id: string, source: string,