Using a Yarn Workspaces monorepo to support multiple platforms on React Native: Android, iOS, macOS, Windows, web, browser extension, Electron.
Check out Running React Native everywhere for an in-depth guide on how and why I recommend trying out this approach if you're planning to support multiple platforms in your app.
- Overview
- Yarn Workspaces monorepo setup
- Android & iOS
- Windows & macOS
- The Web
- Browser Extensions & Electron
This monorepo uses Yarn workspaces and TypeScript to support a modular React Native project.
To reduce redundancy, most package managers employ some kind of hoisting scheme to extract and flatten all dependent modules, as much as possible, into a centralized location.
Yarn workspaces store the flattened dependencies in a node_modules
directory in the project root and makes it accessible to the workspace packages by symlinking the libraries in the packages' node_module
directory.
While it might appear that we can access all modules from the project’s root node_modules
, the reality is that build processes sometimes aren't able to traverse symlinks.
This problem is especially prominent in React Native apps, where both the metro bundler and the native code can't follow symlinks.
A common way to solve this issue in React Native monorepos, is to configure the metro bundler and the native layer to use the project's root node_modules
directory instead of the package's one.
While this approach ensures you gain all the benefits of the hoisting process, it introduces a few complexities:
- Each time you update React Native (or a library that requires native linking), you must also update (or at least keep in sync) the native code with the root project's
node_modules
directory. To me, this process has always seemed error-prone, because you're dealing with multiple languages and build-tools. - Suppose your packages need different versions of React Native (or of a library that requires native linking). In that case, you can't easily ensure it will be installed in a specific location (unless you give up the hoisting mechanism) — adding even more complexities to the table.
For these reasons, React Native Universal Monorepo uses a different approach, fully embracing Yarn's nohoist
.
Of course, this comes with drawbacks. The most obvious one is that nohoist modules could be duplicated in multiple locations, denying the benefit of hoisting mentioned above. Therefore, we'll keep nohoist
scope as small and explicit as possible, targeting only problematic libraries.
Thanks to nohoist, we can avoid making changes to the native code, and we can keep the monorepo configuration in the JavaScript land. This means we can even extract common metro and webpack settings in a workspace package to share them easily across the entire project.
Additionally, different platforms can use different versions of React Native (and native libraries), favoring incremental updates instead of migrating the entire project.
Please notice that I'm not saying this is the right way to do React Native monorepos. It's just an approach that I enjoy using.
- Android (React-Native 0.65)
- iOS (React-Native 0.65)
- Windows (React-Native 0.65)
- MacOS (React-Native 0.63)
- Web (React-Native 0.65)
- Web - Browser Extension (React-Native 0.65)
- Web - Electron (React-Native 0.65)
- Clone the repository:
git@github.com:mmazzarolo/react-native-universal-monorepo.git
- Run yarn install
cd react-native-universal-monorepo && yarn
🚧 I'm working on a blog post to explain how to create this monorepo structure from scratch: Running React Native everywhere
- Overview
- Yarn Workspaces monorepo setup
- Android & iOS
- Windows & macOS
- The Web
- Browser Extensions & Electron
In the meanwhile, if you're interested, please check the following guides:
- Run your React Native app on the web with React Native for Web
- Building a desktop application using Electron and Create React App
- Developing a browser extension with Create React App
Development and build commands:
yarn android:metro
: Start the metro server for android/iOSyarn android:start
: Start developing the android appyarn android:studio
: Open the android app on android Studioyarn ios:metro
: Start the metro server for android/iOSyarn ios:start
: Start developing the iOS appyarn ios:pods
: Install iOS cocoapods dependenciesyarn ios:xcode
: Open the iOS app on XCodeyarn macos:metro
: Start the metro server for macOSyarn macos:start
: Start developing the macOS appyarn macos:pods
: Install macOS cocoapods dependenciesyarn macos:xcode
: Open the macOS app on XCodeyarn web:start
: Start developing the web appyarn web:build
: Create a production build of the web appyarn electron:start
: Start developing the electron appyarn electron:package:mac
: Package the production binary of the electron app for macOSyarn electron:package:win
: Package the production binary of the electron app for windowsyarn electron:package:linux
: Package the production binary of the electron app for linuxyarn browser-ext:start
: Start developing the browser extensionyarn browser-ext:build
: Create a production build of the browser extensionyarn windows:metro
: Start the metro server for windowsyarn windows:start
: Start developing the windows app
Other commands (we use ultra-runner to run these commands on all workspaces):
yarn lint
: Lint each projectyarn lint:fix
: Lint + fix each projectyarn test
: Run tests of each projectyarn typecheck
: Run the TypeScript type-checking on each project
While working on React Native in a monorepo, you'll notice that several packages won't work correctly when hoisted — either because they need to be natively linked or because they end up being bundled twice, breaking the build (e.g., react
, react-dom
).
This is not an issue with the approach used in this project per se. It's more of a common problem with monorepos.
To fix these issues, we mark them as nohoist, so they will be installed in each package that depends on them.
In this monorepo, you can see an example of such libraries in react-native-async-storage
.
In the metro bundler and Webpack configs used across the monorepo, I'm using a set of build-tools that ensures nohoisted packages are resolved correctly.
So, as long as you add these libraries to the nohoist
list, you should be good to go 👍
Contributions, discussions, and feedback are welcome! Please keep in mind that this project is still a WIP, so I suggest asking if there are any active plans on feature changes before submitting new PR 👍