DEV Community

loading...

Part 1 – Converting react native app to react-native-web (react PWA) in monorepo architecture

Zia Ul Rehman
Originally published at ziatechblog.wordpress.com on ・12 min read

TL;DR :

This series is about my journey of converting and existing react native app to render on web with same(90%+) code, using the brilliant react-native-web project whichcame out of twitter.

Prerequisites:

This article assumes you already have some experience with reactjs and react-native, you know how to build mobile builds of a RN codebase, and have them run in a simulator.

Disclaimer:

I am mostly experienced in Ruby on Rails full stack development with mostly focus on backend and DevOps, i have worked on and off on different JS frameworks, but I am not an expert(yet) in babel and webpack configurations etc. So proceed with caution, and point out any improvements in the process in comments.

I am not yet sure how many parts this series will take. Also my work of conversion of this app is still WIP, so who knows what and where that work ends up. But i will share my journey anyway.

Also note, we did not had any active android builds, so this series won’t be explaining steps involved with building android successfully. But you should be able to have them run in the similar way as i will explain for ios below.

You can skip Credits and background section if you like and can jump to “_Let’s Just begin the actual stuff!” section._

Credits/Shoutouts:

Before we begin our journey, i wanted to give credits to a couple of people who helped a lot on this journey, first one is the Bruno Lemos, i got the react-native monorepo setup with initial react-native-web setup from his excellent blog post. And other one is Thomas Gladdines, he was so kind to help me through email with all queries i had in the process.

Background:

On a product i am working on, they have a fully functional react-native app released in iOS app store (It did not had android builds and releases, we are planning on that as well but thats not the topic here). We had its RN version recently upgraded from 56.x to 59.9 , the app IS on app store, but with not a lot of users just yet, as it is a startup still trying to get kick-started with their initial contracts and everything. Anyway.

Suddenly one morning, we have an emergency meeting, and the project manager tells us that we have these X and Y clients whom we are going to partner with, and these are business critical deals. And the issue is, they both need a web app instead of the mobile app, that too ASAP. And we need to port our react-native app to web version within a week or so, knowing we are team of only two devs. Yup! Just like that.

And incidentally, both of us had not any experience of converting apps to web. So I was tasked to do some R&D and provide a feasible plan for this conversion with minimum friction and time requirements. So as per pressure from the business side, we had to choose something where we have minimum to none learning curve.

In a perfect world , we will just be putting our code through some code convertor which coverts our react-native app to reactjs web app. But we don’t live in a perfect world, do we? Turns out, react community is making great progress towards hybrid apps and PWAs but it is not yet quiet there, ionic has its react beta out, there is a project called ReactXP from microsoft and expo is also working on web compatibility of its apps. It almost felt like we needed this conversion a couple of years too early.

So keeping in mind our short time notice and business criticality, we just decided to keep our learning curve low and not worrying too much about future and to use react-native-web, which currently officially supports Rn 0.55, and we are on 0.59, as we had saw some people mention they are successfully running RN-web on RN 0.59, some mentioned some hacks like making react-native-web think its RN 0.55 when compiling for web. So we just decided to go with RN-web to get something running on web ASAP.

I had to do a few re-dos to make it all work, thats why i thought it worths to write about it in detail, so people are not stuck where i did stuck and had to re-setup everything in a hope it will work, kind of restart 🙂

Maintainability and future?

This is a tough question, there is active progress on RN web, although it doesn’t look very active as far as commits go, and its having hard time keeping up with latest RN versions, but as more and more people start using this, more and more people will collaborate and participate in its development, and we should see better future.

The monorepo architecture itself is quiet amazing and works flawlessly with RN(except the issues of linking), but only issue we can foresee with react-native-web is its active development. Its great to try out and get something running. But if you face too many issues and/or missing features you can’t live without, and you also happen to have lot of time to transition to ionic or ReactXP or anything like that, you can try those out. But as I mentioned, those are also not yet that mature, so good luck with that as well.

Let’s Just begin the actual stuff!

Before starting, i should stress on importance of version control, just keep committing every small step which worked with proper commit messages, and you will save a lot of time.

