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

View File

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

View File

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