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
51 changes: 1 addition & 50 deletions packages/react-native/ReactNativeApi.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<0ef11b701c83f2414d61f7ec37c78df1>>
* @generated SignedSource<<0d6ccb57cc35f5518f7a83a709927f2f>>
*
* This file was generated by scripts/js-api/build-types/index.js.
*/
Expand Down Expand Up @@ -398,16 +398,6 @@ declare const staggerImpl: (
time: number,
animations: Array<CompositeAnimation>,
) => CompositeAnimation
declare const States: {
ERROR: "ERROR"
NOT_RESPONDER: "NOT_RESPONDER"
RESPONDER_ACTIVE_LONG_PRESS_IN: "RESPONDER_ACTIVE_LONG_PRESS_IN"
RESPONDER_ACTIVE_LONG_PRESS_OUT: "RESPONDER_ACTIVE_LONG_PRESS_OUT"
RESPONDER_ACTIVE_PRESS_IN: "RESPONDER_ACTIVE_PRESS_IN"
RESPONDER_ACTIVE_PRESS_OUT: "RESPONDER_ACTIVE_PRESS_OUT"
RESPONDER_INACTIVE_PRESS_IN: "RESPONDER_INACTIVE_PRESS_IN"
RESPONDER_INACTIVE_PRESS_OUT: "RESPONDER_INACTIVE_PRESS_OUT"
}
declare const subtract: typeof $$AnimatedImplementation.subtract
declare const subtractImpl: (
a: AnimatedNode_default | number,
Expand Down Expand Up @@ -449,7 +439,6 @@ declare const ToastAndroid_default: {
yOffset: number,
) => void
}
declare const Touchable: typeof TouchableImpl_default
declare const Touchable_default: (
props: TouchableOpacityProps & {
ref?: React.Ref<TouchableOpacityInstance>
Expand All @@ -461,33 +450,6 @@ declare const TouchableHighlight_default: (
ref?: React.Ref<TouchableHighlightInstance>
},
) => React.ReactNode
declare const TouchableImpl_default: {
Mixin: typeof TouchableMixinImpl
renderDebugView: ($$PARAM_0$$: {
color: ColorValue
hitSlop?: EdgeInsetsProp
}) => null | React.ReactNode
}
declare const TouchableMixinImpl: {
withoutDefaultFocusAndBlur: {}
componentDidMount: () => void
componentWillUnmount: () => void
touchableGetInitialState: () => {
touchable: {
responderID: GestureResponderEvent["currentTarget"] | undefined
touchState: TouchableState | undefined
}
}
touchableHandleBlur: (e: BlurEvent) => void
touchableHandleFocus: (e: FocusEvent) => void
touchableHandleResponderGrant: (e: GestureResponderEvent) => void
touchableHandleResponderMove: (e: GestureResponderEvent) => void
touchableHandleResponderRelease: (e: GestureResponderEvent) => void
touchableHandleResponderTerminate: (e: GestureResponderEvent) => void
touchableHandleResponderTerminationRequest: () => any
touchableHandleStartShouldSetResponder: () => any
touchableLongPressCancelsPress: () => boolean
}
declare const TouchableOpacity: typeof Touchable_default
declare const UIManager: typeof UIManager_default
declare const UIManager_default: UIManagerJSInterface
Expand Down Expand Up @@ -5224,7 +5186,6 @@ declare type TimingAnimationConfig = Readonly<
}
>
declare type ToastAndroid = typeof ToastAndroid
declare type Touchable = typeof Touchable
declare type TouchableHighlight = typeof TouchableHighlight
declare type TouchableHighlightBaseProps = {
readonly activeOpacity?: number
Expand Down Expand Up @@ -5329,15 +5290,6 @@ declare type TouchableOpacityTVProps = {
readonly nextFocusRight?: number
readonly nextFocusUp?: number
}
declare type TouchableState =
| typeof States.ERROR
| typeof States.NOT_RESPONDER
| typeof States.RESPONDER_ACTIVE_LONG_PRESS_IN
| typeof States.RESPONDER_ACTIVE_LONG_PRESS_OUT
| typeof States.RESPONDER_ACTIVE_PRESS_IN
| typeof States.RESPONDER_ACTIVE_PRESS_OUT
| typeof States.RESPONDER_INACTIVE_PRESS_IN
| typeof States.RESPONDER_INACTIVE_PRESS_OUT
declare function TouchableWithoutFeedback(
props: TouchableWithoutFeedbackProps,
): React.ReactNode
Expand Down Expand Up @@ -6019,7 +5971,6 @@ export {
TextProps, // 58466ea1
TextStyle, // b62b8399
ToastAndroid, // 88a8969a
Touchable, // c15da0a2
TouchableHighlight, // 20ba0199
TouchableHighlightInstance, // b510c0eb
TouchableHighlightProps, // 337a9164
Expand Down
Loading
Loading