Add ramGroups and preloadedModules support when creating RAM bundles

Reviewed By: davidaurelio

Differential Revision: D6250307

fbshipit-source-id: b7143fa31ded9fcaa28025f05b9d28013361ea0e
This commit is contained in:
Rafael Oleza 2017-11-08 07:03:29 -08:00 committed by Facebook Github Bot
parent 91f724d69d
commit 98518c9fff
6 changed files with 345 additions and 25 deletions

View File

@ -876,6 +876,36 @@ class Bundler {
return transform || {inlineRequires: false};
}
/**
* Returns the options needed to create a RAM bundle.
*/
async getRamOptions(
entryFile: string,
options: {dev: boolean, platform: ?string},
getDependencies: string => Promise<Array<string>>,
): Promise<{|
+preloadedModules: {[string]: true},
+ramGroups: Array<string>,
|}> {
if (!this._getTransformOptions) {
return {
preloadedModules: {},
ramGroups: [],
};
}
const {preloadedModules, ramGroups} = await this._getTransformOptions(
[entryFile],
{dev: options.dev, hot: true, platform: options.platform},
getDependencies,
);
return {
preloadedModules: preloadedModules || {},
ramGroups: ramGroups || [],
};
}
/*
* Helper method to return the global transform options that are kept in the
* Bundler.

View File

@ -36,7 +36,7 @@ export type PlatformRemoteFileMap = {
type SubTree<T: ModuleTransportLike> = (
moduleTransport: T,
moduleTransportsByPath: Map<string, T>,
) => Generator<number, void, void>;
) => Iterable<number>;
const assetPropertyBlacklist = new Set(['files', 'fileSystemLocation', 'path']);

View File

@ -153,6 +153,36 @@ class DeltaTransformer extends EventEmitter {
return this._deltaCalculator.end();
}
/**
* Returns a function that can be used to calculate synchronously the
* transitive dependencies of any given file within the dependency graph.
**/
async getDependenciesFn() {
if (!this._deltaCalculator.getDependencyEdges().size) {
// If by any means the dependency graph has not been initialized, call
// getDelta() to initialize it.
await this._getDelta();
}
return this._getDependencies;
}
async getRamOptions(
entryFile: string,
options: {dev: boolean, platform: ?string},
): Promise<{|
+preloadedModules: {[string]: true},
+ramGroups: $ReadOnlyArray<string>,
|}> {
const getDependenciesFn = await this.getDependenciesFn();
return await this._bundler.getRamOptions(
entryFile,
options,
async (path: string) => Array.from(getDependenciesFn(path)),
);
}
/**
* Main method to calculate the bundle delta. It returns a DeltaResult,
* which contain the source code of the modified and added modules and the
@ -226,6 +256,44 @@ class DeltaTransformer extends EventEmitter {
};
}
_getDependencies = (path: string): Set<string> => {
const dependencies = this._getDeps(
path,
this._deltaCalculator.getDependencyEdges(),
new Set(),
);
// Remove the main entry point, since this method only returns the
// dependencies.
dependencies.delete(path);
return dependencies;
};
_getDeps(
path: string,
edges: DependencyEdges,
deps: Set<string>,
): Set<string> {
if (deps.has(path)) {
return deps;
}
const edge = edges.get(path);
if (!edge) {
return deps;
}
deps.add(path);
for (const [, dependencyPath] of edge.dependencies) {
this._getDeps(dependencyPath, edges, deps);
}
return deps;
}
async _getPrepend(
transformOptions: JSTransformerOptions,
dependencyEdges: DependencyEdges,

View File

@ -15,13 +15,17 @@
const DeltaPatcher = require('./DeltaPatcher');
const {fromRawMappings} = require('../Bundler/source-map');
const {createRamBundleGroups} = require('../Bundler/util');
import type {AssetData} from '../AssetServer';
import type {BundleOptions} from '../Server';
import type {MappingsMap} from '../lib/SourceMap';
import type {ModuleTransportLike} from '../shared/types.flow';
import type DeltaBundler, {Options as BuildOptions} from './';
import type {DeltaEntry, DeltaTransformResponse} from './DeltaTransformer';
import type DeltaTransformer, {
DeltaEntry,
DeltaTransformResponse,
} from './DeltaTransformer';
export type Options = BundleOptions & {
deltaBundleId: ?string,
@ -30,8 +34,8 @@ export type Options = BundleOptions & {
export type RamModule = ModuleTransportLike;
export type RamBundleInfo = {
startupModules: $ReadOnlyArray<ModuleTransportLike>,
lazyModules: $ReadOnlyArray<ModuleTransportLike>,
startupModules: $ReadOnlyArray<RamModule>,
lazyModules: $ReadOnlyArray<RamModule>,
groups: Map<number, Set<number>>,
};
@ -140,33 +144,82 @@ async function getRamBundleInfo(
deltaBundler: DeltaBundler,
options: Options,
): Promise<RamBundleInfo> {
let modules = await getAllModules(deltaBundler, options);
const {id, delta, deltaTransformer} = await _build(deltaBundler, {
...options,
wrapModules: true,
});
modules = modules.map(module => {
const map = fromRawMappings([module]).toMap(module.path, {
excludeSource: options.excludeSource,
const modules = DeltaPatcher.get(id)
.applyDelta(delta)
.getAllModules()
.map(module => {
const map = fromRawMappings([module]).toMap(module.path, {
excludeSource: options.excludeSource,
});
return {
id: module.id,
code: module.code,
map,
name: module.name,
sourcePath: module.path,
source: module.source,
type: module.type,
};
});
return {
id: module.id,
code: module.code,
map,
name: module.name,
sourcePath: module.path,
source: module.source,
type: module.type,
};
const {
preloadedModules,
ramGroups,
} = await deltaTransformer.getRamOptions(options.entryFile, {
dev: options.dev,
platform: options.platform,
});
const startupModules = modules.filter(module => {
return module.type === 'script' || module.type === 'require';
});
const lazyModules = modules.filter(module => {
return module.type === 'asset' || module.type === 'module';
const startupModules = [];
const lazyModules = [];
modules.forEach(module => {
if (preloadedModules.hasOwnProperty(module.sourcePath)) {
startupModules.push(module);
return;
}
if (module.type === 'script' || module.type === 'require') {
startupModules.push(module);
return;
}
if (module.type === 'asset' || module.type === 'module') {
lazyModules.push(module);
}
});
// TODO: Implement RAM groups functionality in Delta Bundler.
return {startupModules, lazyModules, groups: new Map()};
const getDependencies = await deltaTransformer.getDependenciesFn();
const groups = createRamBundleGroups(
ramGroups,
lazyModules,
(module: RamModule, dependenciesByPath: Map<string, RamModule>) => {
const deps = getDependencies(module.sourcePath);
const output = new Set();
for (const dependency of deps) {
const module = dependenciesByPath.get(dependency);
if (module) {
output.add(module.id);
}
}
return output;
},
);
return {
startupModules,
lazyModules,
groups,
};
}
async function getAssets(
@ -192,7 +245,11 @@ async function getAssets(
async function _build(
deltaBundler: DeltaBundler,
options: BuildOptions,
): Promise<{id: string, delta: DeltaTransformResponse}> {
): Promise<{
id: string,
delta: DeltaTransformResponse,
deltaTransformer: DeltaTransformer,
}> {
const {deltaTransformer, id} = await deltaBundler.getDeltaTransformer(
options,
);
@ -200,6 +257,7 @@ async function _build(
return {
id,
delta: await deltaTransformer.getDelta(),
deltaTransformer,
};
}

View File

@ -19,6 +19,8 @@ const CURRENT_TIME = 1482363367000;
describe('Serializers', () => {
const OriginalDate = global.Date;
const getDelta = jest.fn();
const getDependenciesFn = jest.fn();
const getRamOptions = jest.fn();
let deltaBundler;
const deltaResponse = {
@ -38,6 +40,13 @@ describe('Serializers', () => {
beforeEach(() => {
getDelta.mockReturnValueOnce(Promise.resolve(deltaResponse));
getDependenciesFn.mockReturnValue(Promise.resolve(() => new Set()));
getRamOptions.mockReturnValue(
Promise.resolve({
preloadedModules: {},
ramGroups: [],
}),
);
deltaBundler = {
async getDeltaTransformer() {
@ -45,6 +54,8 @@ describe('Serializers', () => {
id: '1234',
deltaTransformer: {
getDelta,
getDependenciesFn,
getRamOptions,
},
};
},
@ -173,6 +184,49 @@ describe('Serializers', () => {
).toMatchSnapshot();
});
it('should use the preloadedModules and ramGroup configs to build a RAM bundle', async () => {
getDelta.mockReset();
getDependenciesFn.mockReset();
getDelta.mockReturnValue(
Promise.resolve({
delta: new Map([
[3, {type: 'module', code: 'code', id: 3, path: '/foo/3.js'}],
[4, {type: 'module', code: 'code', id: 4, path: '/foo/4.js'}],
[5, {type: 'module', code: 'code', id: 5, path: '/foo/5.js'}],
[6, {type: 'module', code: 'code', id: 6, path: '/foo/6.js'}],
]),
pre: new Map([
[7, {type: 'script', code: 'more pre;', id: 7, path: '/foo/7.js'}],
]),
post: new Map([
[8, {type: 'require', code: 'bananas;', id: 8, path: '/foo/8.js'}],
]),
inverseDependencies: [],
reset: 1,
}),
);
getRamOptions.mockReturnValue(
Promise.resolve({
preloadedModules: {'/foo/3.js': true},
ramGroups: ['/foo/5.js'],
}),
);
getDependenciesFn.mockReturnValue(
Promise.resolve(path => {
expect(path).toBe('/foo/5.js');
return new Set(['/foo/6.js']);
}),
);
expect(
await Serializers.getRamBundleInfo(deltaBundler, {deltaBundleId: 10}),
).toMatchSnapshot();
});
it('should return the bundle assets', async () => {
expect(
await Serializers.getAllModules(deltaBundler, {deltaBundleId: 10}),

View File

@ -371,3 +371,113 @@ Object {
"numModifiedFiles": 2,
}
`;
exports[`Serializers should use the preloadedModules and ramGroup configs to build a RAM bundle 1`] = `
Object {
"groups": Map {
5 => Set {
6,
},
},
"lazyModules": Array [
Object {
"code": "code",
"id": 4,
"map": Object {
"file": "/foo/4.js",
"mappings": "",
"names": Array [],
"sources": Array [],
"sourcesContent": Array [],
"version": 3,
},
"name": undefined,
"source": undefined,
"sourcePath": "/foo/4.js",
"type": "module",
},
Object {
"code": "code",
"id": 5,
"map": Object {
"file": "/foo/5.js",
"mappings": "",
"names": Array [],
"sources": Array [],
"sourcesContent": Array [],
"version": 3,
},
"name": undefined,
"source": undefined,
"sourcePath": "/foo/5.js",
"type": "module",
},
Object {
"code": "code",
"id": 6,
"map": Object {
"file": "/foo/6.js",
"mappings": "",
"names": Array [],
"sources": Array [],
"sourcesContent": Array [],
"version": 3,
},
"name": undefined,
"source": undefined,
"sourcePath": "/foo/6.js",
"type": "module",
},
],
"startupModules": Array [
Object {
"code": "more pre;",
"id": 7,
"map": Object {
"file": "/foo/7.js",
"mappings": "",
"names": Array [],
"sources": Array [],
"sourcesContent": Array [],
"version": 3,
},
"name": undefined,
"source": undefined,
"sourcePath": "/foo/7.js",
"type": "script",
},
Object {
"code": "code",
"id": 3,
"map": Object {
"file": "/foo/3.js",
"mappings": "",
"names": Array [],
"sources": Array [],
"sourcesContent": Array [],
"version": 3,
},
"name": undefined,
"source": undefined,
"sourcePath": "/foo/3.js",
"type": "module",
},
Object {
"code": "bananas;",
"id": 8,
"map": Object {
"file": "/foo/8.js",
"mappings": "",
"names": Array [],
"sources": Array [],
"sourcesContent": Array [],
"version": 3,
},
"name": undefined,
"source": undefined,
"sourcePath": "/foo/8.js",
"type": "require",
},
],
}
`;