mirror of https://github.com/status-im/metro.git
metro: allow dynamic dependencies from within node_modules
Summary: Tries to adress https://github.com/facebook/metro/issues/65. We need a reasonnable workaround to support modules like `moment.js` that do dynamic requires but only in some cases. By replacing the call by a function that throws, we move the exception at runtime instead of happening at compile time. We don't want to do that for non-node_modules file because they are fixable directly, while `node_modules` are not fixable by people and they get completely blocked by the error at compile time. Reviewed By: rafeca Differential Revision: D6736989 fbshipit-source-id: a6e1fd9b56fa83907400884efd8f8594018b7c37
This commit is contained in:
parent
40497ee118
commit
46545d4f5c
|
@ -28,6 +28,7 @@ const {
|
|||
|
||||
import type {PostProcessModules} from '../DeltaBundler';
|
||||
import type {Options as JSTransformerOptions} from '../JSTransformer/worker';
|
||||
import type {DynamicRequiresBehavior} from '../ModuleGraph/worker/collectDependencies';
|
||||
import type {GlobalTransformCache} from '../lib/GlobalTransformCache';
|
||||
import type {TransformCache} from '../lib/TransformCaching';
|
||||
import type {Reporter} from '../lib/reporting';
|
||||
|
@ -82,6 +83,7 @@ export type Options = {|
|
|||
+assetRegistryPath: string,
|
||||
+blacklistRE?: RegExp,
|
||||
+cacheVersion: string,
|
||||
+dynamicDepsInPackages: DynamicRequiresBehavior,
|
||||
+enableBabelRCLookup: boolean,
|
||||
+extraNodeModules: {},
|
||||
+getPolyfills: ({platform: ?string}) => $ReadOnlyArray<string>,
|
||||
|
@ -117,17 +119,18 @@ class Bundler {
|
|||
|
||||
opts.projectRoots.forEach(verifyRootExists);
|
||||
|
||||
this._transformer = new Transformer(
|
||||
opts.transformModulePath,
|
||||
opts.maxWorkers,
|
||||
{
|
||||
this._transformer = new Transformer({
|
||||
maxWorkers: opts.maxWorkers,
|
||||
reporters: {
|
||||
stdoutChunk: chunk =>
|
||||
opts.reporter.update({type: 'worker_stdout_chunk', chunk}),
|
||||
stderrChunk: chunk =>
|
||||
opts.reporter.update({type: 'worker_stderr_chunk', chunk}),
|
||||
},
|
||||
opts.workerPath || undefined,
|
||||
);
|
||||
transformModulePath: opts.transformModulePath,
|
||||
dynamicDepsInPackages: opts.dynamicDepsInPackages,
|
||||
workerPath: opts.workerPath || undefined,
|
||||
});
|
||||
|
||||
this._depGraphPromise = DependencyGraph.load({
|
||||
assetExts: opts.assetExts,
|
||||
|
@ -137,6 +140,7 @@ class Bundler {
|
|||
getPolyfills: opts.getPolyfills,
|
||||
getTransformCacheKey: getTransformCacheKeyFn({
|
||||
cacheVersion: opts.cacheVersion,
|
||||
dynamicDepsInPackages: opts.dynamicDepsInPackages,
|
||||
projectRoots: opts.projectRoots,
|
||||
transformModulePath: opts.transformModulePath,
|
||||
}),
|
||||
|
|
|
@ -24,6 +24,7 @@ import type {
|
|||
import type {PostProcessModules} from './DeltaBundler';
|
||||
import type {PostProcessModules as PostProcessModulesForBuck} from './ModuleGraph/types.flow.js';
|
||||
import type {TransformVariants} from './ModuleGraph/types.flow';
|
||||
import type {DynamicRequiresBehavior} from './ModuleGraph/worker/collectDependencies';
|
||||
import type {HasteImpl} from './node-haste/Module';
|
||||
import type {IncomingMessage, ServerResponse} from 'http';
|
||||
|
||||
|
@ -39,6 +40,9 @@ export type ConfigT = {
|
|||
enhanceMiddleware: Middleware => Middleware,
|
||||
|
||||
extraNodeModules: {[id: string]: string},
|
||||
|
||||
+dynamicDepsInPackages: DynamicRequiresBehavior,
|
||||
|
||||
/**
|
||||
* Specify any additional asset file extensions to be used by the packager.
|
||||
* For example, if you want to include a .ttf file, you would return ['ttf']
|
||||
|
@ -160,6 +164,7 @@ const DEFAULT = ({
|
|||
enhanceMiddleware: middleware => middleware,
|
||||
extraNodeModules: {},
|
||||
assetTransforms: false,
|
||||
dynamicDepsInPackages: 'throwAtRuntime',
|
||||
getAssetExts: () => [],
|
||||
getBlacklistRE: () => blacklist(),
|
||||
getEnableBabelRCLookup: () => false,
|
||||
|
|
|
@ -26,6 +26,14 @@ describe('Transformer', function() {
|
|||
const localPath = 'arbitrary/file.js';
|
||||
const transformModulePath = __filename;
|
||||
|
||||
const opts = {
|
||||
maxWorkers: 4,
|
||||
reporters: {},
|
||||
transformModulePath,
|
||||
dynamicDepsInPackages: 'reject',
|
||||
workerPath: null,
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
Cache = jest.fn();
|
||||
Cache.prototype.get = jest.fn((a, b, c) => c());
|
||||
|
@ -55,7 +63,7 @@ describe('Transformer', function() {
|
|||
const transformOptions = {arbitrary: 'options'};
|
||||
const code = 'arbitrary(code)';
|
||||
|
||||
new Transformer(transformModulePath, 4).transformFile(
|
||||
new Transformer(opts).transformFile(
|
||||
fileName,
|
||||
localPath,
|
||||
code,
|
||||
|
@ -74,11 +82,12 @@ describe('Transformer', function() {
|
|||
transformOptions,
|
||||
[],
|
||||
'',
|
||||
'reject',
|
||||
);
|
||||
});
|
||||
|
||||
it('should add file info to parse errors', () => {
|
||||
const transformer = new Transformer(transformModulePath, 4);
|
||||
const transformer = new Transformer(opts);
|
||||
const message = 'message';
|
||||
const snippet = 'snippet';
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import type {BabelSourceMap} from 'babel-core';
|
|||
import type {Options, TransformedCode} from './worker';
|
||||
import type {LocalPath} from '../node-haste/lib/toLocalPath';
|
||||
import type {ResultWithMap} from './worker/minify';
|
||||
import type {DynamicRequiresBehavior} from '../ModuleGraph/worker/collectDependencies';
|
||||
|
||||
import typeof {minify as Minify, transform as Transform} from './worker';
|
||||
|
||||
|
@ -37,31 +38,37 @@ type Reporters = {
|
|||
module.exports = class Transformer {
|
||||
_worker: WorkerInterface;
|
||||
_transformModulePath: string;
|
||||
_dynamicDepsInPackages: DynamicRequiresBehavior;
|
||||
|
||||
constructor(
|
||||
transformModulePath: string,
|
||||
maxWorkers: number,
|
||||
reporters: Reporters,
|
||||
workerPath: string = require.resolve('./worker'),
|
||||
) {
|
||||
this._transformModulePath = transformModulePath;
|
||||
constructor(options: {|
|
||||
+maxWorkers: number,
|
||||
+reporters: Reporters,
|
||||
+transformModulePath: string,
|
||||
+dynamicDepsInPackages: DynamicRequiresBehavior,
|
||||
+workerPath: ?string,
|
||||
|}) {
|
||||
this._transformModulePath = options.transformModulePath;
|
||||
this._dynamicDepsInPackages = options.dynamicDepsInPackages;
|
||||
const {workerPath = require.resolve('./worker')} = options;
|
||||
|
||||
if (maxWorkers > 1) {
|
||||
if (options.maxWorkers > 1) {
|
||||
this._worker = this._makeFarm(
|
||||
workerPath,
|
||||
this._computeWorkerKey,
|
||||
['minify', 'transform'],
|
||||
maxWorkers,
|
||||
options.maxWorkers,
|
||||
);
|
||||
|
||||
const {reporters} = options;
|
||||
this._worker.getStdout().on('data', chunk => {
|
||||
reporters.stdoutChunk(chunk.toString('utf8'));
|
||||
});
|
||||
|
||||
this._worker.getStderr().on('data', chunk => {
|
||||
reporters.stderrChunk(chunk.toString('utf8'));
|
||||
});
|
||||
} else {
|
||||
// eslint-disable-next-line flow-no-fixme
|
||||
// $FlowFixMe: Flow doesn't support dynamic requires
|
||||
this._worker = require(workerPath);
|
||||
}
|
||||
}
|
||||
|
@ -101,6 +108,7 @@ module.exports = class Transformer {
|
|||
options,
|
||||
assetExts,
|
||||
assetRegistryPath,
|
||||
this._dynamicDepsInPackages,
|
||||
);
|
||||
|
||||
debug('Done transforming file', filename);
|
||||
|
|
|
@ -34,6 +34,7 @@ describe('code transformation worker:', () => {
|
|||
},
|
||||
[],
|
||||
'',
|
||||
'reject',
|
||||
);
|
||||
|
||||
expect(result.code).toBe(
|
||||
|
@ -60,6 +61,7 @@ describe('code transformation worker:', () => {
|
|||
},
|
||||
[],
|
||||
'',
|
||||
'reject',
|
||||
);
|
||||
|
||||
expect(result.code).toBe(
|
||||
|
@ -91,6 +93,7 @@ describe('code transformation worker:', () => {
|
|||
},
|
||||
[],
|
||||
'',
|
||||
'reject',
|
||||
);
|
||||
|
||||
expect(result.code).toBe(
|
||||
|
@ -130,11 +133,31 @@ describe('code transformation worker:', () => {
|
|||
},
|
||||
[],
|
||||
'',
|
||||
'reject',
|
||||
);
|
||||
throw new Error('should not reach this');
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(InvalidRequireCallError);
|
||||
if (!(error instanceof InvalidRequireCallError)) {
|
||||
throw error;
|
||||
}
|
||||
expect(error.message).toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('supports dynamic dependencies from within `node_modules`', async () => {
|
||||
await transformCode(
|
||||
'/root/node_modules/bar/file.js',
|
||||
`node_modules/bar/file.js`,
|
||||
'require(global.something);\n',
|
||||
path.join(__dirname, '../../../transformer.js'),
|
||||
false,
|
||||
{
|
||||
dev: true,
|
||||
transform: {},
|
||||
},
|
||||
[],
|
||||
'',
|
||||
'throwAtRuntime',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -32,6 +32,7 @@ import type {MetroSourceMapSegmentTuple} from 'metro-source-map';
|
|||
import type {LocalPath} from '../../node-haste/lib/toLocalPath';
|
||||
import type {ResultWithMap} from './minify';
|
||||
import type {Ast, Plugins as BabelPlugins} from 'babel-core';
|
||||
import type {DynamicRequiresBehavior} from '../../ModuleGraph/worker/collectDependencies';
|
||||
|
||||
export type TransformedCode = {
|
||||
code: string,
|
||||
|
@ -97,6 +98,7 @@ function postTransform(
|
|||
isScript: boolean,
|
||||
options: Options,
|
||||
transformFileStartLogEntry: LogEntry,
|
||||
dynamicDepsInPackages: DynamicRequiresBehavior,
|
||||
receivedAst: ?Ast,
|
||||
): Data {
|
||||
// Transformers can ouptut null ASTs (if they ignore the file). In that case
|
||||
|
@ -125,7 +127,13 @@ function postTransform(
|
|||
} else {
|
||||
let dependencyMapName;
|
||||
try {
|
||||
({dependencies, dependencyMapName} = collectDependencies(ast));
|
||||
const opts = {
|
||||
dynamicRequires: getDynamicDepsBehavior(
|
||||
dynamicDepsInPackages,
|
||||
filename,
|
||||
),
|
||||
};
|
||||
({dependencies, dependencyMapName} = collectDependencies(ast, opts));
|
||||
} catch (error) {
|
||||
if (error instanceof collectDependencies.InvalidRequireCallError) {
|
||||
throw new InvalidRequireCallError(error, filename);
|
||||
|
@ -162,6 +170,24 @@ function postTransform(
|
|||
};
|
||||
}
|
||||
|
||||
function getDynamicDepsBehavior(
|
||||
inPackages: DynamicRequiresBehavior,
|
||||
filename: string,
|
||||
): DynamicRequiresBehavior {
|
||||
switch (inPackages) {
|
||||
case 'reject':
|
||||
return 'reject';
|
||||
case 'throwAtRuntime':
|
||||
const isPackage = /(?:^|[/\\])node_modules[/\\]/.test(filename);
|
||||
return isPackage ? inPackages : 'reject';
|
||||
default:
|
||||
(inPackages: empty);
|
||||
throw new Error(
|
||||
`invalid value for dynamic deps behavior: \`${inPackages}\``,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function transformCode(
|
||||
filename: string,
|
||||
localPath: LocalPath,
|
||||
|
@ -171,6 +197,7 @@ function transformCode(
|
|||
options: Options,
|
||||
assetExts: $ReadOnlyArray<string>,
|
||||
assetRegistryPath: string,
|
||||
dynamicDepsInPackages: DynamicRequiresBehavior,
|
||||
): Data | Promise<Data> {
|
||||
const isJson = filename.endsWith('.json');
|
||||
|
||||
|
@ -216,6 +243,7 @@ function transformCode(
|
|||
isScript,
|
||||
options,
|
||||
transformFileStartLogEntry,
|
||||
dynamicDepsInPackages,
|
||||
];
|
||||
|
||||
return transformResult instanceof Promise
|
||||
|
|
|
@ -21,6 +21,7 @@ const {codeFromAst, comparableCode} = require('../../test-helpers');
|
|||
const {any} = expect;
|
||||
|
||||
const {InvalidRequireCallError} = collectDependencies;
|
||||
const opts = {dynamicRequires: 'reject'};
|
||||
|
||||
it('collects unique dependency identifiers and transforms the AST', () => {
|
||||
const ast = astFromCode(`
|
||||
|
@ -31,7 +32,7 @@ it('collects unique dependency identifiers and transforms the AST', () => {
|
|||
}
|
||||
require('do');
|
||||
`);
|
||||
const {dependencies, dependencyMapName} = collectDependencies(ast);
|
||||
const {dependencies, dependencyMapName} = collectDependencies(ast, opts);
|
||||
expect(dependencies).toEqual([
|
||||
{name: 'b/lib/a', isAsync: false},
|
||||
{name: 'do', isAsync: false},
|
||||
|
@ -53,7 +54,7 @@ it('collects asynchronous dependencies', () => {
|
|||
const ast = astFromCode(`
|
||||
import("some/async/module").then(foo => {});
|
||||
`);
|
||||
const {dependencies, dependencyMapName} = collectDependencies(ast);
|
||||
const {dependencies, dependencyMapName} = collectDependencies(ast, opts);
|
||||
expect(dependencies).toEqual([
|
||||
{name: 'some/async/module', isAsync: true},
|
||||
{name: 'asyncRequire', isAsync: false},
|
||||
|
@ -70,7 +71,7 @@ it('collects mixed dependencies as being sync', () => {
|
|||
const a = require("some/async/module");
|
||||
import("some/async/module").then(foo => {});
|
||||
`);
|
||||
const {dependencies, dependencyMapName} = collectDependencies(ast);
|
||||
const {dependencies, dependencyMapName} = collectDependencies(ast, opts);
|
||||
expect(dependencies).toEqual([
|
||||
{name: 'some/async/module', isAsync: false},
|
||||
{name: 'asyncRequire', isAsync: false},
|
||||
|
@ -88,7 +89,7 @@ it('collects mixed dependencies as being sync; reverse order', () => {
|
|||
import("some/async/module").then(foo => {});
|
||||
const a = require("some/async/module");
|
||||
`);
|
||||
const {dependencies, dependencyMapName} = collectDependencies(ast);
|
||||
const {dependencies, dependencyMapName} = collectDependencies(ast, opts);
|
||||
expect(dependencies).toEqual([
|
||||
{name: 'some/async/module', isAsync: false},
|
||||
{name: 'asyncRequire', isAsync: false},
|
||||
|
@ -104,7 +105,7 @@ it('collects mixed dependencies as being sync; reverse order', () => {
|
|||
describe('Evaluating static arguments', () => {
|
||||
it('supports template literals as arguments', () => {
|
||||
const ast = astFromCode('require(`left-pad`)');
|
||||
const {dependencies, dependencyMapName} = collectDependencies(ast);
|
||||
const {dependencies, dependencyMapName} = collectDependencies(ast, opts);
|
||||
expect(dependencies).toEqual([{name: 'left-pad', isAsync: false}]);
|
||||
expect(codeFromAst(ast)).toEqual(
|
||||
comparableCode(`require(${dependencyMapName}[0], \`left-pad\`);`),
|
||||
|
@ -113,7 +114,7 @@ describe('Evaluating static arguments', () => {
|
|||
|
||||
it('supports template literals with static interpolations', () => {
|
||||
const ast = astFromCode('require(`left${"-"}pad`)');
|
||||
const {dependencies, dependencyMapName} = collectDependencies(ast);
|
||||
const {dependencies, dependencyMapName} = collectDependencies(ast, opts);
|
||||
expect(dependencies).toEqual([{name: 'left-pad', isAsync: false}]);
|
||||
expect(codeFromAst(ast)).toEqual(
|
||||
comparableCode(`require(${dependencyMapName}[0], \`left\${"-"}pad\`);`),
|
||||
|
@ -123,10 +124,12 @@ describe('Evaluating static arguments', () => {
|
|||
it('throws template literals with dyncamic interpolations', () => {
|
||||
const ast = astFromCode('let foo;require(`left${foo}pad`)');
|
||||
try {
|
||||
collectDependencies(ast);
|
||||
collectDependencies(ast, opts);
|
||||
throw new Error('should not reach');
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(InvalidRequireCallError);
|
||||
if (!(error instanceof InvalidRequireCallError)) {
|
||||
throw error;
|
||||
}
|
||||
expect(error.message).toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
@ -134,17 +137,19 @@ describe('Evaluating static arguments', () => {
|
|||
it('throws on tagged template literals', () => {
|
||||
const ast = astFromCode('require(tag`left-pad`)');
|
||||
try {
|
||||
collectDependencies(ast);
|
||||
collectDependencies(ast, opts);
|
||||
throw new Error('should not reach');
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(InvalidRequireCallError);
|
||||
if (!(error instanceof InvalidRequireCallError)) {
|
||||
throw error;
|
||||
}
|
||||
expect(error.message).toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('supports multiple static strings concatenated', () => {
|
||||
const ast = astFromCode('require("foo_" + "bar")');
|
||||
const {dependencies, dependencyMapName} = collectDependencies(ast);
|
||||
const {dependencies, dependencyMapName} = collectDependencies(ast, opts);
|
||||
expect(dependencies).toEqual([{name: 'foo_bar', isAsync: false}]);
|
||||
expect(codeFromAst(ast)).toEqual(
|
||||
comparableCode(`require(${dependencyMapName}[0], "foo_" + "bar");`),
|
||||
|
@ -153,7 +158,7 @@ describe('Evaluating static arguments', () => {
|
|||
|
||||
it('supports concatenating strings and template literasl', () => {
|
||||
const ast = astFromCode('require("foo_" + "bar" + `_baz`)');
|
||||
const {dependencies, dependencyMapName} = collectDependencies(ast);
|
||||
const {dependencies, dependencyMapName} = collectDependencies(ast, opts);
|
||||
expect(dependencies).toEqual([{name: 'foo_bar_baz', isAsync: false}]);
|
||||
expect(codeFromAst(ast)).toEqual(
|
||||
comparableCode(
|
||||
|
@ -164,7 +169,7 @@ describe('Evaluating static arguments', () => {
|
|||
|
||||
it('supports using static variables in require statements', () => {
|
||||
const ast = astFromCode('const myVar="my";require("foo_" + myVar)');
|
||||
const {dependencies, dependencyMapName} = collectDependencies(ast);
|
||||
const {dependencies, dependencyMapName} = collectDependencies(ast, opts);
|
||||
expect(dependencies).toEqual([{name: 'foo_my', isAsync: false}]);
|
||||
expect(codeFromAst(ast)).toEqual(
|
||||
comparableCode(
|
||||
|
@ -176,18 +181,34 @@ describe('Evaluating static arguments', () => {
|
|||
it('throws when requiring non-strings', () => {
|
||||
const ast = astFromCode('require(1)');
|
||||
try {
|
||||
collectDependencies(ast);
|
||||
collectDependencies(ast, opts);
|
||||
throw new Error('should not reach');
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(InvalidRequireCallError);
|
||||
if (!(error instanceof InvalidRequireCallError)) {
|
||||
throw error;
|
||||
}
|
||||
expect(error.message).toMatchSnapshot();
|
||||
}
|
||||
});
|
||||
|
||||
it('throws at runtime when requiring non-strings with special option', () => {
|
||||
const ast = astFromCode('require(1)');
|
||||
const opts = {dynamicRequires: 'throwAtRuntime'};
|
||||
const {dependencies} = collectDependencies(ast, opts);
|
||||
expect(dependencies).toEqual([]);
|
||||
expect(codeFromAst(ast)).toEqual(
|
||||
comparableCode(
|
||||
"(function (name) { throw new Error('Module `' + name " +
|
||||
"+ '` was required dynamically. This is not supported by " +
|
||||
"Metro bundler.'); })(1);",
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('exposes a string as `dependencyMapName` even without collecting dependencies', () => {
|
||||
const ast = astFromCode('');
|
||||
expect(collectDependencies(ast).dependencyMapName).toEqual(any(String));
|
||||
expect(collectDependencies(ast, opts).dependencyMapName).toEqual(any(String));
|
||||
});
|
||||
|
||||
function astFromCode(code) {
|
||||
|
|
|
@ -20,9 +20,13 @@ const prettyPrint = require('babel-generator').default;
|
|||
|
||||
import type {TransformResultDependency} from '../types.flow';
|
||||
|
||||
export type DynamicRequiresBehavior = 'throwAtRuntime' | 'reject';
|
||||
type Options = {|+dynamicRequires: DynamicRequiresBehavior|};
|
||||
|
||||
type Context = {
|
||||
nameToIndex: Map<string, number>,
|
||||
dependencies: Array<{|+name: string, isAsync: boolean|}>,
|
||||
+dynamicRequires: DynamicRequiresBehavior,
|
||||
};
|
||||
|
||||
type CollectedDependencies = {|
|
||||
|
@ -38,9 +42,13 @@ type CollectedDependencies = {|
|
|||
* know the actual module ID. The second argument is only provided for debugging
|
||||
* purposes.
|
||||
*/
|
||||
function collectDependencies(ast: Ast): CollectedDependencies {
|
||||
function collectDependencies(
|
||||
ast: Ast,
|
||||
options: Options,
|
||||
): CollectedDependencies {
|
||||
const visited = new WeakSet();
|
||||
const context = {nameToIndex: new Map(), dependencies: []};
|
||||
const {dynamicRequires} = options;
|
||||
const context = {nameToIndex: new Map(), dependencies: [], dynamicRequires};
|
||||
const visitor = {
|
||||
Program(path, state) {
|
||||
state.dependencyMapIdentifier = path.scope.generateUidIdentifier(
|
||||
|
@ -77,6 +85,9 @@ function isRequireCall(callee) {
|
|||
|
||||
function processImportCall(context, path, node, depMapIdent) {
|
||||
const [, name] = getModuleNameFromCallArgs('import', node, path);
|
||||
if (name == null) {
|
||||
throw invalidRequireOf('import', node);
|
||||
}
|
||||
const index = assignDependencyIndex(context, name, 'import');
|
||||
const mapLookup = createDepMapLookup(depMapIdent, index);
|
||||
const newImport = makeAsyncRequire({
|
||||
|
@ -87,17 +98,32 @@ function processImportCall(context, path, node, depMapIdent) {
|
|||
}
|
||||
|
||||
function processRequireCall(context, path, node, depMapIdent) {
|
||||
const [nameExpression, name] = getModuleNameFromCallArgs(
|
||||
'require',
|
||||
node,
|
||||
path,
|
||||
);
|
||||
const [nameExpr, name] = getModuleNameFromCallArgs('require', node, path);
|
||||
if (name == null) {
|
||||
const {dynamicRequires} = context;
|
||||
switch (dynamicRequires) {
|
||||
case 'reject':
|
||||
throw invalidRequireOf('require', node);
|
||||
case 'throwAtRuntime':
|
||||
const newNode = makeDynamicRequireReplacement({NAME_EXPR: nameExpr});
|
||||
path.replaceWith(newNode);
|
||||
return newNode;
|
||||
default:
|
||||
(dynamicRequires: empty);
|
||||
throw new Error(`invalid dyn requires spec \`${dynamicRequires}\``);
|
||||
}
|
||||
}
|
||||
const index = assignDependencyIndex(context, name, 'require');
|
||||
const mapLookup = createDepMapLookup(depMapIdent, index);
|
||||
node.arguments = [mapLookup, nameExpression];
|
||||
node.arguments = [mapLookup, nameExpr];
|
||||
return node;
|
||||
}
|
||||
|
||||
const makeDynamicRequireReplacement = babelTemplate(
|
||||
"(function(name){throw new Error('Module `'+name+'` was required " +
|
||||
"dynamically. This is not supported by Metro bundler.')})(NAME_EXPR)",
|
||||
);
|
||||
|
||||
/**
|
||||
* Extract the module name from `require` arguments. We support template
|
||||
* literal, for example one could write `require(`foo`)`.
|
||||
|
@ -115,8 +141,7 @@ function getModuleNameFromCallArgs(type, node, path) {
|
|||
if (result.confident && typeof result.value === 'string') {
|
||||
return [nameExpression, result.value];
|
||||
}
|
||||
|
||||
throw invalidRequireOf(type, node);
|
||||
return [nameExpression, null];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -188,7 +188,8 @@ function makeResult(ast: Ast, filename, sourceCode, isPolyfill = false) {
|
|||
dependencies = [];
|
||||
file = JsFileWrapping.wrapPolyfill(ast);
|
||||
} else {
|
||||
({dependencies, dependencyMapName} = collectDependencies(ast));
|
||||
const opts = {dynamicRequires: 'reject'};
|
||||
({dependencies, dependencyMapName} = collectDependencies(ast, opts));
|
||||
file = JsFileWrapping.wrapModule(ast, dependencyMapName);
|
||||
}
|
||||
|
||||
|
|
|
@ -121,6 +121,7 @@ class Server {
|
|||
assetRegistryPath: options.assetRegistryPath,
|
||||
blacklistRE: options.blacklistRE,
|
||||
cacheVersion: options.cacheVersion || '1.0',
|
||||
dynamicDepsInPackages: options.dynamicDepsInPackages,
|
||||
createModuleIdFactory: options.createModuleIdFactory,
|
||||
enableBabelRCLookup:
|
||||
options.enableBabelRCLookup != null
|
||||
|
|
|
@ -115,6 +115,7 @@ async function runMetro({
|
|||
assetRegistryPath: normalizedConfig.assetRegistryPath,
|
||||
blacklistRE: normalizedConfig.getBlacklistRE(),
|
||||
createModuleIdFactory: normalizedConfig.createModuleIdFactory,
|
||||
dynamicDepsInPackages: normalizedConfig.dynamicDepsInPackages,
|
||||
extraNodeModules: normalizedConfig.extraNodeModules,
|
||||
getPolyfills: normalizedConfig.getPolyfills,
|
||||
getModulesRunBeforeMainModule:
|
||||
|
|
|
@ -93,6 +93,7 @@ describe('basic_bundle', () => {
|
|||
const bundleWithPolyfills = await Metro.build(
|
||||
{
|
||||
assetRegistryPath: ASSET_REGISTRY_PATH,
|
||||
dynamicDepsInPackages: 'reject',
|
||||
getModulesRunBeforeMainModule: () => ['InitializeCore'],
|
||||
getPolyfills: () => [polyfill1, polyfill2],
|
||||
projectRoots: [INPUT_PATH, POLYFILLS_PATH],
|
||||
|
@ -113,6 +114,7 @@ describe('basic_bundle', () => {
|
|||
const bundleWithoutPolyfills = await Metro.build(
|
||||
{
|
||||
assetRegistryPath: ASSET_REGISTRY_PATH,
|
||||
dynamicDepsInPackages: 'reject',
|
||||
getModulesRunBeforeMainModule: () => ['InitializeCore'],
|
||||
getPolyfills: () => [],
|
||||
projectRoots: [INPUT_PATH, POLYFILLS_PATH],
|
||||
|
|
|
@ -171,6 +171,7 @@ function toServerOptions(options: Options): ServerOptions {
|
|||
assetRegistryPath: options.assetRegistryPath,
|
||||
blacklistRE: options.blacklistRE,
|
||||
cacheVersion: options.cacheVersion,
|
||||
dynamicDepsInPackages: options.dynamicDepsInPackages,
|
||||
enableBabelRCLookup: options.enableBabelRCLookup,
|
||||
extraNodeModules: options.extraNodeModules,
|
||||
getModulesRunBeforeMainModule: options.getModulesRunBeforeMainModule,
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`getTransformCacheKeyFn Should return always the same key for the same params 1`] = `"7b98b107d047d0c094f33c8c85d28a9c4edaf407"`;
|
||||
exports[`getTransformCacheKeyFn Should return always the same key for the same params 1`] = `"b2ccafe92b59d525a827be33f7579633f4859b48"`;
|
||||
|
|
|
@ -21,6 +21,7 @@ describe('getTransformCacheKeyFn', () => {
|
|||
expect(
|
||||
getTransformCacheKeyFn({
|
||||
cacheVersion: '1.0',
|
||||
dynamicDepsInPackages: 'arbitrary',
|
||||
projectRoots: [__dirname],
|
||||
transformModulePath: path.resolve(
|
||||
__dirname,
|
||||
|
@ -33,6 +34,7 @@ describe('getTransformCacheKeyFn', () => {
|
|||
it('Should return a different key when the params change', async () => {
|
||||
const baseParams = {
|
||||
cacheVersion: '1.0',
|
||||
dynamicDepsInPackages: 'arbitrary',
|
||||
projectRoots: [__dirname],
|
||||
transformModulePath: path.resolve(__dirname, '../../defaultTransform.js'),
|
||||
};
|
||||
|
|
|
@ -24,6 +24,7 @@ const VERSION = require('../../package.json').version;
|
|||
*/
|
||||
function getTransformCacheKeyFn(opts: {|
|
||||
+cacheVersion: string,
|
||||
+dynamicDepsInPackages: string,
|
||||
+projectRoots: $ReadOnlyArray<string>,
|
||||
+transformModulePath: string,
|
||||
|}): (options: mixed) => string {
|
||||
|
@ -45,6 +46,7 @@ function getTransformCacheKeyFn(opts: {|
|
|||
.split(path.sep)
|
||||
.join('-'),
|
||||
transformModuleHash,
|
||||
opts.dynamicDepsInPackages,
|
||||
];
|
||||
|
||||
const transformCacheKey = crypto
|
||||
|
|
|
@ -17,6 +17,7 @@ import type {
|
|||
PostProcessBundleSourcemap,
|
||||
} from '../Bundler';
|
||||
import type {PostProcessModules} from '../DeltaBundler';
|
||||
import type {DynamicRequiresBehavior} from '../ModuleGraph/worker/collectDependencies';
|
||||
import type {GlobalTransformCache} from '../lib/GlobalTransformCache';
|
||||
import type {TransformCache} from '../lib/TransformCaching';
|
||||
import type {Reporter} from '../lib/reporting';
|
||||
|
@ -74,6 +75,7 @@ export type Options = {|
|
|||
blacklistRE?: RegExp,
|
||||
cacheVersion?: string,
|
||||
createModuleIdFactory?: () => (path: string) => number,
|
||||
+dynamicDepsInPackages: DynamicRequiresBehavior,
|
||||
enableBabelRCLookup?: boolean,
|
||||
extraNodeModules?: {},
|
||||
getPolyfills: ({platform: ?string}) => $ReadOnlyArray<string>,
|
||||
|
|
Loading…
Reference in New Issue