Ok, here we go.

Initial monorepo setup:

As i mentioned above, i am using the monorepo architecture to share code between mobile and web, and i followed an excellent blog post on the matter. There is a boilerplate repo also linked in the article, which you can use. But i preferred to setup this whole thing from scratch as described in the post i linked, so have a better sense of understanding of whats actually going on. Turns out, there is a lot going on.

BTW, there is an interesting debate of monorepo vs multi-repo architecture, we are not going that route. But it worths mentioning, when we are using something like RN-web, it makes most sense to have this architecture. Maybe not that much if we were just sharing the services and reducers etc among web and native react.

Ok, now we have our monorepo setup, with basic boilerplate running on both mobile and web. Let’s start importing our existing mobile app to this architecture. Before we do that, i must mention that you can try converting the existing app to monorepo setup in place, but that didn’t work for us, was taking too much time that we decided to just port it over.

Little walkthrough of the monorepo setup

During the setup from above guide(which you have to do from there, i will not be doing that all again over here), you would have noticed we are using yarn, and its workspaces feature. We have a folder called packages on the root, it contains currently 3 sub-folder, each with their own package.json file. but important part is, all the packages will be installed in the node_modules folder at the root. Not in sub node_modules folders.

Lets go through these packages/folders one by one:

1- Components: This folder will hold generic components or the shared code so to speak, in this component we will have everything that we want to share. You can get creative with the naming if you like.

2- Mobile: As name suggests, this folder will hold our mobile specific code. And when working/building mobile we will be staying in this folder. This folder further has the usual folders we see in any react-native app, the ios android src folders etc. You should already know what are those and how we use them.

3- Web: This is where actual web magic happens. This is supposed to be focus of this article.

Gotchas!

Just like there is no free lunch, there are a few issues I have came across. I am pasting first 3 straight from Bruno’s original article.

  1. react-native-web supports most of the react-native API, but a few pieces are missing like Alert, Modal, RefreshControl and WebView
  2. If you come across a dependency that doesn’t work well with the monorepo structure, you can add it to the nohoist list;
  3. react-native link might not work well with monorepo projects without nohoist; to workaround this, use nohoist on **/react-native OR instead of installing the dependencies only using yarn workspace mobile add xxx, install them in the root directory as well: yarn add xxx -W. Now you can link it and then later remove it from the root package.json. (So far what I have been doing is the second option from this. Which is to copy dependencies to root packakeg.jsonand later remove them from there.)
  4. Lookout for library versions when you copy the dependencies from existing app, they WILL change and may jump to latest versions without you noticing if you don’t properly lock them in package.json, so i recommend you spend some time locking them properly and than making sure everything works in old app, before starting to port it.
  5. Be prepared to have some details of the functionality of your mobile app to be compromised, at-least for short term. As odd as it sounds, its a reality I faced, some RN components are not even supported in RN-web, one of them is Alert, which we happen to use a lot, so we will need some patch(if we can find any) to make it work or we will have to use something else to achieve the same functionality.
  6. Unless you decide to limit the width of your web app, you will probably have to fix a lot of responsiveness issues. And for some components/screens of your app. It may even feel like re-writing the view layer. As you can imagine the drastic changes for responsiveness.
  7. I have not yet setup some proper versioning script like the one we were using in the old setup, but that might be a bit of problem for us. And we may need something custom for that, unless we can live without this and waste time every-time we need to release a new version.
  8. Lastly, as of point 4, 5 and 6, you can already guess, this RN and RN-web combo will work best for new react apps aiming for PWA or hybrid results, so to speak. This doesn’t mean it doesn’t worth a shot when you are short on time and want something out on web from existing RN codebase.

Importing existing app to this new setup

Let’s start with dependencies

I decided to first have dependencies moved, installed, and yarn.lock file properly updated and than copy actual code. So i did that. I copied everything under dependencies and devDependencies sections in old package.json(except react and react-native, any any other duplicates), and moved it over to packages/components/package.json sections respectively. (Note that I was not careful about the gotcha number 4 i mentioned above, that caused number of issues for us later on, so lookout for that). After i got everything installed and mobile app was still running fine(of-course, because we haven’t imported anything from these new dependencies), i made a new commit.

