metro: introduce asyncRequire function

Reviewed By: davidaurelio

Differential Revision: D6498107

fbshipit-source-id: a9c4ab634e60f19b7058205eddcd248f57f63500
This commit is contained in:
Jean Lauliac 2017-12-07 10:35:27 -08:00 committed by Facebook Github Bot
parent 636c0ed5c9
commit 2ecf6c9450
3 changed files with 20 additions and 52 deletions

View File

@ -56,13 +56,11 @@ it('collects asynchronous dependencies', () => {
const {dependencies, dependencyMapName} = collectDependencies(ast);
expect(dependencies).toEqual([
{name: 'some/async/module', isAsync: true},
{name: 'BundleSegments', isAsync: false},
{name: 'asyncRequire', isAsync: false},
]);
expect(codeFromAst(ast)).toEqual(
comparableCode(`
require(${dependencyMapName}[1], "BundleSegments").loadForModule(${dependencyMapName}[0]).then(function () {
return require(${dependencyMapName}[0], "some/async/module");
}).then(foo => {});
require(${dependencyMapName}[1], "asyncRequire")(${dependencyMapName}[0]).then(foo => {});
`),
);
});
@ -75,14 +73,12 @@ it('collects mixed dependencies as being sync', () => {
const {dependencies, dependencyMapName} = collectDependencies(ast);
expect(dependencies).toEqual([
{name: 'some/async/module', isAsync: false},
{name: 'BundleSegments', isAsync: false},
{name: 'asyncRequire', isAsync: false},
]);
expect(codeFromAst(ast)).toEqual(
comparableCode(`
const a = require(${dependencyMapName}[0], "some/async/module");
require(${dependencyMapName}[1], "BundleSegments").loadForModule(${dependencyMapName}[0]).then(function () {
return require(${dependencyMapName}[0], "some/async/module");
}).then(foo => {});
require(${dependencyMapName}[1], "asyncRequire")(${dependencyMapName}[0]).then(foo => {});
`),
);
});
@ -95,13 +91,11 @@ it('collects mixed dependencies as being sync; reverse order', () => {
const {dependencies, dependencyMapName} = collectDependencies(ast);
expect(dependencies).toEqual([
{name: 'some/async/module', isAsync: false},
{name: 'BundleSegments', isAsync: false},
{name: 'asyncRequire', isAsync: false},
]);
expect(codeFromAst(ast)).toEqual(
comparableCode(`
require(${dependencyMapName}[1], "BundleSegments").loadForModule(${dependencyMapName}[0]).then(function () {
return require(${dependencyMapName}[0], "some/async/module");
}).then(foo => {});
require(${dependencyMapName}[1], "asyncRequire")(${dependencyMapName}[0]).then(foo => {});
const a = require(${dependencyMapName}[0], "some/async/module");
`),
);

View File

@ -23,7 +23,7 @@ const DEP_MAP_NAME = 'arbitrary';
const DEPS = [
{name: 'b/lib/a', isAsync: false},
{name: 'do', isAsync: false},
{name: 'BundleSegments', isAsync: false},
{name: 'asyncRequire', isAsync: false},
{name: 'some/async/module', isAsync: true},
{name: 'setup/something', isAsync: false},
];
@ -32,9 +32,7 @@ it('returns dependencies from the transformed AST', () => {
const ast = astFromCode(`
const a = require(${DEP_MAP_NAME}[0], 'b/lib/a');
exports.do = () => require(${DEP_MAP_NAME}[1], "do");
require(${DEP_MAP_NAME}[2], "BundleSegments").loadForModule(${DEP_MAP_NAME}[3]).then(function () {
return require(${DEP_MAP_NAME}[3], "some/async/module");
}).then(foo => {});
require(${DEP_MAP_NAME}[2], "asyncRequire")(${DEP_MAP_NAME}[3]).then(foo => {});
if (!something) {
require(${DEP_MAP_NAME}[4], "setup/something");
}
@ -45,9 +43,7 @@ it('returns dependencies from the transformed AST', () => {
comparableCode(`
const a = require(${DEP_MAP_NAME}[0]);
exports.do = () => require(${DEP_MAP_NAME}[1]);
require(${DEP_MAP_NAME}[2]).loadForModule(${DEP_MAP_NAME}[3]).then(function () {
return require(${DEP_MAP_NAME}[3]);
}).then(foo => {});
require(${DEP_MAP_NAME}[2])(${DEP_MAP_NAME}[3]).then(foo => {});
if (!something) {
require(${DEP_MAP_NAME}[4]);
}
@ -66,20 +62,16 @@ it('strips unused dependencies and translates require() calls', () => {
it('strips unused dependencies and translates loadForModule() calls', () => {
const ast = astFromCode(`
require(${DEP_MAP_NAME}[2], "BundleSegments").loadForModule(${DEP_MAP_NAME}[3]).then(function () {
return require(${DEP_MAP_NAME}[3], "some/async/module");
}).then(foo => {});
require(${DEP_MAP_NAME}[2], "asyncRequire")(${DEP_MAP_NAME}[3]).then(foo => {});
`);
const dependencies = optimizeDependencies(ast, DEPS, DEP_MAP_NAME);
expect(dependencies).toEqual([
{name: 'BundleSegments', isAsync: false},
{name: 'asyncRequire', isAsync: false},
{name: 'some/async/module', isAsync: true},
]);
expect(codeFromAst(ast)).toEqual(
comparableCode(`
require(${DEP_MAP_NAME}[0]).loadForModule(${DEP_MAP_NAME}[1]).then(function () {
return require(${DEP_MAP_NAME}[1]);
}).then(foo => {});
require(${DEP_MAP_NAME}[0])(${DEP_MAP_NAME}[1]).then(foo => {});
`),
);
});
@ -87,27 +79,23 @@ it('strips unused dependencies and translates loadForModule() calls', () => {
it('strips unused dependencies and translates loadForModule() calls; different ordering', () => {
const ast = astFromCode(`
require(${DEP_MAP_NAME}[0], 'something/else');
require(${DEP_MAP_NAME}[2], "BundleSegments").loadForModule(${DEP_MAP_NAME}[1]).then(function () {
return require(${DEP_MAP_NAME}[1], "some/async/module");
}).then(foo => {});
require(${DEP_MAP_NAME}[2], "asyncRequire")(${DEP_MAP_NAME}[1]).then(foo => {});
`);
const deps = [
{name: 'something/else', isAsync: false},
{name: 'some/async/module', isAsync: true},
{name: 'BundleSegments', isAsync: false},
{name: 'asyncRequire', isAsync: false},
];
const dependencies = optimizeDependencies(ast, deps, DEP_MAP_NAME);
expect(dependencies).toEqual([
{name: 'something/else', isAsync: false},
{name: 'BundleSegments', isAsync: false},
{name: 'asyncRequire', isAsync: false},
{name: 'some/async/module', isAsync: true},
]);
expect(codeFromAst(ast)).toEqual(
comparableCode(`
require(${DEP_MAP_NAME}[0]);
require(${DEP_MAP_NAME}[1]).loadForModule(${DEP_MAP_NAME}[2]).then(function () {
return require(${DEP_MAP_NAME}[2]);
}).then(foo => {});
require(${DEP_MAP_NAME}[1])(${DEP_MAP_NAME}[2]).then(foo => {});
`),
);
});

View File

@ -54,8 +54,7 @@ function collectDependencies(ast: Ast): CollectedDependencies {
return;
}
if (node.callee.type === 'Import') {
const reqNode = processImportCall(context, path, node, depMapIdent);
visited.add(reqNode);
processImportCall(context, path, node, depMapIdent);
return;
}
if (isRequireCall(node.callee)) {
@ -81,24 +80,17 @@ function processImportCall(context, path, node, depMapIdent) {
const index = assignDependencyIndex(context, name, 'import');
const mapLookup = createDepMapLookup(depMapIdent, index);
const newImport = makeAsyncRequire({
REQUIRE_ARGS: createRequireArgs(mapLookup, nameLiteral),
MODULE_ID: mapLookup,
BUNDLE_SEGMENTS_PATH: {
type: 'StringLiteral',
value: 'BundleSegments',
},
ASYNC_REQUIRE_PATH: {type: 'StringLiteral', value: 'asyncRequire'},
});
path.replaceWith(newImport);
// This is the inner require() call. We return it so it
// gets marked as already visited.
return newImport.expression.arguments[0].body.body[0].argument;
}
function processRequireCall(context, node, depMapIdent) {
const [nameLiteral, name] = getModuleNameFromCallArgs(node);
const index = assignDependencyIndex(context, name, 'require');
const mapLookup = createDepMapLookup(depMapIdent, index);
node.arguments = createRequireArgs(mapLookup, nameLiteral);
node.arguments = [mapLookup, nameLiteral];
return node;
}
@ -154,15 +146,9 @@ function createDepMapLookup(depMapIndent, index: number) {
}
const makeAsyncRequire = babelTemplate(
`require(BUNDLE_SEGMENTS_PATH).loadForModule(MODULE_ID).then(
function() { return require(REQUIRE_ARGS); }
)`,
`require(ASYNC_REQUIRE_PATH)(MODULE_ID)`,
);
function createRequireArgs(mapLookup, moduleNameLiteral) {
return [mapLookup, moduleNameLiteral];
}
function invalidRequireOf(type, node) {
return new InvalidRequireCallError(
`Calls to ${type}() expect exactly 1 string literal argument, ` +