Skip to content

Native Modules #96

@grabbou

Description

@grabbou

I've been thinking about the state of native modules and what we could do better to improve them. After working with few other React Native team members on the details, I think I am ready to share what I've came up with so far.

This issue is based on some feedback I gathered from #60. Since the scope is a bit broader than just linking libraries, I decided to open a new issue.

This document describes the current developer experience working with native modules, most important problems and areas where we could improve. The intent is for this issue to become umbrella for various RFCs that will be submitted as a part of it.

Glossary of terms

I want this issue to be super easy to understand, hence I decided to break down the terminology that I use throughout this issue. If you find anything unclear, please comment and I'll add it here.

  • React Native library - a npm package that can contain one or many native modules
  • Native Module - a module that extends capabilities of React Native. It usually ships with Android and iOS source code.
  • Native Dependency - native module that depends on other Java and/or ObjC libraries, that may be distributed as source, or as compiled binaries.
  • Linking a dependency - the process of adding files present in a native module to your iOS and Android projects
  • react-native- React Native CLI
  • react-native link - command to link a dependency to your project. It is a part of React Native CLI

Problem

Working with native modules in React Native can be overwhelming. From the library author perspective, it is not clear how native dependencies could be handled and what is the recommended structure for a module.

From the app developer perspective, it is not easy to consume React Native dependencies from the npm registry. Some of them are pure Javascript libraries that can be installed just like any other Javascript dependency. Others require additional steps to be performed (namely Android and iOS files to be added to respective iOS and Android projects). This can be confusing and increases the entry-barrier for the developers that want to extend basic capabilities of React Native with 3rd party modules.

See the reasons highlighted below for details.

Lack of standard

There is no standard or recommended way to create a React Native library. This problem is especially visible on iOS, where no built-in package manager for native dependencies is available (see "No support for native dependencies on iOS" section).

As a result, each React Native library tends to have a slightly different structure in order to satisfy their use-case. This makes it hard to provide a decent tooling support that can automate most of the repeated steps (see "Poor tooling support" section).

It is also not clear at a glance what steps need to be performed. This degrades the developer experience from a trivial “yarn add” to many complex instructions.

No support for native dependencies on iOS

Some React Native dependencies rely on native libraries in order to provide their functionality. It is a common practice to wrap a native SDK and expose it to React Native developers.

On Android, the open-source community settled on Gradle as the go-to package manager. However, iOS doesn’t have a built-in package manager that would handle downloading, compiling and linking native dependencies to a project. As a result, React Native developers are often required to perform a set of manual steps, some of them, requiring a decent native knowledge.

Few approaches to handling native dependencies have developed over the course of few years within the community, some of them are listed below:

  • shifting the responsibility over to user-space. For example, react-native-fbsdk requires developers to install Facebook SDK themselves and make sure its already configured in their projects at the time of installing this library
  • using a iOS tool specialised in handling native dependencies. The most popular one amongst the community and Expo seems to be CocoaPods. It relies on a Podfile (similar to package.json, where dependencies are defined). This is an equivalent of Gradle for iOS. React Native already provides CocoaPods Podspecs for its built-in modules
  • checking in native files to repository. Similar to React Native shipping with precompiled Android sources for faster build times, some library developers opt to check-in SDKs and native dependencies to the Git repository. That way, they are distributed over through npm registry.

There's been a great discussion in #60 on how this could be addressed.

Poor tooling support

Since there is no recommended or the de facto standard way to create a React Native library, there are no commands that would support developers in making them. That contributes to a poor debugging experience (discussed in the next section).

There are few community driven initiatives, such as react-native-create-library (which is a perfect example of repeating format from a different module, in this case - react-native-share).

At the same time, on iOS, “react-native link” is unable to offer the level of reliability and performance that React Native developers would expect. Since there’s no standard way of managing React Native dependencies, it has to apply few heuristics in order to work (for example, locate Android and iOS source files using glob).

Debugging

The debugging experience while working with native modules could be improved. In order to test their libraries, developers need to create an example React Native app where they can require their library from source and make sure it meets their expectations.