Now import actual code

This particular step may sound like an easy thing, but it’s actually not. Believe me, I spent at-least one and a half of day to make it build for mobile in this new architecture! Yup…

To import old code, i will explain what strategy i took, and you can decide what works best for you. I decided to copy whole existing code to components package of the app, which is for the shared code. Idea was to first build the mobile successfully in this architecture, then try on web, and move what needs to be moved to mobile or web specific sub-packages.

So i just copied everything from my old src folder to packages/components/src and in our old setup, we had App.js outside of the src, i moved that inside of the src as well, and had to update some import paths in App.js but that’s fine. Now app should work? No, don’t forget linking and other xcode specific settings your dependencies may require.

Linking libraries and xcode fixes for our app

There might be a better way to do this than what i am going to describe by my experience.

As mentioned in point number 3 of the gotchas section, linking is a bit tricky. Before linking though, if you use cocoa pods, please setup those. Even if you don’t, this might be a good time to use them. Install cocoa pods. When it looks good, choose how would you like to link the libraries, as described in above mentioned section, for now, what i am doing is copying over all dependencies from packages/components/package.json to main package.json, yarn install and run react-native link from main directory. Then remove these dependencies from main package.json and yarn install. Last step, obviously, inside ios directory run pod install.

I had some complications , which turned out to be caused by something else, which i will explain in a minute, but on this stage, what i did was to copy over my old podfile completely. I mean if you have a podfile, it didn’t hurt to copy old one and just fix the references to the node_modules folder present in main directory of the repo.

At this step, you can try and run your .scworkspace file of your project, clean the build have the metro bundler running in the background (as described in the monorepo setup guide i linked command is something like: yarn workspace mobile start), and let’s try building the app. If you are lucky enough, your app will run, mine didn’t. It did built successfully, but it failed when loading files from metro bundler.

Strange path and fs errors

First i had bundling failed: Error: Unable to resolve modulepath... error, i blindly added a package named something path(you can google it up, i don’t remember, maybe it was named just path). Then i started seeing bundling failed: Error: While trying to resolve modulefsfrom file... and node_modules/fs/package.json was successfully found. However, this package itself specifies a main module field that could not be resolvedthats when i thought maybe, **it is something wrong with my config** , not my dependencies, because i have all my dev and other dependencies installed same as in previous app. So it is not the dependencies i need but something else. What is turned out to be? _I missed to copy babel presets from oldbabel.config.js` to new one in mobile package. And that solved both of these issues. _

That was the part where I wasted a lot of my time. after this, i retried the build, low and behold! another error of-course 😀

react-native native module cannot be null errors

This error was appearing after loading from metro bundler, turns out, it is complaining about missing proper linking. As some libraries don’t just require you to run react-native link they sometimes involve manual steps as well, like the react-native-permissions package. So i had two options at this stage:

  1. Go through all my dependencies readme, and make sure we have everything setup as needed.
  2. Match the old .xcworkspace file libraries and linked section and make sure we have everything matching up.

For the shortage of time, i took route 2, i don’t recommend it, especially if you have android builds active as well. This took me some time to make sure we have everything we need. And after a few rounds of failure with similar errors, i was able to run the app.

NOTE: This is the section i am almost sure about that this would have some better way of doing this than the above mentioned two, definitely better than the approach I took. Comment if you can suggest anything, and i will add it in the list for readers.

Boom! Mobile app is running in monorepo setup!

There might be some other bits according to your setup, but that was it for me and i had my mobile build running! Finally!

PS: As i mentioned, i had not properly locked my dependencies, i had some issues in the app at this point, but it was working, and our only goal was to have a web version ready ASAP. So we just took note of those issues and moved on.

download

Well, at-least for part one. We have our mobile app running in this architecture. We can go from here in the next part and actually start porting for web app.

Please share your experiences and anything you want to add/fix in the article.

Till next time, TC.

Part 2 can be found here.

Discussion (0)