mirror of https://github.com/status-im/metro.git
react-native: BundleSegments
Reviewed By: davidaurelio Differential Revision: D6210177 fbshipit-source-id: c39e4118389a9739e9e70ba34feb5d335a7f2546
This commit is contained in:
parent
1ec323bbd5
commit
3129532cd9
|
@ -36,13 +36,21 @@ export type TransformedCode = {
|
||||||
map?: ?CompactRawMappings,
|
map?: ?CompactRawMappings,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Transform<ExtraOptions: {}> = ({|
|
export type TransformArgs<ExtraOptions: {}> = {|
|
||||||
filename: string,
|
filename: string,
|
||||||
localPath: string,
|
localPath: string,
|
||||||
options: ExtraOptions & TransformOptions,
|
options: ExtraOptions & TransformOptions,
|
||||||
plugins?: BabelPlugins,
|
plugins?: BabelPlugins,
|
||||||
src: string,
|
src: string,
|
||||||
|}) => {ast: ?Ast, code: string, map: ?MappingsMap | RawMappings};
|
|};
|
||||||
|
export type TransformResults = {
|
||||||
|
ast: ?Ast,
|
||||||
|
code: string,
|
||||||
|
map: ?MappingsMap | RawMappings,
|
||||||
|
};
|
||||||
|
export type Transform<ExtraOptions: {}> = (
|
||||||
|
TransformArgs<ExtraOptions>,
|
||||||
|
) => TransformResults;
|
||||||
|
|
||||||
export type Transformer<ExtraOptions: {} = {}> = {
|
export type Transformer<ExtraOptions: {} = {}> = {
|
||||||
transform: Transform<ExtraOptions>,
|
transform: Transform<ExtraOptions>,
|
||||||
|
|
|
@ -13,15 +13,16 @@
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const babylon = require('babylon');
|
||||||
const collectDependencies = require('../collect-dependencies');
|
const collectDependencies = require('../collect-dependencies');
|
||||||
const astFromCode = require('babylon').parse;
|
|
||||||
const {codeFromAst, comparableCode} = require('../../test-helpers');
|
const {codeFromAst, comparableCode} = require('../../test-helpers');
|
||||||
|
|
||||||
const {any} = expect;
|
const {any} = expect;
|
||||||
|
|
||||||
const {InvalidRequireCallError} = collectDependencies;
|
const {InvalidRequireCallError} = collectDependencies;
|
||||||
|
|
||||||
describe('dependency collection from ASTs:', () => {
|
describe('dependency collection from ASTs', () => {
|
||||||
it('collects dependency identifiers from the code', () => {
|
it('collects dependency identifiers from the code', () => {
|
||||||
const ast = astFromCode(`
|
const ast = astFromCode(`
|
||||||
const a = require('b/lib/a');
|
const a = require('b/lib/a');
|
||||||
|
@ -42,13 +43,14 @@ describe('dependency collection from ASTs:', () => {
|
||||||
const ast = astFromCode(`
|
const ast = astFromCode(`
|
||||||
const a = require('b/lib/a');
|
const a = require('b/lib/a');
|
||||||
if (!something) {
|
if (!something) {
|
||||||
require.async("some/async/module").then(foo => {});
|
import("some/async/module").then(foo => {});
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
expect(collectDependencies(ast).dependencies).toEqual([
|
expect(collectDependencies(ast).dependencies).toEqual([
|
||||||
'b/lib/a',
|
'b/lib/a',
|
||||||
'some/async/module',
|
'some/async/module',
|
||||||
|
'BundleSegments',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -107,7 +109,7 @@ describe('dependency collection from ASTs:', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Dependency collection from optimized ASTs:', () => {
|
describe('Dependency collection from optimized ASTs', () => {
|
||||||
const dependencyMapName = 'arbitrary';
|
const dependencyMapName = 'arbitrary';
|
||||||
const {forOptimization} = collectDependencies;
|
const {forOptimization} = collectDependencies;
|
||||||
let ast, names;
|
let ast, names;
|
||||||
|
@ -116,12 +118,11 @@ describe('Dependency collection from optimized ASTs:', () => {
|
||||||
ast = astFromCode(`
|
ast = astFromCode(`
|
||||||
const a = require(${dependencyMapName}[0], 'b/lib/a');
|
const a = require(${dependencyMapName}[0], 'b/lib/a');
|
||||||
exports.do = () => require(${dependencyMapName}[1], "do");
|
exports.do = () => require(${dependencyMapName}[1], "do");
|
||||||
require.async(${dependencyMapName}[2], 'some/async/module').then(foo => {});
|
|
||||||
if (!something) {
|
if (!something) {
|
||||||
require(${dependencyMapName}[3], "setup/something");
|
require(${dependencyMapName}[2], "setup/something");
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
names = ['b/lib/a', 'do', 'some/async/module', 'setup/something'];
|
names = ['b/lib/a', 'do', 'setup/something'];
|
||||||
});
|
});
|
||||||
|
|
||||||
it('passes the `dependencyMapName` through', () => {
|
it('passes the `dependencyMapName` through', () => {
|
||||||
|
@ -146,11 +147,14 @@ describe('Dependency collection from optimized ASTs:', () => {
|
||||||
comparableCode(`
|
comparableCode(`
|
||||||
const a = require(${dependencyMapName}[0]);
|
const a = require(${dependencyMapName}[0]);
|
||||||
exports.do = () => require(${dependencyMapName}[1]);
|
exports.do = () => require(${dependencyMapName}[1]);
|
||||||
require.async(${dependencyMapName}[2]).then(foo => {});
|
|
||||||
if (!something) {
|
if (!something) {
|
||||||
require(${dependencyMapName}[3]);
|
require(${dependencyMapName}[2]);
|
||||||
}
|
}
|
||||||
`),
|
`),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function astFromCode(code) {
|
||||||
|
return babylon.parse(code, {plugins: ['dynamicImport']});
|
||||||
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const babelTemplate = require('babel-template');
|
||||||
const nullthrows = require('fbjs/lib/nullthrows');
|
const nullthrows = require('fbjs/lib/nullthrows');
|
||||||
|
|
||||||
const {traverse, types} = require('babel-core');
|
const {traverse, types} = require('babel-core');
|
||||||
|
@ -22,6 +23,7 @@ type AST = Object;
|
||||||
class Replacement {
|
class Replacement {
|
||||||
nameToIndex: Map<string, number>;
|
nameToIndex: Map<string, number>;
|
||||||
nextIndex: number;
|
nextIndex: number;
|
||||||
|
replaceImports = true;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.nameToIndex = new Map();
|
this.nameToIndex = new Map();
|
||||||
|
@ -74,6 +76,7 @@ function getInvalidProdRequireMessage(node) {
|
||||||
class ProdReplacement {
|
class ProdReplacement {
|
||||||
replacement: Replacement;
|
replacement: Replacement;
|
||||||
names: Array<string>;
|
names: Array<string>;
|
||||||
|
replaceImports = false;
|
||||||
|
|
||||||
constructor(names) {
|
constructor(names) {
|
||||||
this.replacement = new Replacement();
|
this.replacement = new Replacement();
|
||||||
|
@ -129,19 +132,15 @@ function createMapLookup(dependencyMapIdentifier, propertyIdentifier) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const makeAsyncRequire = babelTemplate(
|
||||||
|
`require(BUNDLE_SEGMENTS_PATH).loadForModule(MODULE_ID).then(
|
||||||
|
() => require(MODULE_PATH),
|
||||||
|
)`,
|
||||||
|
);
|
||||||
|
|
||||||
function collectDependencies(ast, replacement, dependencyMapIdentifier) {
|
function collectDependencies(ast, replacement, dependencyMapIdentifier) {
|
||||||
const visited = new WeakSet();
|
const visited = new WeakSet();
|
||||||
const traversalState = {dependencyMapIdentifier};
|
const traversalState = {dependencyMapIdentifier};
|
||||||
function processRequireCall(node, state, isAsync) {
|
|
||||||
const arg = replacement.getRequireCallArg(node);
|
|
||||||
const index = replacement.getIndex(arg);
|
|
||||||
node.arguments = replacement.makeArgs(
|
|
||||||
types.numericLiteral(index),
|
|
||||||
arg,
|
|
||||||
state.dependencyMapIdentifier,
|
|
||||||
);
|
|
||||||
visited.add(node);
|
|
||||||
}
|
|
||||||
traverse(
|
traverse(
|
||||||
ast,
|
ast,
|
||||||
{
|
{
|
||||||
|
@ -152,16 +151,27 @@ function collectDependencies(ast, replacement, dependencyMapIdentifier) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
CallExpression(path, state) {
|
CallExpression(path, state) {
|
||||||
const node = path.node;
|
const node = path.node;
|
||||||
|
if (replacement.replaceImports && node.callee.type === 'Import') {
|
||||||
|
processImportCall(path, node, replacement, state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (visited.has(node)) {
|
if (visited.has(node)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isRequireCall(node.callee)) {
|
if (!isRequireCall(node.callee)) {
|
||||||
processRequireCall(node, state, false);
|
return;
|
||||||
} else if (isAsyncRequireCall(node.callee)) {
|
|
||||||
processRequireCall(node, state, true);
|
|
||||||
}
|
}
|
||||||
|
const arg = replacement.getRequireCallArg(node);
|
||||||
|
const index = replacement.getIndex(arg);
|
||||||
|
node.arguments = replacement.makeArgs(
|
||||||
|
types.numericLiteral(index),
|
||||||
|
arg,
|
||||||
|
state.dependencyMapIdentifier,
|
||||||
|
);
|
||||||
|
visited.add(node);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
|
@ -174,6 +184,31 @@ function collectDependencies(ast, replacement, dependencyMapIdentifier) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function processImportCall(path, node, replacement, state) {
|
||||||
|
const args = node.arguments;
|
||||||
|
if (args.length !== 1 || !isLiteralString(args[0])) {
|
||||||
|
throw new InvalidRequireCallError(
|
||||||
|
'Calls to import() expect exactly 1 string literal argument, ' +
|
||||||
|
'but this was found: ' +
|
||||||
|
prettyPrint(node).code,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const modulePath = args[0];
|
||||||
|
const index = replacement.getIndex(modulePath);
|
||||||
|
const newImport = makeAsyncRequire({
|
||||||
|
MODULE_PATH: modulePath,
|
||||||
|
MODULE_ID: createMapLookup(
|
||||||
|
state.dependencyMapIdentifier,
|
||||||
|
types.numericLiteral(index),
|
||||||
|
),
|
||||||
|
BUNDLE_SEGMENTS_PATH: {
|
||||||
|
type: 'StringLiteral',
|
||||||
|
value: 'BundleSegments',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
path.replaceWith(newImport);
|
||||||
|
}
|
||||||
|
|
||||||
function isLiteralString(node) {
|
function isLiteralString(node) {
|
||||||
return (
|
return (
|
||||||
node.type === 'StringLiteral' ||
|
node.type === 'StringLiteral' ||
|
||||||
|
@ -185,15 +220,6 @@ function isRequireCall(callee) {
|
||||||
return callee.type === 'Identifier' && callee.name === 'require';
|
return callee.type === 'Identifier' && callee.name === 'require';
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAsyncRequireCall(callee) {
|
|
||||||
return (
|
|
||||||
callee.type === 'MemberExpression' &&
|
|
||||||
!callee.computed &&
|
|
||||||
callee.property.name === 'async' &&
|
|
||||||
isRequireCall(callee.object)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class InvalidRequireCallError extends Error {
|
class InvalidRequireCallError extends Error {
|
||||||
constructor(message) {
|
constructor(message) {
|
||||||
super(message);
|
super(message);
|
||||||
|
@ -201,7 +227,7 @@ class InvalidRequireCallError extends Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
const xp = (module.exports = (ast: AST) =>
|
const xp = (module.exports = (ast: AST) =>
|
||||||
collectDependencies(ast, new Replacement()));
|
collectDependencies(ast, new Replacement(), undefined));
|
||||||
|
|
||||||
xp.forOptimization = (
|
xp.forOptimization = (
|
||||||
ast: AST,
|
ast: AST,
|
||||||
|
|
|
@ -138,12 +138,20 @@ function guardedLoadModule(moduleId: ModuleID, module) {
|
||||||
const ID_MASK_SHIFT = 16;
|
const ID_MASK_SHIFT = 16;
|
||||||
const LOCAL_ID_MASK = ~0 >>> ID_MASK_SHIFT;
|
const LOCAL_ID_MASK = ~0 >>> ID_MASK_SHIFT;
|
||||||
|
|
||||||
|
function unpackModuleId(
|
||||||
|
moduleId: ModuleID,
|
||||||
|
): {segmentId: number, localId: number} {
|
||||||
|
const segmentId = moduleId >>> ID_MASK_SHIFT;
|
||||||
|
const localId = moduleId & LOCAL_ID_MASK;
|
||||||
|
return {segmentId, localId};
|
||||||
|
}
|
||||||
|
require.unpackModuleId = unpackModuleId;
|
||||||
|
|
||||||
function loadModuleImplementation(moduleId, module) {
|
function loadModuleImplementation(moduleId, module) {
|
||||||
const nativeRequire = global.nativeRequire;
|
const nativeRequire = global.nativeRequire;
|
||||||
if (!module && nativeRequire) {
|
if (!module && nativeRequire) {
|
||||||
const bundleId = moduleId >>> ID_MASK_SHIFT;
|
const {segmentId, localId} = unpackModuleId(moduleId);
|
||||||
const localId = moduleId & LOCAL_ID_MASK;
|
nativeRequire(localId, segmentId);
|
||||||
nativeRequire(localId, bundleId);
|
|
||||||
module = modules[moduleId];
|
module = modules[moduleId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,12 +56,21 @@ function guardedLoadModule(moduleId, module) {
|
||||||
var ID_MASK_SHIFT = 16;
|
var ID_MASK_SHIFT = 16;
|
||||||
var LOCAL_ID_MASK = ~0 >>> ID_MASK_SHIFT;
|
var LOCAL_ID_MASK = ~0 >>> ID_MASK_SHIFT;
|
||||||
|
|
||||||
|
function unpackModuleId(moduleId) {
|
||||||
|
var segmentId = moduleId >>> ID_MASK_SHIFT;
|
||||||
|
var localId = moduleId & LOCAL_ID_MASK;
|
||||||
|
return { segmentId: segmentId, localId: localId };
|
||||||
|
}
|
||||||
|
_require.unpackModuleId = unpackModuleId;
|
||||||
|
|
||||||
function loadModuleImplementation(moduleId, module) {
|
function loadModuleImplementation(moduleId, module) {
|
||||||
var nativeRequire = global.nativeRequire;
|
var nativeRequire = global.nativeRequire;
|
||||||
if (!module && nativeRequire) {
|
if (!module && nativeRequire) {
|
||||||
var bundleId = moduleId >>> ID_MASK_SHIFT;
|
var _unpackModuleId = unpackModuleId(moduleId),
|
||||||
var localId = moduleId & LOCAL_ID_MASK;
|
segmentId = _unpackModuleId.segmentId,
|
||||||
nativeRequire(localId, bundleId);
|
localId = _unpackModuleId.localId;
|
||||||
|
|
||||||
|
nativeRequire(localId, segmentId);
|
||||||
module = modules[moduleId];
|
module = modules[moduleId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,12 +226,21 @@ function guardedLoadModule(moduleId, module) {
|
||||||
var ID_MASK_SHIFT = 16;
|
var ID_MASK_SHIFT = 16;
|
||||||
var LOCAL_ID_MASK = ~0 >>> ID_MASK_SHIFT;
|
var LOCAL_ID_MASK = ~0 >>> ID_MASK_SHIFT;
|
||||||
|
|
||||||
|
function unpackModuleId(moduleId) {
|
||||||
|
var segmentId = moduleId >>> ID_MASK_SHIFT;
|
||||||
|
var localId = moduleId & LOCAL_ID_MASK;
|
||||||
|
return { segmentId: segmentId, localId: localId };
|
||||||
|
}
|
||||||
|
_require.unpackModuleId = unpackModuleId;
|
||||||
|
|
||||||
function loadModuleImplementation(moduleId, module) {
|
function loadModuleImplementation(moduleId, module) {
|
||||||
var nativeRequire = global.nativeRequire;
|
var nativeRequire = global.nativeRequire;
|
||||||
if (!module && nativeRequire) {
|
if (!module && nativeRequire) {
|
||||||
var bundleId = moduleId >>> ID_MASK_SHIFT;
|
var _unpackModuleId = unpackModuleId(moduleId),
|
||||||
var localId = moduleId & LOCAL_ID_MASK;
|
segmentId = _unpackModuleId.segmentId,
|
||||||
nativeRequire(localId, bundleId);
|
localId = _unpackModuleId.localId;
|
||||||
|
|
||||||
|
nativeRequire(localId, segmentId);
|
||||||
module = modules[moduleId];
|
module = modules[moduleId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue