Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions .flowconfig
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ experimental.multi_platform.extensions=.android
munge_underscores=true

module.name_mapper='^react-native$' -> '<PROJECT_ROOT>/packages/react-native/index.js'
module.name_mapper='^react-native/setup-env$' -> '<PROJECT_ROOT>/packages/react-native/src/setup-env.js'
module.name_mapper='^react-native/\(.*\)$' -> '<PROJECT_ROOT>/packages/react-native/\1'
module.name_mapper='^@react-native/dev-middleware$' -> '<PROJECT_ROOT>/packages/dev-middleware'
module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\|xml\|ktx\|heic\|heif\)$' -> '<PROJECT_ROOT>/packages/react-native/Libraries/Image/RelativeImageStub'
Expand Down
5 changes: 5 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ module.exports = {
'.*': './jest/preprocessor.js',
},
resolver: './packages/jest-preset/jest/resolver.js',
moduleNameMapper: {
// `resolver.js` strips `exports`, so alias this subpath to its `src/` impl.
'^react-native/setup-env$':
'<rootDir>/packages/react-native/src/setup-env.js',
},
setupFiles: ['./packages/jest-preset/jest/local-setup.js'],
fakeTimers: {
enableGlobally: true,
Expand Down
13 changes: 7 additions & 6 deletions packages/community-cli-plugin/src/utils/loadMetroConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,17 @@ function getCommunityCliDefaultConfig(
return {
resolver,
serializer: {
// We can include multiple copies of InitializeCore here because metro will
// We can include multiple copies of setup-env here because Metro will
// only add ones that are already part of the bundle
getModulesRunBeforeMainModule: () => [
require.resolve(
path.join(ctx.reactNativePath, 'Libraries/Core/InitializeCore'),
{paths: [ctx.root]},
),
// NOTE: ctx.reactNativePath is an absolute path, therefore we need to
// reference setup-env.js here by exact path specifier.
require.resolve(path.join(ctx.reactNativePath, 'src/setup-env.js'), {
paths: [ctx.root],
}),
...outOfTreePlatforms.map(platform =>
require.resolve(
`${ctx.platforms[platform].npmPackageName}/Libraries/Core/InitializeCore`,
`${ctx.platforms[platform].npmPackageName}/setup-env`,
{paths: [ctx.root]},
),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ eslintTester.run('../no-deep-imports', rule, {
"import Foo from 'react-native-foo';",
"import Foo from 'react-native-foo/Foo';",
"import Foo from 'react/native/Foo';",
"import 'react-native/Libraries/Core/InitializeCore';",
"require('react-native/Libraries/Core/InitializeCore');",
"import Foo from 'react-native/src/fb_internal/Foo'",
"require('react-native/src/fb_internal/Foo')",
"import 'react-native/setup-env';",
"require('react-native/setup-env');",
],
invalid: [
{
Expand Down Expand Up @@ -125,5 +125,31 @@ eslintTester.run('../no-deep-imports', rule, {
],
output: null,
},
{
code: "import 'react-native/Libraries/Core/InitializeCore';",
errors: [
{
messageId: 'useReplacementSource',
data: {
importPath: 'react-native/Libraries/Core/InitializeCore',
replacementSource: 'react-native/setup-env',
},
},
],
output: "import 'react-native/setup-env';",
},
{
code: "require('react-native/Libraries/Core/InitializeCore');",
errors: [
{
messageId: 'useReplacementSource',
data: {
importPath: 'react-native/Libraries/Core/InitializeCore',
replacementSource: 'react-native/setup-env',
},
},
],
output: "require('react-native/setup-env');",
},
],
});
44 changes: 33 additions & 11 deletions packages/eslint-plugin-react-native/no-deep-imports.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ module.exports = {
messages: {
deepImport:
"'{{importPath}}' React Native deep imports are deprecated. Please use the top level import instead.",
useReplacementSource:
"'{{importPath}}' is deprecated. Please import '{{replacementSource}}' instead.",
},
schema: [],
fixable: 'code',
Expand All @@ -31,12 +33,14 @@ module.exports = {
ImportDeclaration(node) {
if (
!isDeepReactNativeImport(node.source) ||
isInitializeCoreImport(node.source) ||
isSecondaryEntryPoint(node.source) ||
isFbInternalImport(node.source)
) {
return;
}
if (reportReplacementSource(node.source)) {
return;
}
if (isDefaultImport(node)) {
const reactNativeSource = node.source.value.slice(
'react-native/'.length,
Expand Down Expand Up @@ -88,13 +92,16 @@ module.exports = {
CallExpression(node) {
if (
!isDeepRequire(node) ||
isInitializeCoreImport(node.arguments[0]) ||
isSecondaryEntryPoint(node.arguments[0]) ||
isFbInternalImport(node.arguments[0])
) {
return;
}

if (reportReplacementSource(node.arguments[0])) {
return;
}

const parent = node.parent;
const importPath = node.arguments[0].value;

Expand Down Expand Up @@ -123,6 +130,26 @@ module.exports = {
},
};

function reportReplacementSource(source) {
const reactNativeSource = source.value.slice('react-native/'.length);
const mapping = publicAPIMapping[reactNativeSource];
if (!mapping || !mapping.replacementSource) {
return false;
}
context.report({
node: source,
messageId: 'useReplacementSource',
data: {
importPath: source.value,
replacementSource: mapping.replacementSource,
},
fix(fixer) {
return fixer.replaceText(source, `'${mapping.replacementSource}'`);
},
});
return true;
}

function getStandardReport(source) {
return {
node: source,
Expand Down Expand Up @@ -167,20 +194,15 @@ module.exports = {
return parts.length > 1 && parts[0] === 'react-native';
}

function isInitializeCoreImport(source) {
if (source.type !== 'Literal' || typeof source.value !== 'string') {
return false;
}

return source.value === 'react-native/Libraries/Core/InitializeCore';
}

function isSecondaryEntryPoint(source) {
if (source.type !== 'Literal' || typeof source.value !== 'string') {
return false;
}

return source.value === 'react-native/asset-registry';
return (
source.value === 'react-native/asset-registry' ||
source.value === 'react-native/setup-env'
);
}

function isFbInternalImport(source) {
Expand Down
7 changes: 7 additions & 0 deletions packages/eslint-plugin-react-native/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ const publicAPIMapping = {
default: 'experimental_LayoutConformance',
types: ['LayoutConformanceProps'],
},
'Libraries/Core/InitializeCore': {
// `InitializeCore` has no public named export; the deep import must be
// swapped for the `react-native/setup-env` entry point entirely.
default: null,
types: null,
replacementSource: 'react-native/setup-env',
},
'Libraries/Lists/FlatList': {
default: 'FlatList',
types: ['FlatListProps'],
Expand Down
5 changes: 5 additions & 0 deletions packages/jest-preset/jest-preset.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ module.exports = {
platforms: ['android', 'ios', 'native'],
},
moduleNameMapper: {
// `setup-env` is a secondary entry point exposed via the package's
// `exports`, but `./jest/resolver.js` strips `exports` and the generic
// mapper below resolves subpaths as literal directory paths. Alias it
// explicitly so it resolves to its `src/` implementation.
'^react-native/setup-env$': `${path.dirname(require.resolve('react-native'))}/src/setup-env.js`,
'^react-native($|/.*)': `${path.dirname(require.resolve('react-native'))}/$1`,
},
resolver: require.resolve('./jest/resolver.js'),
Expand Down
1 change: 1 addition & 0 deletions packages/jest-preset/jest/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ mock(
'm#react-native/Libraries/Core/InitializeCore',
'm#./mocks/InitializeCore',
);
mock('m#react-native/setup-env', 'm#./mocks/InitializeCore');
mock('m#react-native/Libraries/Core/NativeExceptionsManager');
mock('m#react-native/Libraries/Image/Image', 'm#./mocks/Image');
mock(
Expand Down
2 changes: 1 addition & 1 deletion packages/metro-config/src/index.flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export function getDefaultConfig(projectRoot: string): ConfigT {
serializer: {
// Note: This option is overridden in cli-plugin-metro (getOverrideConfig)
getModulesRunBeforeMainModule: () => [
require.resolve('react-native/Libraries/Core/InitializeCore'),
require.resolve('react-native/setup-env'),
],
getPolyfills: () => require('@react-native/js-polyfills')(),
isThirdPartyModule({path: modulePath}: Readonly<{path: string, ...}>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,17 +82,3 @@ test('import from other package', () => {
`"import { foo } from 'react-native-foo';"`,
);
});

test('import react-native/Libraries/Core/InitializeCore', () => {
const code = `
import 'react-native/Libraries/Core/InitializeCore';
require('react-native/Libraries/Core/InitializeCore');
export * from 'react-native/Libraries/Core/InitializeCore';
`;

expect(transform(code, [rnDeepImportsWarningPlugin])).toMatchInlineSnapshot(`
"import 'react-native/Libraries/Core/InitializeCore';
require('react-native/Libraries/Core/InitializeCore');
export * from 'react-native/Libraries/Core/InitializeCore';"
`);
});
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,6 @@ function isDeepReactNativeImport(source) {
return parts.length > 1 && parts[0] === 'react-native';
}

function isInitializeCoreImport(source) {
return source === 'react-native/Libraries/Core/InitializeCore';
}

function withLocation(node, loc) {
if (!node.loc) {
return {...node, loc};
Expand All @@ -55,7 +51,7 @@ module.exports = ({types: t}) => ({
ImportDeclaration(path, state) {
const source = path.node.source.value;

if (isDeepReactNativeImport(source) && !isInitializeCoreImport(source)) {
if (isDeepReactNativeImport(source)) {
const loc = path.node.loc;
state.import.push({source, loc});
}
Expand All @@ -71,10 +67,7 @@ module.exports = ({types: t}) => ({
) {
const source =
args[0].node.type === 'StringLiteral' ? args[0].node.value : '';
if (
isDeepReactNativeImport(source) &&
!isInitializeCoreImport(source)
) {
if (isDeepReactNativeImport(source)) {
const loc = path.node.loc;
state.require.push({source, loc});
}
Expand All @@ -83,11 +76,7 @@ module.exports = ({types: t}) => ({
ExportNamedDeclaration(path, state) {
const source = path.node.source;

if (
source &&
isDeepReactNativeImport(source.value) &&
!isInitializeCoreImport(source)
) {
if (source && isDeepReactNativeImport(source.value)) {
const loc = path.node.loc;
state.export.push({source: source.value, loc});
}
Expand Down
1 change: 1 addition & 0 deletions packages/react-native/Libraries/Core/InitializeCore.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
* 1. Require system.
* 2. Bridged modules.
*
* @deprecated Since 0.87. Use `'react-native/setup-env'` instead.
*/

'use strict';
Expand Down
1 change: 1 addition & 0 deletions packages/react-native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"types": null,
"default": "./src/asset-registry.js"
},
"./setup-env": "./src/setup-env.js",
"./src/fb_internal/*": "./src/fb_internal/*",
"./package.json": "./package.json"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/react-native/src/asset-registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
'use strict';

// ----------------------------------------------------------------------------
// Secondary react-native/asset-registry entry point.
// react-native/asset-registry
//
// This is an untyped secondary entry point intended to be referenced from
// Metro's `transformer.assetRegistryPath` config option. This entry point may
Expand Down
22 changes: 22 additions & 0 deletions packages/react-native/src/setup-env.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/

'use strict';
'use client';

// ----------------------------------------------------------------------------
// react-native/setup-env
//
// Side-effectful module that sets up the core React Native JavaScript
// environment. This includes global timers (`setTimeout` etc), the global
// `console` object, and hooks for printing stack traces with source maps.
// ----------------------------------------------------------------------------

require('./private/setup/setUpDefaultReactNativeEnvironment').default();
3 changes: 2 additions & 1 deletion packages/rn-tester/IntegrationTests/IntegrationTestsApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@

'use strict';

require('react-native/Libraries/Core/InitializeCore');
require('react-native/setup-env');

const React = require('react');
const ReactNative = require('react-native');

Expand Down
Loading