Distributing React Native components with native code
If you’ve ever tried to split out the native code of your React Native project into external packages, you might have encountered a severe lack of documentation. In this article, I will walk you trough a project layout for wrapping up your JS, Obj-C and Java code in a sweet little npm package that supports automatic linking when installed.
The 9 project layout
When working on my own react-native-filesystem, I couldn’t find any good guidelines for how to structure my native code, so I decided to create a boilerplate on Github with a HelloWorld component, hoping it could help someone else trying to accomplish the same.
The 9 project layout is not as retarded as it sounds: it’s three sets of projects with JavaScript, Objective-C and Java, for cross-platform iOS & Android code. I’ll walk you through them.
The root projects
The first set of projects is where your components (and modules) live. This is what will get published to npm, and nothing more. Technically, this is all you need. If you want to develop within the context of an existing application, you can link it directly with a relative path in your package.json
and load changes with an npm install
:
"dependencies": {
"react-native-helloworld": "../path/to/code/"
}
However, you might want to enjoy the quick iteration of test-driven development, and make sure your code base stays stable as it evolves. That’s where the next three projects come in handy.
The test projects
The test projects contain unit-tests for your Obj-C code, unit-tests for your Java-code, and integration-tests written in Javascript.
If you open tests/ios/HelloWorldTets.xcodeproj
in XCode, you'll find the root source code linked under Libraries/HelloWorld
, and an example unit test under the HelloWorldUnitTests target. The HelloWorldIntegrationTests contains a test runner configuration that picks up integration tests from tests/integration-test
. If you select a device to run on (f.ex. an iOS simulator) and hit ⌘+U, both of these test suites will run in the standard XCode test runner.
If you open tests/android
in Android Studio, you can edit the native Java-code and unit tests side-by-side, and run your tests in the visual test runner by right-clicking on the test-package and selecting Run Tests in tests. As you can see, these are just standard JUnit-tests.
Finally, to run the integration tests on Android, run the tests
app on a device or an emulator through Android Studio or react-native run-android
.
You have to click on each test to run them for now, but I’m planning on adding an automated test runner also for Android in the future. The integration tests are written in JavaScript as React components, and if you open the tests/
-folder in your JS-editor of choice, you can edit them in concert with the component code (which you'll find in the symlinks
folder).
The test projects also provide a context for developing the component code with all dependencies (the root projects just link React/Native as peer dependencies).
A 6 project layout might be enough for some, but if you are making something more complex, you should consider including sample code.
The sample projects
The last three projects provide a full-fledged React Native application that demonstrates the practical use of your package. Again, this code is ignored when the npm package is bundled. Ideally, the XCode and Android-projects are nothing more than the boilerplate generated by react-native-cli.
As you can see, the main reason for having all these separate projects is to keep your distributable npm package lightweight. While developing, I work exclusively within the contexts of the three test-projects, and the JS-project of the sample.
The package.json
of the root project includes the following configuration to let react-native-cli know where the Android code is located:
"rnpm": {
"android": {
"sourceDir": "./android"
}
}
The XCode project with the iOS code is detected automatically. This means that once the package is published, it can be installed and linked like this:
npm install react-native-helloworld --save
react-native link react-native-helloworld
Or, if you’re in a hurry, with a single command:
react-native install react-native-helloworld
If you have customised the boilerplate generated by React Native too much for the automatic linking to work, you can still link it manually. But what is it exactly that the automatic linker does?
Make sure the npm-package is installed:
npm install react-native-helloworld --save
iOS
Open your XCode project, right-click on the Libraries folder and select Add files to YourProjectName. Locate and select your-project/node_modules/react-native-helloworld/ios/HelloWorld.xcodeproj
.
Go to your project settings, select your main target, make sure the General tab is selected, find the section Linked Frameworks and Libraries at the bottom, and add libHelloWorld.a
.
Android
Add the two following lines to your-project/android/gradle.properties
:
include ':react-native-helloworld'
project(':react-native-helloworld').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-helloworld/android')
Then add the following line to the dependencies
section of your-project/android/app/build.gradle
:
compile project(':react-native-helloworld')
Finally, add the bold part to your-project/android/app/src/main/java/com/yourproject/MainApplication.java
:
import com.benwixen.helloworld.HelloWorldPackage;
...
public class MainApplication extends Application implements ReactApplication {
return Arrays.asList(
new MainReactPackage(),
new HelloWorldPackage()
);
...
}
Time to make components
That’s it for our layout. Now it’s time to go out and fill the many holes in the React Native ecosystem. Clone the boilerplate if you want a starting point.
With the right set of components, 80–100% shared code between platforms is within reach for most types of applications. Let’s make app development great again!
Follow me on twitter: benwixen
Originally published at www.benwixen.com on November 14, 2016.