-
-
Notifications
You must be signed in to change notification settings - Fork 518
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refact: remove mixed CJS/ESM, refactorize index.native.tsx (#1982)
## Description Users are reporting that on Vite they cannot build their project with react-native-screens because of the mixed CJS/ESM files that are being created while building a bundle (you can see [here](https://publint.dev/react-native-screens@3.27.0) that some package managers reports errors about mixed CJS/ESM files). To reduce that behavior I've decided to remove CJS completely while bundling the project, resulting in building only ESM files. Unfortunately because of that I had to remove optional requiring process inside the index.native.tsx file, but this shouldn't have a large impact while using rn-screens. I also decided to move some parts of the screens implementation to separate files - this should improve readability, better understanding of code for newcomers and should improve developer experience overall. Having only imports and exports in index files is also a good practice - this was my main reason why I've planned to do that. Closes #1908 - I'll try to ensure that this will fix Vite for sure 👍 ## Changes - Disable bundling CJS files from react-native-screens - Refactorize index.native.tsx files to separate files ## Test code and steps to reproduce First, try to bundle the project - you can see that inside `lib` there shouldn't be `common` directory with the CJS files. Then, try to run FabricTestExample with a couple of tests. Application should work properly as usual. ## Developer notes There are some points that I stumbled upon and I need to mention here. - I've managed to move all of the native components from class to function components, **except**: - **Screen:** Unfortunately we need to stay with class components there, as for now we would like to keep behavior of using `setNativeProps` for a screen (does anybody do that? Or is react-native calling this method for a screen wherever? There's a field for a discussion). - **SearchBar:** Because of managing ref's state and dropping it down to the methods responsible for commands I was also unable to convert this to functional component. - I tried to also refactor index.tsx file, but I see no reason to do this. For now I'm keeping it as it is (with only a slight changes to this file): - Because of a conflict of naming between SearchBarCommands (from types.tsx) and SearchBarCommands as a native component -> it's not that easy to fix, so I suggest fixing this in a future (might be also a good first issue). - I also tried to move `index.native.tsx` to `index.tsx` and to move `index.tsx` to `index.web.tsx`, but because of a conflict I described above and because I don't see the point of rendering conditionally native components depending if `Platform.OS !== 'web'` (and rendering a `View` if Platform.OS is web) I'm keeping the current naming. - Let me know what do you think about the refactor of index.native.tsx! This change is a **Proof of concept** and I codenamed it as a second stage of this PR, so we might give it a try, but I'm all ears about your opinion - IMO it is worth merging . ## Checklist - [X] Ensured that nothing changed while refactoring index.native.tsx - [X] Ensured that CI passes
- Loading branch information
Showing
25 changed files
with
1,468 additions
and
1,268 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import React, { PropsWithChildren, ReactNode } from 'react'; | ||
import { Platform, StyleProp, View, ViewStyle } from 'react-native'; | ||
|
||
// Native components | ||
import FullWindowOverlayNativeComponent from '../fabric/FullWindowOverlayNativeComponent'; | ||
const NativeFullWindowOverlay: React.ComponentType< | ||
PropsWithChildren<{ | ||
style: StyleProp<ViewStyle>; | ||
}> | ||
> = FullWindowOverlayNativeComponent as any; | ||
|
||
function FullWindowOverlay(props: { children: ReactNode }) { | ||
if (Platform.OS !== 'ios') { | ||
console.warn('Using FullWindowOverlay is only valid on iOS devices.'); | ||
return <View {...props} />; | ||
} | ||
return ( | ||
<NativeFullWindowOverlay | ||
style={{ position: 'absolute', width: '100%', height: '100%' }}> | ||
{props.children} | ||
</NativeFullWindowOverlay> | ||
); | ||
} | ||
|
||
export default FullWindowOverlay; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { View } from 'react-native'; | ||
import React, { ReactNode } from 'react'; | ||
|
||
export default View as React.ComponentType<{ | ||
children: ReactNode; | ||
}>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
/* eslint-disable @typescript-eslint/no-var-requires */ | ||
import React from 'react'; | ||
import { Animated, View } from 'react-native'; | ||
|
||
import TransitionProgressContext from '../TransitionProgressContext'; | ||
import DelayedFreeze from './helpers/DelayedFreeze'; | ||
import { ScreenProps } from 'react-native-screens'; | ||
|
||
import { | ||
freezeEnabled, | ||
isNativePlatformSupported, | ||
screensEnabled, | ||
} from '../core'; | ||
|
||
// Native components | ||
import ScreenNativeComponent from '../fabric/ScreenNativeComponent'; | ||
|
||
export const NativeScreen: React.ComponentType<ScreenProps> = | ||
ScreenNativeComponent as any; | ||
let AnimatedNativeScreen: React.ComponentType<ScreenProps>; | ||
|
||
// Incomplete type, all accessible properties available at: | ||
// react-native/Libraries/Components/View/ReactNativeViewViewConfig.js | ||
interface ViewConfig extends View { | ||
viewConfig: { | ||
validAttributes: { | ||
style: { | ||
display: boolean; | ||
}; | ||
}; | ||
}; | ||
} | ||
|
||
export class InnerScreen extends React.Component<ScreenProps> { | ||
private ref: React.ElementRef<typeof View> | null = null; | ||
private closing = new Animated.Value(0); | ||
private progress = new Animated.Value(0); | ||
private goingForward = new Animated.Value(0); | ||
|
||
setNativeProps(props: ScreenProps): void { | ||
this.ref?.setNativeProps(props); | ||
} | ||
|
||
setRef = (ref: React.ElementRef<typeof View> | null): void => { | ||
this.ref = ref; | ||
this.props.onComponentRef?.(ref); | ||
}; | ||
|
||
render() { | ||
const { | ||
enabled = screensEnabled(), | ||
freezeOnBlur = freezeEnabled(), | ||
...rest | ||
} = this.props; | ||
|
||
// To maintain default behavior of formSheet stack presentation style and to have reasonable | ||
// defaults for new medium-detent iOS API we need to set defaults here | ||
const { | ||
sheetAllowedDetents = 'large', | ||
sheetLargestUndimmedDetent = 'all', | ||
sheetGrabberVisible = false, | ||
sheetCornerRadius = -1.0, | ||
sheetExpandsWhenScrolledToEdge = true, | ||
} = rest; | ||
|
||
if (enabled && isNativePlatformSupported) { | ||
AnimatedNativeScreen = | ||
AnimatedNativeScreen || Animated.createAnimatedComponent(NativeScreen); | ||
|
||
let { | ||
// Filter out active prop in this case because it is unused and | ||
// can cause problems depending on react-native version: | ||
// https://github.com/react-navigation/react-navigation/issues/4886 | ||
active, | ||
activityState, | ||
children, | ||
isNativeStack, | ||
gestureResponseDistance, | ||
onGestureCancel, | ||
...props | ||
} = rest; | ||
|
||
if (active !== undefined && activityState === undefined) { | ||
console.warn( | ||
'It appears that you are using old version of react-navigation library. Please update @react-navigation/bottom-tabs, @react-navigation/stack and @react-navigation/drawer to version 5.10.0 or above to take full advantage of new functionality added to react-native-screens' | ||
); | ||
activityState = active !== 0 ? 2 : 0; // in the new version, we need one of the screens to have value of 2 after the transition | ||
} | ||
|
||
const handleRef = (ref: ViewConfig) => { | ||
if (ref?.viewConfig?.validAttributes?.style) { | ||
ref.viewConfig.validAttributes.style = { | ||
...ref.viewConfig.validAttributes.style, | ||
display: false, | ||
}; | ||
this.setRef(ref); | ||
} | ||
}; | ||
|
||
return ( | ||
<DelayedFreeze freeze={freezeOnBlur && activityState === 0}> | ||
<AnimatedNativeScreen | ||
{...props} | ||
activityState={activityState} | ||
sheetAllowedDetents={sheetAllowedDetents} | ||
sheetLargestUndimmedDetent={sheetLargestUndimmedDetent} | ||
sheetGrabberVisible={sheetGrabberVisible} | ||
sheetCornerRadius={sheetCornerRadius} | ||
sheetExpandsWhenScrolledToEdge={sheetExpandsWhenScrolledToEdge} | ||
gestureResponseDistance={{ | ||
start: gestureResponseDistance?.start ?? -1, | ||
end: gestureResponseDistance?.end ?? -1, | ||
top: gestureResponseDistance?.top ?? -1, | ||
bottom: gestureResponseDistance?.bottom ?? -1, | ||
}} | ||
// This prevents showing blank screen when navigating between multiple screens with freezing | ||
// https://github.com/software-mansion/react-native-screens/pull/1208 | ||
ref={handleRef} | ||
onTransitionProgress={ | ||
!isNativeStack | ||
? undefined | ||
: Animated.event( | ||
[ | ||
{ | ||
nativeEvent: { | ||
progress: this.progress, | ||
closing: this.closing, | ||
goingForward: this.goingForward, | ||
}, | ||
}, | ||
], | ||
{ useNativeDriver: true } | ||
) | ||
} | ||
onGestureCancel={ | ||
onGestureCancel ?? | ||
(() => { | ||
// for internal use | ||
}) | ||
}> | ||
{!isNativeStack ? ( // see comment of this prop in types.tsx for information why it is needed | ||
children | ||
) : ( | ||
<TransitionProgressContext.Provider | ||
value={{ | ||
progress: this.progress, | ||
closing: this.closing, | ||
goingForward: this.goingForward, | ||
}}> | ||
{children} | ||
</TransitionProgressContext.Provider> | ||
)} | ||
</AnimatedNativeScreen> | ||
</DelayedFreeze> | ||
); | ||
} else { | ||
// same reason as above | ||
let { | ||
active, | ||
activityState, | ||
style, | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
onComponentRef, | ||
...props | ||
} = rest; | ||
|
||
if (active !== undefined && activityState === undefined) { | ||
activityState = active !== 0 ? 2 : 0; | ||
} | ||
return ( | ||
<Animated.View | ||
style={[style, { display: activityState !== 0 ? 'flex' : 'none' }]} | ||
ref={this.setRef} | ||
{...props} | ||
/> | ||
); | ||
} | ||
} | ||
} | ||
|
||
// context to be used when the user wants to use enhanced implementation | ||
// e.g. to use `useReanimatedTransitionProgress` (see `reanimated` folder in repo) | ||
export const ScreenContext = React.createContext(InnerScreen); | ||
|
||
class Screen extends React.Component<ScreenProps> { | ||
static contextType = ScreenContext; | ||
|
||
render() { | ||
const ScreenWrapper = (this.context || InnerScreen) as React.ElementType; | ||
return <ScreenWrapper {...this.props} />; | ||
} | ||
} | ||
|
||
export default Screen; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { ScreenProps } from 'react-native-screens'; | ||
import { Animated, View } from 'react-native'; | ||
import React from 'react'; | ||
|
||
import { screensEnabled } from '../core'; | ||
|
||
export const InnerScreen = View; | ||
|
||
// We're using class component here because of the error from reanimated: | ||
// createAnimatedComponent` does not support stateless functional components; use a class component instead. | ||
export class NativeScreen extends React.Component<ScreenProps> { | ||
render(): JSX.Element { | ||
let { | ||
active, | ||
activityState, | ||
style, | ||
enabled = screensEnabled(), | ||
...rest | ||
} = this.props; | ||
|
||
if (enabled) { | ||
if (active !== undefined && activityState === undefined) { | ||
activityState = active !== 0 ? 2 : 0; // change taken from index.native.tsx | ||
} | ||
return ( | ||
<View | ||
// @ts-expect-error: hidden exists on web, but not in React Native | ||
hidden={activityState === 0} | ||
style={[style, { display: activityState !== 0 ? 'flex' : 'none' }]} | ||
{...rest} | ||
/> | ||
); | ||
} | ||
|
||
return <View {...rest} />; | ||
} | ||
} | ||
|
||
const Screen = Animated.createAnimatedComponent(NativeScreen); | ||
|
||
export const ScreenContext = React.createContext(Screen); | ||
|
||
export default Screen; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { Platform, View } from 'react-native'; | ||
import React from 'react'; | ||
import { ScreenContainerProps } from 'react-native-screens'; | ||
import { isNativePlatformSupported, screensEnabled } from '../core'; | ||
|
||
// Native components | ||
import ScreenContainerNativeComponent from '../fabric/ScreenContainerNativeComponent'; | ||
import ScreenNavigationContainerNativeComponent from '../fabric/ScreenNavigationContainerNativeComponent'; | ||
|
||
export const NativeScreenContainer: React.ComponentType<ScreenContainerProps> = | ||
Platform.OS !== 'web' ? (ScreenContainerNativeComponent as any) : View; | ||
export const NativeScreenNavigationContainer: React.ComponentType<ScreenContainerProps> = | ||
Platform.OS !== 'web' | ||
? (ScreenNavigationContainerNativeComponent as any) | ||
: View; | ||
|
||
function ScreenContainer(props: ScreenContainerProps) { | ||
const { enabled = screensEnabled(), hasTwoStates, ...rest } = props; | ||
|
||
if (enabled && isNativePlatformSupported) { | ||
if (hasTwoStates) { | ||
const ScreenNavigationContainer = | ||
Platform.OS === 'ios' | ||
? NativeScreenNavigationContainer | ||
: NativeScreenContainer; | ||
return <ScreenNavigationContainer {...rest} />; | ||
} | ||
return <NativeScreenContainer {...rest} />; | ||
} | ||
return <View {...rest} />; | ||
} | ||
|
||
export default ScreenContainer; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { View } from 'react-native'; | ||
|
||
export const NativeScreenContainer = View; | ||
export const NativeScreenNavigationContainer = View; | ||
|
||
export default View; |
Oops, something went wrong.