Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,14 @@ Type: `Array<string>`

Additional platforms to look out for, For example, if you want to add a "custom" platform, and use modules ending in .custom.js, you would return ['custom'] here.

#### `ignoreRequireCyclePrefixes`

Type: `Array<string>`

In development mode, suppress require cycle warnings for modules that start with one of the prefixes in this array. This is useful if a module you use has a require cycle that's not worth fixing.

Note that paths are relative to the root of the project. For example, if you want to exclude all files of a module named `my-library` in `node_modules`, you should use `['node_modules/my-library']`.

---
### Transformer Options

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Object {
"emptyModulePath": "metro-runtime/src/modules/empty-module",
"extraNodeModules": Object {},
"hasteImplModulePath": undefined,
"ignoreRequireCyclePrefixes": Array [],
"nodeModulesPaths": Array [],
"platforms": Array [
"ios",
Expand Down Expand Up @@ -195,6 +196,7 @@ Object {
"emptyModulePath": "metro-runtime/src/modules/empty-module",
"extraNodeModules": Object {},
"hasteImplModulePath": undefined,
"ignoreRequireCyclePrefixes": Array [],
"nodeModulesPaths": Array [],
"platforms": Array [
"ios",
Expand Down Expand Up @@ -340,6 +342,7 @@ Object {
"emptyModulePath": "metro-runtime/src/modules/empty-module",
"extraNodeModules": Object {},
"hasteImplModulePath": undefined,
"ignoreRequireCyclePrefixes": Array [],
"nodeModulesPaths": Array [],
"platforms": Array [
"ios",
Expand Down Expand Up @@ -485,6 +488,7 @@ Object {
"emptyModulePath": "metro-runtime/src/modules/empty-module",
"extraNodeModules": Object {},
"hasteImplModulePath": undefined,
"ignoreRequireCyclePrefixes": Array [],
"nodeModulesPaths": Array [],
"platforms": Array [
"ios",
Expand Down
1 change: 1 addition & 0 deletions packages/metro-config/src/configTypes.flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ type ResolverConfigT = {|
resolverMainFields: $ReadOnlyArray<string>,
sourceExts: $ReadOnlyArray<string>,
useWatchman: boolean,
ignoreRequireCyclePrefixes: $ReadOnlyArray<string>,
|};

type SerializerConfigT = {|
Expand Down
1 change: 1 addition & 0 deletions packages/metro-config/src/defaults/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const getDefaultValues = (projectRoot: ?string): ConfigT => ({
resolveRequest: null,
resolverMainFields: ['browser', 'main'],
useWatchman: true,
ignoreRequireCyclePrefixes: [],
},

serializer: {
Expand Down
24 changes: 24 additions & 0 deletions packages/metro-runtime/src/polyfills/__tests__/require-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ describe('require', () => {
'global',
'__DEV__',
'__METRO_GLOBAL_PREFIX__',
'__IGNORE_REQUIRE_CYCLE_PREFIXES__',
moduleSystemCode,
);

Expand Down Expand Up @@ -665,6 +666,29 @@ describe('require', () => {
console.warn = warn;
});

it('does not log warning for cyclic dependency in ignore list', () => {
createModuleSystem(moduleSystem, true, '', ['foo']);

createModule(moduleSystem, 0, 'foo.js', (global, require) => {
require(1);
});

createModule(moduleSystem, 1, 'bar.js', (global, require) => {
require(2);
});

createModule(moduleSystem, 2, 'baz.js', (global, require) => {
require(0);
});

const warn = console.warn;
console.warn = jest.fn();

moduleSystem.__r(0);
expect(console.warn).toHaveBeenCalledTimes(0);
console.warn = warn;
});

it('sets the exports value to their current value', () => {
createModuleSystem(moduleSystem, false, '');

Expand Down
26 changes: 21 additions & 5 deletions packages/metro-runtime/src/polyfills/require.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

declare var __DEV__: boolean;
declare var __METRO_GLOBAL_PREFIX__: string;
// JSON-encoded array of strings that we should not print require cycle errors
// for
declare var __IGNORE_REQUIRE_CYCLE_PREFIXES__: $ReadOnlyArray<string>;

type DependencyMap = Array<ModuleID>;
type Exports = any;
Expand Down Expand Up @@ -178,11 +181,13 @@ function metroRequire(moduleId: ModuleID | VerboseModuleNameForDev): Exports {
);
// We want to show A -> B -> A:
cycle.push(cycle[0]);
console.warn(
`Require cycle: ${cycle.join(' -> ')}\n\n` +
'Require cycles are allowed, but can result in uninitialized values. ' +
'Consider refactoring to remove the need for a cycle.',
);
if (shouldPrintRequireCycle(cycle[0])) {
console.warn(
`Require cycle: ${cycle.join(' -> ')}\n\n` +
'Require cycles are allowed, but can result in uninitialized values. ' +
'Consider refactoring to remove the need for a cycle.',
);
}
}
}

Expand All @@ -193,6 +198,17 @@ function metroRequire(moduleId: ModuleID | VerboseModuleNameForDev): Exports {
: guardedLoadModule(moduleIdReallyIsNumber, module);
}

function shouldPrintRequireCycle(module: ?string): boolean {
const prefixes = __IGNORE_REQUIRE_CYCLE_PREFIXES__;
if (!module || !prefixes || !Array.isArray(prefixes)) return true;

for (const prefix of prefixes) {
if (module.startsWith(prefix)) return false;
}

return true;
}

function metroImportDefault(moduleId: ModuleID | VerboseModuleNameForDev) {
if (__DEV__ && typeof moduleId === 'string') {
const verboseName = moduleId;
Expand Down
45 changes: 40 additions & 5 deletions packages/metro/src/lib/__tests__/getPreludeCode-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@ const vm = require('vm');
describe(`${mode} mode`, () => {
const isDev = mode === 'development';
const globalPrefix = '';
const ignoreRequireCyclePrefixes = [];

it('sets up `process.env.NODE_ENV` and `__DEV__`', () => {
const sandbox: $FlowFixMe = {};
vm.createContext(sandbox);
vm.runInContext(getPreludeCode({isDev, globalPrefix}), sandbox);
vm.runInContext(
getPreludeCode({isDev, globalPrefix, ignoreRequireCyclePrefixes}),
sandbox,
);
expect(sandbox.process.env.NODE_ENV).toEqual(mode);
expect(sandbox.__DEV__).toEqual(isDev);
});
Expand All @@ -31,17 +35,38 @@ const vm = require('vm');
const sandbox: $FlowFixMe = {};
vm.createContext(sandbox);
vm.runInContext(
getPreludeCode({isDev, globalPrefix: '__metro'}),
getPreludeCode({
isDev,
globalPrefix: '__metro',
ignoreRequireCyclePrefixes,
}),
sandbox,
);
expect(sandbox.__METRO_GLOBAL_PREFIX__).toBe('__metro');
});

it('sets up `__IGNORE_REQUIRE_CYCLE_PREFIXES__`', () => {
const sandbox: $FlowFixMe = {};
vm.createContext(sandbox);
vm.runInContext(
getPreludeCode({
isDev,
globalPrefix,
ignoreRequireCyclePrefixes: ['blah'],
}),
sandbox,
);
expect(sandbox.__IGNORE_REQUIRE_CYCLE_PREFIXES__).toEqual(['blah']);
});

it('does not override an existing `process.env`', () => {
const nextTick = () => {};
const sandbox: $FlowFixMe = {process: {nextTick, env: {FOOBAR: 123}}};
vm.createContext(sandbox);
vm.runInContext(getPreludeCode({isDev, globalPrefix}), sandbox);
vm.runInContext(
getPreludeCode({isDev, globalPrefix, ignoreRequireCyclePrefixes}),
sandbox,
);
expect(sandbox.process.env.NODE_ENV).toEqual(mode);
expect(sandbox.process.env.FOOBAR).toEqual(123);
expect(sandbox.process.nextTick).toEqual(nextTick);
Expand All @@ -53,7 +78,12 @@ const vm = require('vm');
const BAR = 2;
vm.createContext(sandbox);
vm.runInContext(
getPreludeCode({isDev, globalPrefix, extraVars: {FOO, BAR}}),
getPreludeCode({
isDev,
globalPrefix,
ignoreRequireCyclePrefixes,
extraVars: {FOO, BAR},
}),
sandbox,
);
expect(sandbox.FOO).toBe(FOO);
Expand All @@ -64,7 +94,12 @@ const vm = require('vm');
const sandbox: $FlowFixMe = {};
vm.createContext(sandbox);
vm.runInContext(
getPreludeCode({isDev, globalPrefix, extraVars: {__DEV__: 123}}),
getPreludeCode({
isDev,
globalPrefix,
ignoreRequireCyclePrefixes,
extraVars: {__DEV__: 123},
}),
sandbox,
);
expect(sandbox.__DEV__).toBe(isDev);
Expand Down
7 changes: 7 additions & 0 deletions packages/metro/src/lib/getPreludeCode.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,24 @@ function getPreludeCode({
extraVars,
isDev,
globalPrefix,
ignoreRequireCyclePrefixes,
}: {|
+extraVars?: {[string]: mixed, ...},
+isDev: boolean,
+globalPrefix: string,
+ignoreRequireCyclePrefixes: $ReadOnlyArray<string>,
|}): string {
const vars = [
// Ensure these variable names match the ones referenced in metro-runtime
// require.js
'__BUNDLE_START_TIME__=this.nativePerformanceNow?nativePerformanceNow():Date.now()',
`__DEV__=${String(isDev)}`,
...formatExtraVars(extraVars),
'process=this.process||{}',
`__METRO_GLOBAL_PREFIX__='${globalPrefix}'`,
`__IGNORE_REQUIRE_CYCLE_PREFIXES__=${JSON.stringify(
ignoreRequireCyclePrefixes,
)}`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is dev-only, I would avoid injecting this variable if isDev is false.

Also, let's make sure this name starts with __METRO, e.g.

Suggested change
`__IGNORE_REQUIRE_CYCLE_PREFIXES__=${JSON.stringify(
ignoreRequireCyclePrefixes,
)}`,
`__METRO_IGNORE_REQUIRE_CYCLE_PREFIXES__=${JSON.stringify(
ignoreRequireCyclePrefixes,
)}`,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in the latest change!

];
return `var ${vars.join(',')};${processEnv(
isDev ? 'development' : 'production',
Expand Down
9 changes: 8 additions & 1 deletion packages/metro/src/lib/getPrependedScripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ async function getPrependedScripts(
_getPrelude({
dev: options.dev,
globalPrefix: config.transformer.globalPrefix,
ignoreRequireCyclePrefixes: config.resolver.ignoreRequireCyclePrefixes,
}),
...dependencies.values(),
];
Expand All @@ -77,12 +78,18 @@ async function getPrependedScripts(
function _getPrelude({
dev,
globalPrefix,
ignoreRequireCyclePrefixes,
}: {
dev: boolean,
globalPrefix: string,
ignoreRequireCyclePrefixes: $ReadOnlyArray<string>,
...
}): Module<> {
const code = getPreludeCode({isDev: dev, globalPrefix});
const code = getPreludeCode({
isDev: dev,
globalPrefix,
ignoreRequireCyclePrefixes,
});
const name = '__prelude__';

return {
Expand Down