Skip to content
Open
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
2 changes: 2 additions & 0 deletions .flowconfig
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ 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/react-private-interface$' -> '<PROJECT_ROOT>/packages/react-native/src/react-private-interface.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
4 changes: 2 additions & 2 deletions .github/workflows/test-all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -471,9 +471,9 @@ jobs:
- name: Flow
shell: bash
run: yarn flow-check
- name: TypeScript
- name: TypeScript (legacy deep imports / manual types)
shell: bash
run: yarn test-typescript
run: yarn test-typescript-legacy

test_js:
runs-on: ubuntu-latest
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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
"test-release-local-clean": "node ./scripts/release-testing/test-release-local-clean.js",
"test-release-local": "node ./scripts/release-testing/test-release-local.js",
"test-ios": "./scripts/objc-test.sh test",
"test-typescript": "tsc -p packages/react-native/types/tsconfig.json",
"test-generated-typescript": "tsc -p packages/react-native/types_generated/tsconfig.test.json",
"test-typescript-legacy": "tsc -p packages/react-native/__typetests__/tsconfig.legacy.json",
"test-generated-typescript": "tsc -p packages/react-native/__typetests__/tsconfig.json",
"test": "jest",
"fantom": "./scripts/fantom.sh",
"fantom-cli": "./scripts/fantom-cli.sh",
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
6 changes: 0 additions & 6 deletions packages/jest-preset/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,3 @@ module.exports = {
```

You can further customize your Jest configuration by specifying other options. See [Jest's `jest.config.js` documentation](https://jestjs.io/docs/configuration) to learn more.

### Migration Note

This Jest preset used to be part of the core `react-native` package and accessible at `react-native/jest-preset.js`. As long as `@react-native/jest-preset` is installed, `react-native/jest-preset.js` will be aliased to this package and continue to work but is deprecated.

Follow the installation instructions above to migrate to `@react-native/jest-preset` and change `preset: 'react-native'` to `preset: '@react-native/jest-preset` to migrate.
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
Loading
Loading