metro: collectDependencies: do not hardwire dependency on asyncRequire

Reviewed By: rafeca

Differential Revision: D6773002

fbshipit-source-id: a55b63f6bf20f467496ffe1d18c663b87f5d7ba6
This commit is contained in:
Jean Lauliac 2018-01-29 08:55:05 -08:00 committed by Facebook Github Bot
parent 47519ec56b
commit 4b7371d732
17 changed files with 99 additions and 18 deletions

View File

@ -81,6 +81,7 @@ export type PostProcessBundleSourcemap = ({
export type Options = {| export type Options = {|
+assetExts: Array<string>, +assetExts: Array<string>,
+assetRegistryPath: string, +assetRegistryPath: string,
+asyncRequireModulePath: string,
+blacklistRE?: RegExp, +blacklistRE?: RegExp,
+cacheVersion: string, +cacheVersion: string,
+dynamicDepsInPackages: DynamicRequiresBehavior, +dynamicDepsInPackages: DynamicRequiresBehavior,
@ -120,6 +121,7 @@ class Bundler {
opts.projectRoots.forEach(verifyRootExists); opts.projectRoots.forEach(verifyRootExists);
this._transformer = new Transformer({ this._transformer = new Transformer({
asyncRequireModulePath: opts.asyncRequireModulePath,
maxWorkers: opts.maxWorkers, maxWorkers: opts.maxWorkers,
reporters: { reporters: {
stdoutChunk: chunk => stdoutChunk: chunk =>
@ -139,6 +141,7 @@ class Bundler {
extraNodeModules: opts.extraNodeModules, extraNodeModules: opts.extraNodeModules,
getPolyfills: opts.getPolyfills, getPolyfills: opts.getPolyfills,
getTransformCacheKey: getTransformCacheKeyFn({ getTransformCacheKey: getTransformCacheKeyFn({
asyncRequireModulePath: opts.asyncRequireModulePath,
cacheVersion: opts.cacheVersion, cacheVersion: opts.cacheVersion,
dynamicDepsInPackages: opts.dynamicDepsInPackages, dynamicDepsInPackages: opts.dynamicDepsInPackages,
projectRoots: opts.projectRoots, projectRoots: opts.projectRoots,

View File

@ -27,6 +27,7 @@ describe('Transformer', function() {
const transformModulePath = __filename; const transformModulePath = __filename;
const opts = { const opts = {
asyncRequireModulePath: 'asyncRequire',
maxWorkers: 4, maxWorkers: 4,
reporters: {}, reporters: {},
transformModulePath, transformModulePath,
@ -82,6 +83,7 @@ describe('Transformer', function() {
transformOptions, transformOptions,
[], [],
'', '',
'asyncRequire',
'reject', 'reject',
); );
}); });

View File

@ -38,16 +38,19 @@ type Reporters = {
module.exports = class Transformer { module.exports = class Transformer {
_worker: WorkerInterface; _worker: WorkerInterface;
_transformModulePath: string; _transformModulePath: string;
_asyncRequireModulePath: string;
_dynamicDepsInPackages: DynamicRequiresBehavior; _dynamicDepsInPackages: DynamicRequiresBehavior;
constructor(options: {| constructor(options: {|
+maxWorkers: number, +maxWorkers: number,
+reporters: Reporters, +reporters: Reporters,
+transformModulePath: string, +transformModulePath: string,
+asyncRequireModulePath: string,
+dynamicDepsInPackages: DynamicRequiresBehavior, +dynamicDepsInPackages: DynamicRequiresBehavior,
+workerPath: ?string, +workerPath: ?string,
|}) { |}) {
this._transformModulePath = options.transformModulePath; this._transformModulePath = options.transformModulePath;
this._asyncRequireModulePath = options.asyncRequireModulePath;
this._dynamicDepsInPackages = options.dynamicDepsInPackages; this._dynamicDepsInPackages = options.dynamicDepsInPackages;
const {workerPath = require.resolve('./worker')} = options; const {workerPath = require.resolve('./worker')} = options;
@ -108,6 +111,7 @@ module.exports = class Transformer {
options, options,
assetExts, assetExts,
assetRegistryPath, assetRegistryPath,
this._asyncRequireModulePath,
this._dynamicDepsInPackages, this._dynamicDepsInPackages,
); );

View File

@ -34,6 +34,7 @@ describe('code transformation worker:', () => {
}, },
[], [],
'', '',
'asyncRequire',
'reject', 'reject',
); );
@ -61,6 +62,7 @@ describe('code transformation worker:', () => {
}, },
[], [],
'', '',
'asyncRequire',
'reject', 'reject',
); );
@ -93,6 +95,7 @@ describe('code transformation worker:', () => {
}, },
[], [],
'', '',
'asyncRequire',
'reject', 'reject',
); );
@ -133,6 +136,7 @@ describe('code transformation worker:', () => {
}, },
[], [],
'', '',
'asyncRequire',
'reject', 'reject',
); );
throw new Error('should not reach this'); throw new Error('should not reach this');
@ -157,6 +161,7 @@ describe('code transformation worker:', () => {
}, },
[], [],
'', '',
'asyncRequire',
'throwAtRuntime', 'throwAtRuntime',
); );
}); });

View File

@ -98,6 +98,7 @@ function postTransform(
isScript: boolean, isScript: boolean,
options: Options, options: Options,
transformFileStartLogEntry: LogEntry, transformFileStartLogEntry: LogEntry,
asyncRequireModulePath: string,
dynamicDepsInPackages: DynamicRequiresBehavior, dynamicDepsInPackages: DynamicRequiresBehavior,
receivedAst: ?Ast, receivedAst: ?Ast,
): Data { ): Data {
@ -128,6 +129,7 @@ function postTransform(
let dependencyMapName; let dependencyMapName;
try { try {
const opts = { const opts = {
asyncRequireModulePath,
dynamicRequires: getDynamicDepsBehavior( dynamicRequires: getDynamicDepsBehavior(
dynamicDepsInPackages, dynamicDepsInPackages,
filename, filename,
@ -197,6 +199,7 @@ function transformCode(
options: Options, options: Options,
assetExts: $ReadOnlyArray<string>, assetExts: $ReadOnlyArray<string>,
assetRegistryPath: string, assetRegistryPath: string,
asyncRequireModulePath: string,
dynamicDepsInPackages: DynamicRequiresBehavior, dynamicDepsInPackages: DynamicRequiresBehavior,
): Data | Promise<Data> { ): Data | Promise<Data> {
const isJson = filename.endsWith('.json'); const isJson = filename.endsWith('.json');
@ -243,6 +246,7 @@ function transformCode(
isScript, isScript,
options, options,
transformFileStartLogEntry, transformFileStartLogEntry,
asyncRequireModulePath,
dynamicDepsInPackages, dynamicDepsInPackages,
]; ];

View File

@ -21,7 +21,10 @@ const {codeFromAst, comparableCode} = require('../../test-helpers');
const {any} = expect; const {any} = expect;
const {InvalidRequireCallError} = collectDependencies; const {InvalidRequireCallError} = collectDependencies;
const opts = {dynamicRequires: 'reject'}; const opts = {
asyncRequireModulePath: 'asyncRequire',
dynamicRequires: 'reject',
};
it('collects unique dependency identifiers and transforms the AST', () => { it('collects unique dependency identifiers and transforms the AST', () => {
const ast = astFromCode(` const ast = astFromCode(`
@ -193,7 +196,10 @@ describe('Evaluating static arguments', () => {
it('throws at runtime when requiring non-strings with special option', () => { it('throws at runtime when requiring non-strings with special option', () => {
const ast = astFromCode('require(1)'); const ast = astFromCode('require(1)');
const opts = {dynamicRequires: 'throwAtRuntime'}; const opts = {
asyncRequireModulePath: 'asyncRequire',
dynamicRequires: 'throwAtRuntime',
};
const {dependencies} = collectDependencies(ast, opts); const {dependencies} = collectDependencies(ast, opts);
expect(dependencies).toEqual([]); expect(dependencies).toEqual([]);
expect(codeFromAst(ast)).toEqual( expect(codeFromAst(ast)).toEqual(

View File

@ -27,6 +27,7 @@ const {objectContaining} = jasmine;
describe('optimizing JS modules', () => { describe('optimizing JS modules', () => {
const filename = 'arbitrary/file.js'; const filename = 'arbitrary/file.js';
const sourceExts = new Set(['js', 'json']); const sourceExts = new Set(['js', 'json']);
const asyncRequireModulePath = 'asyncRequire';
const optimizationOptions = { const optimizationOptions = {
dev: false, dev: false,
platform: 'android', platform: 'android',
@ -43,7 +44,7 @@ describe('optimizing JS modules', () => {
let transformResult; let transformResult;
beforeAll(() => { beforeAll(() => {
const trOpts = {filename, sourceExts, transformer}; const trOpts = {asyncRequireModulePath, filename, sourceExts, transformer};
const result = transformModule(originalCode, trOpts); const result = transformModule(originalCode, trOpts);
invariant(result.type === 'code', 'result must be code'); invariant(result.type === 'code', 'result must be code');
transformResult = new Buffer( transformResult = new Buffer(

View File

@ -33,6 +33,7 @@ jest.mock('image-size', () => buffer => {
describe('transforming JS modules:', () => { describe('transforming JS modules:', () => {
const filename = 'arbitrary.js'; const filename = 'arbitrary.js';
const sourceExts = new Set(['js', 'json']); const sourceExts = new Set(['js', 'json']);
const asyncRequireModulePath = 'asyncRequire';
let transformer; let transformer;
@ -47,6 +48,7 @@ describe('transforming JS modules:', () => {
const {bodyAst, sourceCode, transformedCode} = createTestData(); const {bodyAst, sourceCode, transformedCode} = createTestData();
const options = (variants?: TransformVariants) => ({ const options = (variants?: TransformVariants) => ({
asyncRequireModulePath,
filename, filename,
sourceExts, sourceExts,
transformer, transformer,

View File

@ -21,12 +21,16 @@ const prettyPrint = require('babel-generator').default;
import type {TransformResultDependency} from '../types.flow'; import type {TransformResultDependency} from '../types.flow';
export type DynamicRequiresBehavior = 'throwAtRuntime' | 'reject'; export type DynamicRequiresBehavior = 'throwAtRuntime' | 'reject';
type Options = {|+dynamicRequires: DynamicRequiresBehavior|}; type Options = {|
+dynamicRequires: DynamicRequiresBehavior,
+asyncRequireModulePath: string,
|};
type Context = { type Context = {
nameToIndex: Map<string, number>, +asyncRequireModulePath: string,
dependencies: Array<{|+name: string, isAsync: boolean|}>,
+dynamicRequires: DynamicRequiresBehavior, +dynamicRequires: DynamicRequiresBehavior,
dependencies: Array<{|+name: string, isAsync: boolean|}>,
nameToIndex: Map<string, number>,
}; };
type CollectedDependencies = {| type CollectedDependencies = {|
@ -47,8 +51,13 @@ function collectDependencies(
options: Options, options: Options,
): CollectedDependencies { ): CollectedDependencies {
const visited = new WeakSet(); const visited = new WeakSet();
const {dynamicRequires} = options; const {asyncRequireModulePath, dynamicRequires} = options;
const context = {nameToIndex: new Map(), dependencies: [], dynamicRequires}; const context = {
asyncRequireModulePath,
dynamicRequires,
dependencies: [],
nameToIndex: new Map(),
};
const visitor = { const visitor = {
Program(path, state) { Program(path, state) {
state.dependencyMapIdentifier = path.scope.generateUidIdentifier( state.dependencyMapIdentifier = path.scope.generateUidIdentifier(
@ -90,9 +99,10 @@ 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 {asyncRequireModulePath} = context;
const newImport = makeAsyncRequire({ const newImport = makeAsyncRequire({
MODULE_ID: mapLookup, MODULE_ID: mapLookup,
ASYNC_REQUIRE_PATH: {type: 'StringLiteral', value: 'asyncRequire'}, ASYNC_REQUIRE_PATH: {type: 'StringLiteral', value: asyncRequireModulePath},
}); });
path.replaceWith(newImport); path.replaceWith(newImport);
} }

View File

@ -39,6 +39,7 @@ import type {
import type {Ast} from 'babel-core'; import type {Ast} from 'babel-core';
export type TransformOptions<ExtraOptions> = {| export type TransformOptions<ExtraOptions> = {|
+asyncRequireModulePath: string,
filename: string, filename: string,
hasteImpl?: HasteImpl, hasteImpl?: HasteImpl,
polyfill?: boolean, polyfill?: boolean,
@ -76,7 +77,7 @@ function transformModule(
return {type: 'unknown'}; return {type: 'unknown'};
} }
const code = content.toString('utf8'); const sourceCode = content.toString('utf8');
const {filename, transformer, polyfill, variants = defaultVariants} = options; const {filename, transformer, polyfill, variants = defaultVariants} = options;
const transformed: {[key: string]: TransformResult} = {}; const transformed: {[key: string]: TransformResult} = {};
@ -85,15 +86,22 @@ function transformModule(
filename, filename,
localPath: filename, localPath: filename,
options: {...defaultTransformOptions, ...variants[variantName]}, options: {...defaultTransformOptions, ...variants[variantName]},
src: code, src: sourceCode,
}); });
invariant(ast != null, 'ast required from the transform results'); invariant(ast != null, 'ast required from the transform results');
transformed[variantName] = makeResult(ast, filename, code, polyfill); const {asyncRequireModulePath} = options;
transformed[variantName] = makeResult({
ast,
asyncRequireModulePath,
filename,
isPolyfill: polyfill || false,
sourceCode,
});
} }
let hasteID = null; let hasteID = null;
if (filename.indexOf(NODE_MODULES) === -1 && !polyfill) { if (filename.indexOf(NODE_MODULES) === -1 && !polyfill) {
hasteID = docblock.parse(docblock.extract(code)).providesModule; hasteID = docblock.parse(docblock.extract(sourceCode)).providesModule;
if (options.hasteImpl) { if (options.hasteImpl) {
if (options.hasteImpl.enforceHasteNameMatches) { if (options.hasteImpl.enforceHasteNameMatches) {
options.hasteImpl.enforceHasteNameMatches(filename, hasteID); options.hasteImpl.enforceHasteNameMatches(filename, hasteID);
@ -182,17 +190,25 @@ function getAssetSize(
return {width, height}; return {width, height};
} }
function makeResult(ast: Ast, filename, sourceCode, isPolyfill = false) { function makeResult(options: {|
+ast: Ast,
+asyncRequireModulePath: string,
+filename: string,
+isPolyfill: boolean,
+sourceCode: string,
|}) {
let dependencies, dependencyMapName, file; let dependencies, dependencyMapName, file;
if (isPolyfill) { const {ast} = options;
if (options.isPolyfill) {
dependencies = []; dependencies = [];
file = JsFileWrapping.wrapPolyfill(ast); file = JsFileWrapping.wrapPolyfill(ast);
} else { } else {
const opts = {dynamicRequires: 'reject'}; const {asyncRequireModulePath} = options;
const opts = {asyncRequireModulePath, dynamicRequires: 'reject'};
({dependencies, dependencyMapName} = collectDependencies(ast, opts)); ({dependencies, dependencyMapName} = collectDependencies(ast, opts));
file = JsFileWrapping.wrapModule(ast, dependencyMapName); file = JsFileWrapping.wrapModule(ast, dependencyMapName);
} }
const {filename, sourceCode} = options;
const gen = generate(file, filename, sourceCode, false); const gen = generate(file, filename, sourceCode, false);
return { return {
code: gen.code, code: gen.code,

View File

@ -168,6 +168,9 @@ class Server {
bundlerOpts.globalTransformCache = options.globalTransformCache; bundlerOpts.globalTransformCache = options.globalTransformCache;
bundlerOpts.watch = this._opts.watch; bundlerOpts.watch = this._opts.watch;
bundlerOpts.reporter = reporter; bundlerOpts.reporter = reporter;
bundlerOpts.asyncRequireModulePath =
options.asyncRequireModulePath ||
'metro/src/lib/bundle-modules/asyncRequire';
this._bundler = new Bundler(bundlerOpts); this._bundler = new Bundler(bundlerOpts);
// changes to the haste map can affect resolution of files in the bundle // changes to the haste map can affect resolution of files in the bundle

View File

@ -169,6 +169,7 @@ function toServerOptions(options: Options): ServerOptions {
assetTransforms: options.assetTransforms, assetTransforms: options.assetTransforms,
assetExts: options.assetExts, assetExts: options.assetExts,
assetRegistryPath: options.assetRegistryPath, assetRegistryPath: options.assetRegistryPath,
asyncRequireModulePath: options.asyncRequireModulePath,
blacklistRE: options.blacklistRE, blacklistRE: options.blacklistRE,
cacheVersion: options.cacheVersion, cacheVersion: options.cacheVersion,
dynamicDepsInPackages: options.dynamicDepsInPackages, dynamicDepsInPackages: options.dynamicDepsInPackages,

View File

@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`getTransformCacheKeyFn Should return always the same key for the same params 1`] = `"c4732dae08f17ef9c571cb8bcc24d249166862c6"`; exports[`getTransformCacheKeyFn Should return always the same key for the same params 1`] = `"1b825aa92d266cc01c946596e777463bd7e68c82"`;

View File

@ -20,6 +20,7 @@ describe('getTransformCacheKeyFn', () => {
it('Should return always the same key for the same params', async () => { it('Should return always the same key for the same params', async () => {
expect( expect(
getTransformCacheKeyFn({ getTransformCacheKeyFn({
asyncRequireModulePath: 'beep',
cacheVersion: '1.0', cacheVersion: '1.0',
dynamicDepsInPackages: 'arbitrary', dynamicDepsInPackages: 'arbitrary',
projectRoots: [__dirname], projectRoots: [__dirname],
@ -33,6 +34,7 @@ describe('getTransformCacheKeyFn', () => {
it('Should return a different key when the params change', async () => { it('Should return a different key when the params change', async () => {
const baseParams = { const baseParams = {
asyncRequireModulePath: 'beep',
cacheVersion: '1.0', cacheVersion: '1.0',
dynamicDepsInPackages: 'arbitrary', dynamicDepsInPackages: 'arbitrary',
projectRoots: [__dirname], projectRoots: [__dirname],

View File

@ -0,0 +1,19 @@
/**
* Copyright (c) 2016-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @format
* @flow
*/
'use strict';
// eslint-disable-next-line flow-no-fixme
const dynamicRequire = (require: $FlowFixMe);
module.exports = function(moduleID: mixed): Promise<mixed> {
return Promise.resolve(dynamicRequire(moduleID));
};

View File

@ -23,6 +23,7 @@ const VERSION = require('../../package.json').version;
* passed transform options. * passed transform options.
*/ */
function getTransformCacheKeyFn(opts: {| function getTransformCacheKeyFn(opts: {|
+asyncRequireModulePath: string,
+cacheVersion: string, +cacheVersion: string,
+dynamicDepsInPackages: string, +dynamicDepsInPackages: string,
+projectRoots: $ReadOnlyArray<string>, +projectRoots: $ReadOnlyArray<string>,
@ -46,6 +47,7 @@ function getTransformCacheKeyFn(opts: {|
.split(path.sep) .split(path.sep)
.join('-'), .join('-'),
transformModuleHash, transformModuleHash,
opts.asyncRequireModulePath,
opts.dynamicDepsInPackages, opts.dynamicDepsInPackages,
]; ];

View File

@ -71,6 +71,7 @@ export type Options = {|
// TODO: Remove this option below (T23793920) // TODO: Remove this option below (T23793920)
assetTransforms?: boolean, assetTransforms?: boolean,
assetExts?: Array<string>, assetExts?: Array<string>,
+asyncRequireModulePath?: string,
+assetRegistryPath: string, +assetRegistryPath: string,
blacklistRE?: RegExp, blacklistRE?: RegExp,
cacheVersion?: string, cacheVersion?: string,