This can be set up in many ways, each having a different developer experience.

For example, one of the most popular ways of setting up a demo project for the library is to run a “react-native init” command to create a stand-alone React Native app and make the library a dependency.

This is usually done using file protocol (symlinks are not supported) that tells a package manager to copy files from the specified location and move them over to node_modules, as if they were installed from the registry. You can see it live in react-native-fbads.

The above approach has the following drawbacks:

  • developers need to install dependencies twice, once for the library itself (by running npm install or yarn install in the root of the project) and second time, for the demo app itself (this time, in the example/ folder)
  • there are two versions of React Native installed that need to be kept in sync
    changes to the library are not respected in real-time - developers have to re-install dependencies for
  • the demo app in order for the package manager to copy the new files again

Solution - Define the standard

We should describe how to create a React Native library and build reliable tooling around that. This should be done via series of RFCs.

Managing native dependencies

The standard should describe how to create a React Native library that has native code and native dependencies and how it can be exported for the end user in the easiest way possible.

While doing so, we should focus on maximising the strengths and capabilities of existing package managers for the given platform, such as Gradle and CocoaPods in order to reduce the overall maintenance cost and support of additional use-cases. Going this direction would make the specification really small in terms of the rules and make it familiar to those who have experience working with iOS and Android already.

One advantage is that Expo is also already using CocoaPods and Gradle to support "ejected" applications, which might be an additional driver for promoting this pattern throughout the community. That means we can share the effort to support the same set of tools for both Expo and React Native developers. All in all, Expo developers interact with React Native CLI once they eject from Expo.

Complementary tooling for scaffolding libraries

Creating packages for React Native’s been always pretty complex and manual process that can potentially lead to annoying issues down the road. It would be great to extend the capabilities of the “react-native” command line tools to support that use-case.

The suggested approach would be to provide support for:

  • creating a native library that conforms to the standard,
  • validating that a package is a valid library that conforms to a standard

See Flutter documentation for reference on how this can be addressed.

Better debugging experience

The standard project structure should provide great developer experience out of the box. That said, library generated with the react-native (see Improved Tooling section) should have an example app that avoids the drawbacks mentioned in the Debugging section.

A good example of such project structure is react-native-opentok that doesn’t copy library sources to example app’s node_modules, making the changes done to the library being reflected in the realtime.

Another one is react-native-camera that uses custom Metro configuration to require the library from source.

Whole-new linking experience

The purpose of the react-native link would be to automate adding a React Native library to a React Native project. It would only work for libraries that follow the standard library structure. That means, linking a library that has "Gradle" and "CocoaPods" inside.

Having a clear set of supported use-cases would make it much easier to make it reliable and reduce the overall cost of its maintanance. The current react-native link has been working in more-or-less its original form for around 2 years. That's a lot given the amount of updates that did happen to React Native.

The intent is for React Native developers to be able to run react-native install XYZ without worrying about the details of a particular library. With CocoaPods and Gradle, it wouldn't require much of the effort (especially compared with the current link maintanance) to offer such improvement.

Additional platforms

This proposal would be only targeted at Android and iOS (that are part of React Native right now). It would be great to work with Windows team on specifying the details for their platform. Right now, they can extend react-native link (and other CLI commands) on themselves. I would expect them to provide support for their standard using this mechanism too.

Next steps

I would appreciate your feedback on this document and encourage a discussion what we could do better in order to make this experience better for React Native developers.

The next step would be to submit an RFC with the standard structure for a library, how it should manage dependencies, how the example app should be provided for best debugging experience.

Rollout

Once the RFC gets merged, we will work inside react-native-cli to implement it. This will require some additional tools and commands to be created (they will be described in the RFC).

It is worth noting that React Native itself, as a framework, will remain neutral as far as the project structure and how the dependencies should be handled. The term "standard" is probably overused here. I decided to open this issue here, instead of react-native-cli repository for better visibility.

Metadata

Metadata

Assignees

No one assigned

    Labels

    💡 ProposalThis label identifies a proposal🗣 DiscussionThis label identifies an ongoing discussion on a subject

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions