Edit (11/1/2018): After I finished this work, I found wix/wml which solves the file syncing issue with minimal pain. While the approach in this post is still generally applicable, I’d recommend starting with
wml rather than using custom scripts to sync the module files.
When reading about various options for native app development, a huge pitch for using React Native (RN) is that you don’t need to switch context & languages, just use what you already know from web apps and it will mostly work!
While this is definitely true for almost all projects, if you’re assembling a product with distinct but similar web and native releases, it’s not just enough to be able work in the same general language, but you want to be able to share common code and functionality between your projects.
The smaller code ecosystem of React Native
Without getting to deep into the weeds, React Native operates differently enough from the typical browser based DOM environment that only a fraction of third party code written from React web will operate seamlessly when pulled into a RN project. This is especially true for anything UI related, as anytime a project throws a
<div> into its output, it’s not longer good to go for RN.
After building a moderately complicated web app, I was used to pulling in well written UI libraries to handle a large amount of simple UI aspects, such as react-bootstrap-table, react-select, and uniforms. These libraries are not perfect, but tend to save you dev time while giving most of the features and UI that you need.
Later on when starting out with React Native, I was surprised to find the ecosystem a lot more limited than I initially expected. There was not a single go-to solution for styling/theming an app, with popular solutions like NativeBase being rather poorly documented and buggy implementation on basic features. I don’t mean to bash their work, but it’s a result of a working with a ‘bleeding-edge’ framework with a smaller dev pool to work with the projects.
As a result of the lesser amount of high-quality libraries, we ended up using a larger amount of custom components than initially expected. The good thing about doing this is React components are actually pretty quick and stable to create, if you do it right, and you can expand them to fit exactly what you need.
Sharing React Native and Web code
After trying a number of methods and hitting issue after issue, the our final solution ended up being relatively simple:
Maintain all module code in a top level directory
Use a simple build script to copy over all the module code into each project directory
When needed to separate RN vs Web specific code, create two copies of a file to be imported with the RN version using the extension
rsync during development, so it only updates files that are changed, and both CRNA and CRA build tools pick up on these changes and refresh the project right after you hit save. This ends up with a process that gives up single-digit second iteration speed when editing modules, along with confidence our web and native module code is always in sync.
Although not done in the example project, this also allows you to import modules from your projects using absolute path for both RN and Web, for example
import FancyText from 'src/modules/fancy-text', avoiding the need to remember deal with dropping in large amounts of
../../.. in larger projects.
The ‘cleaner’ methods that didn’t quite work
There are a number of far more ‘correct’ ways to do this that simply didn’t pan out. Here are a few of them along with why they didn’t work.
Use local npm packages
Use remote npm packages
This can be done via private repository or using github to host the module. While this works better than local packages, they new issue that came up is the huge difference in how the CRNA and CRA were set up to build. Basically React Native compiles everything with Babel, where Webpack in CRA excludes everything in node_modules. This will cause web build errors in all your modules, as Webpack will not be able to properly process anything ES6/JSX if Babel hasn’t processed it yet.
For our Web app, we hadn’t yet ejected CRA and I had no desire to do so just so I can spend a ton of time tweaking Webpack config to make this work. Other people have had success doing it this way, but tweaking config and ‘Monkey Patching’ individual modules seemed more work than it was worth.
- The answer to the above SO post, this solution did work but the actual pack operation takes 20+ seconds, even with a small number of package, which does not allow for acceptable iteration speed when trying to work on modules. It also still required additional dev/build scripts to keep the modules in sync.
Try it out
If you want to see an example of this up and running, I’ve created a github project which uses CRA and CRNA and sets up a few basic examples of using shared components between the two.