Skip to main content

Multi-platform Support for React Native

caution

The feature is still experimental. Behaviors might change in the future.

Benefits

React Native supports conditional bundling of files with platform specific extensions. For example, if you have different implementations of an Image component for iOS and Android, you can have an Image.ios.js file and Image.android.js file, and an import of Image can be resolved to either file based on the platform you are targeting.

These platform specific files live under the same repository, but it would normally require two flowconfigs to check them like the following setup:

.flowconfig
; for ios
[ignore]
.*\.android\.js$
[options]
module.file_ext=.js
module.file_ext=.ios.js
.flowconfig.android
; for android
[ignore]
; Ignore other platform suffixes
.*\.ios\.js$
[options]
module.file_ext=.js
module.file_ext=.android.js

Flow's optional React Native multi-platform support allows you to check your entire project with mixed platforms under a single Flow root, so that during the development of a module with both .ios and .android files, you no longer have to run both Flow servers and constantly switch between different servers to see type errors on different platforms.

Quick Start

You can start by deleting the flowconfig for all other platforms, deleting all the platform specific configs in the only remaining flowconfig, and add the following new lines to the options section:

experimental.multi_platform=true
experimental.multi_platform.extensions=.ios
experimental.multi_platform.extensions=.android

For example, these are the required changes for the .flowconfig example above:

.flowconfig
[ignore]
- .*\.android\.js$
[options]
module.file_ext=.js
- module.file_ext=.ios.js
+ experimental.multi_platform=true
+ experimental.multi_platform.extensions=.ios
+ experimental.multi_platform.extensions=.android

After enabling the new configurations, there will likely be new errors. The sections below explain the additional rules that Flow imposes to check a multiplatform React Native project.

Common Interface Files

Suppose you have a file that imports the Image module, but Image module has different iOS and Android implementations as follows:

MyReactNativeApp.js
import * as React from 'react';
import Image from './Image';

<Image src="/hello.png" />;
<Image src="/world.png" lazyLoading={true} />;
Image.ios.js
import * as React from 'react';

type Props = { src: string, lazyLoading?: boolean };

export default function Image(props: Props): React.Node { /* ... */ }
Image.android.js
import * as React from 'react';

type Props = { src: string, lazyLoading: boolean };

export default class Image extends React.Components<Props> {
static defaultProps: { lazyLoading: boolean } = { lazyLoading: false };
render(): React.Node { /* ... */ }
}

When you enabled multiplatform support, you will likely see that error that the ./Image module cannot be resolved. To fix the error, you need to create a common interface file under the same directory:

Common Interface File in .js.flow

One option is to write a common interface file in .js.flow:

With Component Types

Image.js.flow
import * as React from 'react';

type Props = { src: string, lazyLoading?: boolean };

declare const Image: component(...Props);
export default Image;

With React.ComponentType

Image.js.flow
import * as React from 'react';

type Props = { src: string, lazyLoading?: boolean };

declare const Image: React.ComponentType<Props>;
export default Image;

Flow will ensure that the module types of both Image.ios.js and ./Image.android.js are subtype of the module type of ./Image.js.flow. Flow will also ensure that there exists an implementation for each platform you declared in your .flowconfig.

Common Interface File in .js

Sometimes you might target desktop platforms in addition to iOS and Android, and you only have a special implementation for one platform, and all the other platforms will use the fallback implementation in a .js file. For example:

Image.js
import * as React from 'react';
import DefaultImage from 'react-native/Libraries/Image';

export default DefaultImage;
Image.ios.js
import * as React from 'react';

type Props = { src: string, lazyLoading: boolean };

export default function Image(props: Props): React.Node {
// Custom implementation to take advantage of some unique iOS capabilities
}

In this case, Flow will use the .js file as the common interface file, and check all other platform-specific implementation files' against the .js file. Since the .js file is already a fallback implementation, Flow will no longer require that platform-specific implementation files exist for all platforms.