mirror of https://github.com/status-im/metro.git
Add support for RAM bundle groups
Summary: Adds support for “RAM bundle groups” (common section for module groups) to the new Buck integration Reviewed By: jeanlauliac Differential Revision: D5112065 fbshipit-source-id: 038c06b8f4133c7fcd39aba8bb04a5ef42594f3e
This commit is contained in:
parent
502f24a266
commit
3e8991548b
|
@ -19,6 +19,7 @@ const crypto = require('crypto');
|
||||||
const debug = require('debug')('RNP:Bundle');
|
const debug = require('debug')('RNP:Bundle');
|
||||||
const invariant = require('fbjs/lib/invariant');
|
const invariant = require('fbjs/lib/invariant');
|
||||||
|
|
||||||
|
const {createRamBundleGroups} = require('./util');
|
||||||
const {fromRawMappings} = require('./source-map');
|
const {fromRawMappings} = require('./source-map');
|
||||||
const {isMappingsMap} = require('../lib/SourceMap');
|
const {isMappingsMap} = require('../lib/SourceMap');
|
||||||
|
|
||||||
|
@ -185,7 +186,7 @@ class Bundle extends BundleBase {
|
||||||
lazyModules,
|
lazyModules,
|
||||||
get groups() {
|
get groups() {
|
||||||
if (!groups) {
|
if (!groups) {
|
||||||
groups = createGroups(ramGroups || [], lazyModules);
|
groups = createRamBundleGroups(ramGroups || [], lazyModules, subtree);
|
||||||
}
|
}
|
||||||
return groups;
|
return groups;
|
||||||
},
|
},
|
||||||
|
@ -344,18 +345,18 @@ function partition(array, predicate) {
|
||||||
return [included, excluded];
|
return [included, excluded];
|
||||||
}
|
}
|
||||||
|
|
||||||
function * filter(iterator, predicate) {
|
function * subtree(
|
||||||
for (const value of iterator) {
|
moduleTransport: ModuleTransport,
|
||||||
if (predicate(value)) {
|
moduleTransportsByPath: Map<string, ModuleTransport>,
|
||||||
yield value;
|
seen = new Set(),
|
||||||
}
|
) {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function * subtree(moduleTransport: ModuleTransport, moduleTransportsByPath, seen = new Set()) {
|
|
||||||
seen.add(moduleTransport.id);
|
seen.add(moduleTransport.id);
|
||||||
/* $FlowFixMe: there may not be a `meta` object */
|
const {meta} = moduleTransport;
|
||||||
for (const [, {path}] of moduleTransport.meta.dependencyPairs || []) {
|
invariant(
|
||||||
|
meta != null,
|
||||||
|
'Unexpected module transport without meta information: ' + moduleTransport.sourcePath,
|
||||||
|
);
|
||||||
|
for (const [, {path}] of meta.dependencyPairs || []) {
|
||||||
const dependency = moduleTransportsByPath.get(path);
|
const dependency = moduleTransportsByPath.get(path);
|
||||||
if (dependency && !seen.has(dependency.id)) {
|
if (dependency && !seen.has(dependency.id)) {
|
||||||
yield dependency.id;
|
yield dependency.id;
|
||||||
|
@ -364,75 +365,6 @@ function * subtree(moduleTransport: ModuleTransport, moduleTransportsByPath, see
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ArrayMap extends Map {
|
|
||||||
get(key) {
|
|
||||||
let array = super.get(key);
|
|
||||||
if (!array) {
|
|
||||||
array = [];
|
|
||||||
this.set(key, array);
|
|
||||||
}
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createGroups(ramGroups: Array<string>, lazyModules) {
|
|
||||||
// build two maps that allow to lookup module data
|
|
||||||
// by path or (numeric) module id;
|
|
||||||
const byPath = new Map();
|
|
||||||
const byId = new Map();
|
|
||||||
lazyModules.forEach(m => {
|
|
||||||
byPath.set(m.sourcePath, m);
|
|
||||||
byId.set(m.id, m.sourcePath);
|
|
||||||
});
|
|
||||||
|
|
||||||
// build a map of group root IDs to an array of module IDs in the group
|
|
||||||
const result: Map<number, Set<number>> = new Map(
|
|
||||||
ramGroups
|
|
||||||
.map(modulePath => {
|
|
||||||
const root = byPath.get(modulePath);
|
|
||||||
if (!root) {
|
|
||||||
throw Error(`Group root ${modulePath} is not part of the bundle`);
|
|
||||||
}
|
|
||||||
return [
|
|
||||||
root.id,
|
|
||||||
// `subtree` yields the IDs of all transitive dependencies of a module
|
|
||||||
/* $FlowFixMe: assumes the module is always in the Map */
|
|
||||||
new Set(subtree(byPath.get(root.sourcePath), byPath)),
|
|
||||||
];
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
if (ramGroups.length > 1) {
|
|
||||||
// build a map of all grouped module IDs to an array of group root IDs
|
|
||||||
const all = new ArrayMap();
|
|
||||||
for (const [parent, children] of result) {
|
|
||||||
for (const module of children) {
|
|
||||||
all.get(module).push(parent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// find all module IDs that are part of more than one group
|
|
||||||
const doubles = filter(all, ([, parents]) => parents.length > 1);
|
|
||||||
for (const [moduleId, parents] of doubles) {
|
|
||||||
// remove them from their groups
|
|
||||||
/* $FlowFixMe: this assumes the element exists. */
|
|
||||||
parents.forEach(p => result.get(p).delete(moduleId));
|
|
||||||
|
|
||||||
// print a warning for each removed module
|
|
||||||
const parentNames = parents.map(byId.get, byId);
|
|
||||||
const lastName = parentNames.pop();
|
|
||||||
throw new Error(
|
|
||||||
/* $FlowFixMe: this assumes the element exists. */
|
|
||||||
`Module ${byId.get(moduleId)} belongs to groups ${
|
|
||||||
parentNames.join(', ')}, and ${lastName
|
|
||||||
}. Removing it from all groups.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isRawMappings = Array.isArray;
|
const isRawMappings = Array.isArray;
|
||||||
|
|
||||||
module.exports = Bundle;
|
module.exports = Bundle;
|
||||||
|
|
|
@ -379,7 +379,7 @@ describe('Bundle', () => {
|
||||||
}).toThrow(
|
}).toThrow(
|
||||||
new Error(
|
new Error(
|
||||||
`Module ${fsLocation('invariant')} belongs to groups ${fsLocation('React')}` +
|
`Module ${fsLocation('invariant')} belongs to groups ${fsLocation('React')}` +
|
||||||
`, and ${fsLocation('OtherFramework')}. Removing it from all groups.`,
|
`, and ${fsLocation('OtherFramework')}. Ensure that each module is only part of one group.`,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,6 +16,12 @@ const babelGenerate = require('babel-generator').default;
|
||||||
const babylon = require('babylon');
|
const babylon = require('babylon');
|
||||||
|
|
||||||
import type {AssetDescriptor} from '.';
|
import type {AssetDescriptor} from '.';
|
||||||
|
import type {ModuleTransportLike} from '../../../local-cli/bundle/types.flow';
|
||||||
|
|
||||||
|
type SubTree<T: ModuleTransportLike> = (
|
||||||
|
moduleTransport: T,
|
||||||
|
moduleTransportsByPath: Map<string, T>,
|
||||||
|
) => Generator<number, void, void>;
|
||||||
|
|
||||||
const assetPropertyBlacklist = new Set([
|
const assetPropertyBlacklist = new Set([
|
||||||
'files',
|
'files',
|
||||||
|
@ -69,7 +75,82 @@ function filterObject(object, blacklist) {
|
||||||
return copied;
|
return copied;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createRamBundleGroups<T: ModuleTransportLike>(
|
||||||
|
ramGroups: $ReadOnlyArray<string>,
|
||||||
|
groupableModules: $ReadOnlyArray<T>,
|
||||||
|
subtree: SubTree<T>,
|
||||||
|
): Map<number, Set<number>> {
|
||||||
|
// build two maps that allow to lookup module data
|
||||||
|
// by path or (numeric) module id;
|
||||||
|
const byPath = new Map();
|
||||||
|
const byId = new Map();
|
||||||
|
groupableModules.forEach(m => {
|
||||||
|
byPath.set(m.sourcePath, m);
|
||||||
|
byId.set(m.id, m.sourcePath);
|
||||||
|
});
|
||||||
|
|
||||||
|
// build a map of group root IDs to an array of module IDs in the group
|
||||||
|
const result: Map<number, Set<number>> = new Map(
|
||||||
|
ramGroups
|
||||||
|
.map(modulePath => {
|
||||||
|
const root = byPath.get(modulePath);
|
||||||
|
if (root == null) {
|
||||||
|
throw Error(`Group root ${modulePath} is not part of the bundle`);
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
root.id,
|
||||||
|
// `subtree` yields the IDs of all transitive dependencies of a module
|
||||||
|
new Set(subtree(root, byPath)),
|
||||||
|
];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (ramGroups.length > 1) {
|
||||||
|
// build a map of all grouped module IDs to an array of group root IDs
|
||||||
|
const all = new ArrayMap();
|
||||||
|
for (const [parent, children] of result) {
|
||||||
|
for (const module of children) {
|
||||||
|
all.get(module).push(parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// find all module IDs that are part of more than one group
|
||||||
|
const doubles = filter(all, ([, parents]) => parents.length > 1);
|
||||||
|
for (const [moduleId, parents] of doubles) {
|
||||||
|
const parentNames = parents.map(byId.get, byId);
|
||||||
|
const lastName = parentNames.pop();
|
||||||
|
throw new Error(
|
||||||
|
`Module ${byId.get(moduleId) || moduleId} belongs to groups ${
|
||||||
|
parentNames.join(', ')}, and ${String(lastName)
|
||||||
|
}. Ensure that each module is only part of one group.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function * filter(iterator, predicate) {
|
||||||
|
for (const value of iterator) {
|
||||||
|
if (predicate(value)) {
|
||||||
|
yield value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArrayMap extends Map {
|
||||||
|
get(key) {
|
||||||
|
let array = super.get(key);
|
||||||
|
if (!array) {
|
||||||
|
array = [];
|
||||||
|
this.set(key, array);
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
createRamBundleGroups,
|
||||||
generateAssetCodeFileAst,
|
generateAssetCodeFileAst,
|
||||||
generateAssetTransformResult,
|
generateAssetTransformResult,
|
||||||
isAssetTypeAnImage,
|
isAssetTypeAnImage,
|
||||||
|
|
|
@ -26,14 +26,14 @@ let ids, modules, requireCall;
|
||||||
const idForPath = ({path}) => getId(path);
|
const idForPath = ({path}) => getId(path);
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
modules = [
|
modules = [
|
||||||
makeModule('a', 'script'),
|
makeModule('a', [], 'script'),
|
||||||
makeModule('b'),
|
makeModule('b', ['c']),
|
||||||
makeModule('c'),
|
makeModule('c', ['f']),
|
||||||
makeModule('d'),
|
makeModule('d', ['e']),
|
||||||
makeModule('e'),
|
makeModule('e'),
|
||||||
makeModule('f'),
|
makeModule('f'),
|
||||||
];
|
];
|
||||||
requireCall = makeModule('r', 'script', 'require(1);');
|
requireCall = makeModule('r', [], 'script', 'require(1);');
|
||||||
|
|
||||||
ids = new Map(modules.map(({file}, i) => [file.path, i]));
|
ids = new Map(modules.map(({file}, i) => [file.path, i]));
|
||||||
({code, map} = createRamBundle());
|
({code, map} = createRamBundle());
|
||||||
|
@ -79,7 +79,7 @@ it('creates a source map', () => {
|
||||||
expect(map.x_facebook_offsets).toEqual([1, 2, 3, 4, 5, 6]);
|
expect(map.x_facebook_offsets).toEqual([1, 2, 3, 4, 5, 6]);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Optimization:', () => {
|
describe('Startup section optimization', () => {
|
||||||
let last, preloaded;
|
let last, preloaded;
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
last = modules[modules.length - 1];
|
last = modules[modules.length - 1];
|
||||||
|
@ -130,12 +130,62 @@ describe('Optimization:', () => {
|
||||||
return section;
|
return section;
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function createRamBundle(preloadedModules = new Set()) {
|
describe('RAM groups / common sections', () => {
|
||||||
const build = indexedRamBundle.createBuilder(preloadedModules);
|
let groups, groupHeads;
|
||||||
|
beforeAll(() => {
|
||||||
|
groups = [
|
||||||
|
[modules[1], modules[2], modules[5]],
|
||||||
|
[modules[3], modules[4]],
|
||||||
|
];
|
||||||
|
groupHeads = groups.map(g => g[0]);
|
||||||
|
({code, map} = createRamBundle(undefined, groupHeads.map(getPath)));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('supports grouping the transitive dependencies of files into common sections', () => {
|
||||||
|
const {codeOffset, table} = parseOffsetTable(code);
|
||||||
|
|
||||||
|
groups.forEach(group => {
|
||||||
|
const [head, ...deps] = group.map(x => idForPath(x.file));
|
||||||
|
const groupEntry = table[head];
|
||||||
|
deps.forEach(id => expect(table[id]).toEqual(groupEntry));
|
||||||
|
|
||||||
|
const [offset, length] = groupEntry;
|
||||||
|
const groupCode = code.slice(codeOffset + offset, codeOffset + offset + length - 1);
|
||||||
|
expect(groupCode.toString())
|
||||||
|
.toEqual(group.map(m => m.file.code).join('\n'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('reflects section groups in the source map', () => {
|
||||||
|
expect(map.x_facebook_offsets).toEqual([1, 2, 2, 5, 5, 2]);
|
||||||
|
const maps = map.sections.slice(-2);
|
||||||
|
const toplevelOffsets = [2, 5];
|
||||||
|
|
||||||
|
maps.map((groupMap, i) => [groups[i], groupMap]).forEach(([group, groupMap], i) => {
|
||||||
|
const offsets = group.reduce(moduleLineOffsets, [])[0];
|
||||||
|
expect(groupMap).toEqual({
|
||||||
|
map: {
|
||||||
|
version: 3,
|
||||||
|
sections: group.map((module, j) => ({
|
||||||
|
map: module.file.map,
|
||||||
|
offset: {line: offsets[j], column: 0},
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
offset: {line: toplevelOffsets[i], column: 0},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function moduleLineOffsets([offsets = [], line = 0], module) {
|
||||||
|
return [[...offsets, line], line + countLines(module)];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function createRamBundle(preloadedModules = new Set(), ramGroups) {
|
||||||
|
const build = indexedRamBundle.createBuilder(preloadedModules, ramGroups);
|
||||||
const result = build({
|
const result = build({
|
||||||
filename: 'arbitrary/filename.js',
|
filename: 'arbitrary/filename.js',
|
||||||
idForPath,
|
idForPath,
|
||||||
|
@ -149,10 +199,10 @@ function createRamBundle(preloadedModules = new Set()) {
|
||||||
return {code: result.code, map: result.map};
|
return {code: result.code, map: result.map};
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeModule(name, type = 'module', moduleCode = `var ${name};`) {
|
function makeModule(name, deps = [], type = 'module', moduleCode = `var ${name};`) {
|
||||||
const path = `/${name}.js`;
|
const path = makeModulePath(name);
|
||||||
return {
|
return {
|
||||||
dependencies: [],
|
dependencies: deps.map(makeDependency),
|
||||||
file: {
|
file: {
|
||||||
code: type === 'module' ? makeModuleCode(moduleCode) : moduleCode,
|
code: type === 'module' ? makeModuleCode(moduleCode) : moduleCode,
|
||||||
map: type !== 'module'
|
map: type !== 'module'
|
||||||
|
@ -177,6 +227,18 @@ function makeModuleCode(moduleCode) {
|
||||||
return `__d(() => {${moduleCode}})`;
|
return `__d(() => {${moduleCode}})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function makeModulePath(name) {
|
||||||
|
return `/${name}.js`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeDependency(name) {
|
||||||
|
const path = makeModulePath(name);
|
||||||
|
return {
|
||||||
|
id: name,
|
||||||
|
path,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function getId(path) {
|
function getId(path) {
|
||||||
if (path === requireCall.file.path) {
|
if (path === requireCall.file.path) {
|
||||||
return -1;
|
return -1;
|
|
@ -11,8 +11,10 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const buildSourceMapWithMetaData = require('../../../../local-cli/bundle/output/unbundle/build-unbundle-sourcemap-with-metadata.js');
|
const buildSourceMapWithMetaData = require('../../../../local-cli/bundle/output/unbundle/build-unbundle-sourcemap-with-metadata.js');
|
||||||
|
const nullthrows = require('fbjs/lib/nullthrows');
|
||||||
|
|
||||||
const {buildTableAndContents, createModuleGroups} = require('../../../../local-cli/bundle/output/unbundle/as-indexed-file');
|
const {buildTableAndContents, createModuleGroups} = require('../../../../local-cli/bundle/output/unbundle/as-indexed-file');
|
||||||
|
const {createRamBundleGroups} = require('../../Bundler/util');
|
||||||
const {concat} = require('./util');
|
const {concat} = require('./util');
|
||||||
|
|
||||||
import type {FBIndexMap} from '../../lib/SourceMap.js';
|
import type {FBIndexMap} from '../../lib/SourceMap.js';
|
||||||
|
@ -23,12 +25,14 @@ function asIndexedRamBundle({
|
||||||
idForPath,
|
idForPath,
|
||||||
modules,
|
modules,
|
||||||
preloadedModules,
|
preloadedModules,
|
||||||
|
ramGroupHeads,
|
||||||
requireCalls,
|
requireCalls,
|
||||||
}) {
|
}) {
|
||||||
const [startup, deferred] = partition(modules, preloadedModules);
|
const [startup, deferred] = partition(modules, preloadedModules);
|
||||||
const startupModules = Array.from(concat(startup, requireCalls));
|
const startupModules = Array.from(concat(startup, requireCalls));
|
||||||
const deferredModules = deferred.map(m => toModuleTransport(m.file, idForPath));
|
const deferredModules = deferred.map(m => toModuleTransport(m, idForPath));
|
||||||
const moduleGroups = createModuleGroups(new Map(), deferredModules);
|
const ramGroups = createRamBundleGroups(ramGroupHeads || [], deferredModules, subtree);
|
||||||
|
const moduleGroups = createModuleGroups(ramGroups, deferredModules);
|
||||||
|
|
||||||
const tableAndContents = buildTableAndContents(
|
const tableAndContents = buildTableAndContents(
|
||||||
startupModules.map(getModuleCode).join('\n'),
|
startupModules.map(getModuleCode).join('\n'),
|
||||||
|
@ -41,15 +45,17 @@ function asIndexedRamBundle({
|
||||||
code: Buffer.concat(tableAndContents),
|
code: Buffer.concat(tableAndContents),
|
||||||
map: buildSourceMapWithMetaData({
|
map: buildSourceMapWithMetaData({
|
||||||
fixWrapperOffset: false,
|
fixWrapperOffset: false,
|
||||||
startupModules: startupModules.map(m => toModuleTransport(m.file, idForPath)),
|
|
||||||
lazyModules: deferredModules,
|
lazyModules: deferredModules,
|
||||||
|
moduleGroups,
|
||||||
|
startupModules: startupModules.map(m => toModuleTransport(m, idForPath)),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function toModuleTransport(file, idForPath) {
|
function toModuleTransport({dependencies, file}, idForPath) {
|
||||||
return {
|
return {
|
||||||
code: file.code,
|
code: file.code,
|
||||||
|
dependencies,
|
||||||
id: idForPath(file),
|
id: idForPath(file),
|
||||||
map: file.map,
|
map: file.map,
|
||||||
name: file.path,
|
name: file.path,
|
||||||
|
@ -71,7 +77,26 @@ function partition(modules, preloadedModules) {
|
||||||
return [startup, deferred];
|
return [startup, deferred];
|
||||||
}
|
}
|
||||||
|
|
||||||
function createBuilder(preloadedModules: Set<string>): OutputFn<FBIndexMap> {
|
function *subtree(
|
||||||
return x => asIndexedRamBundle({preloadedModules, ...x});
|
moduleTransport,
|
||||||
|
moduleTransportsByPath,
|
||||||
|
seen = new Set(),
|
||||||
|
) {
|
||||||
|
seen.add(moduleTransport.id);
|
||||||
|
for (const {path} of moduleTransport.dependencies) {
|
||||||
|
const dependency = nullthrows(moduleTransportsByPath.get(path));
|
||||||
|
if (!seen.has(dependency.id)) {
|
||||||
|
yield dependency.id;
|
||||||
|
yield *subtree(dependency, moduleTransportsByPath, seen);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBuilder(
|
||||||
|
preloadedModules: Set<string>,
|
||||||
|
ramGroupHeads: ?$ReadOnlyArray<string>,
|
||||||
|
): OutputFn<FBIndexMap> {
|
||||||
|
return x => asIndexedRamBundle({...x, preloadedModules, ramGroupHeads});
|
||||||
|
}
|
||||||
|
|
||||||
exports.createBuilder = createBuilder;
|
exports.createBuilder = createBuilder;
|
||||||
|
|
Loading…
Reference in New Issue