Bundling React Native for the Web with Webpack and TypeScript
Recently I decided to make a web demo for my React Native component for creating preferences screens:
I, like many other people, was under the impression that React Native applications could easily be bundled for the web. After all, react-native-web is now an official part of React Native.
Turns out, this is easier said then done. There is of course expo, which can make it seem easy, but if you need something less standard, then things can get very rough very quickly.
There is of course the documentation by the original author of react-native-web — but if you browse his issues — and you inevitably will — looking for clues about arcane webpack error messages — you will find tons of issues and you can even trace the growing annoyance of its developer, forced to provide technical support for webpack — over those issues for the last two years — by now he is simply immediately closing all issues about webpack bundling problems. His message is very clear: raw bundling of React Native apps for the Web is not for the faint hearted and if you do not feel like browsing through tons of source code and eventually writing a webpack loader of your own — then better go for expo. It is there for a reason.
Still, if you need some special features, like a web demo of a component in my case, raw webpack bundling is the only way to go.
As I feel that I have accumulated some precious experience in this matter, I decided to share it with you.
The rest of this story will assume you that you have read the original guide at https://necolas.github.io/react-native-web/docs/, then you did exactly how its author said, and nevertheless you got some weird errors.
Here be dragons: webpack error messages
webpack error message when bundling React Native come essentially in two flavors.
Errors about missing components
What is key to understand here is how react-native-web works — it replaces the react-native module with a react-native-web module through an alias — that can be resolved by webpack itself, its babel plugin or the TypeScript loader if you need it. The original configuration relies on webpack — better stick to it. Avoid ts-loader, it is far too complex and will require separate configurations for your code and for the React Native modules— babel is the way to go if you use TypeScript.
So far so good. The only thing necolas didn’t tell you is that his setup requires an ideal world and we do not live in an ideal world. Many 3rd party react-native modules do not play by the rules. Instead of including the react-native libraries through their official entry points, they deep link inside the source. This way of linking is not, and cannot, be supported by react-native-web.
So the key thing that you should notice in this error output is react-native in the call stack. This error message contains a call stack — at the bottom is your code, then it calls @react-navigation, then you enter react-native. This is exactly what should not happen. First clue: If you have react-native in your stack, this is not good — you should have react-native-web. @react-navigation is deep-linking into react-native and you must break this chain somewhere.
Usually, in this case, there will be a component somewhere in this stack that has an alternative implementation in react-native-web — you will have to find which.
A web alternative of Utilities/Platform
is provided by react-native-web but webpack cannot find it because of the deep link.
An even better alternative is to simply replace SafeAreaView
from react-native-safe-area-context with SafeAreaView
from react-native-web, breaking the chain in the middle. As always, the problem lies in a 3rd party module.
A webpack alias can solve this problem:
Errors about invalid syntax
react-native uses some non-standard JavaScript features and one of them is Flow annotations which is the culprit here. You must make sure that all of these files are transpiled by babel. As you followed the original guide exactly how its author said, you already have module:metro-react-native-babel-preset
. Now you just have to be absolutely sure that it applies to all those 3rd party modules:
Notice all the additional includes. These are 3rd party modules not covered by the configuration if simply copied and pasted it from the original guide.
The big roadblock
Ok, by now you have a perfectly working webpack.config.js
, but guess what, it is still not enough. You have hit the big road block — SafeAreaCompat
.
The implementation in react-native-web is not complete and does not support Navigator
s.
Luckily the folks who made expo for us (frankly they deserve paying them a subscription) have been kind enough to open-source their implementation and we simply borrow it:
npm install — save-dev expo-dev-menu
And then one final webpack alias:
'react-native-safe-area-context': 'expo-dev-menu/vendored/react-native-safe-area-context/src'
If you have reached so far, then you probably have it very close to working. I have tried to provide you not only the ready to use answers but also the methodology I used to get so far. Once again, manually bundling React Native for the Web is not an easy task and, depending on your app, may or may not work.
I am an unemployed engineer with a strong interest in Node.js, React and GIS who is in the middle of a huge sex scandal with political implications in France. Should you require help in setting up React Native projects for the web, I will be more than willing to help you.