Running React Native everywhere: Windows & macOS
Tags: javascript, webdev, react, reactnative
Published: 2021-09-22 11:58:19

---
title: Running React Native everywhere: Windows & macOS
published: true
date: 2021-09-22 11:58:00 UTC
tags: #javascript #webdev #react #reactnative
canonical_url: https://mmazzarolo.com/blog/2021-09-19-running-react-native-everywhere-windows-macos/
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zk8dx7tdu3su6ow5mlnk.png
---

## TL;DR

Third part of the ["Running React Native everywhere"](https://dev.to/mmazzarolo/running-react-native-everywhere-4gpp) series: a tutorial about structuring your monorepo to run multiple React Native apps targeting different platforms.

This time, we'll focus on the __Windows__ and __macOS__ platforms.

## About React Native for Windows + macOS

**[React Native for Windows + macOS](https://microsoft.github.io/react-native-windows/) brings React Native support for the [Windows SDK](https://developer.microsoft.com/en-us/windows/downloads) as well as the [macOS 10.13 SDK](https://www.apple.com/newsroom/2017/09/macos-high-sierra-now-available-as-a-free-update/)**. With this, you can use JavaScript to build native Windows apps for [all devices supported by Windows 10 and higher](https://developer.microsoft.com/windows/get-started-windows-10) including PCs, tablets, 2-in-1s, Xbox, Mixed reality devices, etc., as well as the macOS desktop and laptop ecosystems.

The React Native for Windows + macOS development flow is very similar to the Android and iOS one. If you're already familiar with building mobile React Native apps and with the Windows or macOS SDK, you should be able to quickly jump into a React Native for Windows + macOS codebase.

Both the Windows and macOS platforms are currently being maintained by Microsoft.  
As of today, React Native for Windows is in a much more stable shape than React Native for macOS, but they're both getting better and better.

The React Native for Windows + macOS documentation follows a classic approach to setup the projects: it shows you how to add them directly in an existing React Native mobile app, resulting in having the Android, iOS, macOS, and Windows code located in the same directory, sharing a single metro bundler setup.    
As explained in the [monorepo setup guide](https://dev.to/mmazzarolo/running-react-native-everywhere-yarn-workspaces-monorepo-3j5g), we'll follow a slightly different approach and create a workspace for each platform. By doing so, __we're making our codebase a bit more complex in exchange for a simplified incremental React Native update path__, because we won't be forced to use the same React Native version on all platforms.  

> Please keep in mind that this solution is probably a bit over-engineered if you __do__ already expect to always use the same React Native version on all platforms. In that case, I'd suggest you to go with the classic approach shown in the React Native for Windows + macOS documentation 👍

To add support for the Windows and macOS platforms to our monorepo, we'll follow the same pattern we used with the mobile app, creating a workspace for each platform:

```bash
.
└── <project-root>/
    └── packages/
        # React Native JavaScript code shared across the apps
        ├── app/
        │   ├── src/
        │   └── package.json
        # macOS app configuration files and native code
        └── macos/
        │   ├── macos/
        │   ├── index.js
        │   ├── metro.config.js
        │   └── package.json
        # Android/iOS app configuration files and native code
        └── mobile/
        │   ├── android/
        │   ├── ios/
        │   ├── index.js
        │   ├── metro.config.js
        │   └── package.json
        # Windows app configuration files and native code
        └── windows/
            ├── windows/
            ├── index.js
            ├── metro.config.js
            └── package.json
```

Something worth noticing is that React Native for Windows + macOS uses metro bundler, just like React Native mobile does.  
So we can leverage the same monorepo tooling we used in our mobile app! 💥

## Windows

To create the `windows` workspace we'll follow the same procedure we used for the `mobile` one.

First of all, add the `react-native-windows` library to the `nohoist` list in the root's `package.json`:

```diff
 {
   "name": "my-app",
   "version": "0.0.1",
   "private": true,
   "workspaces": {
     "packages": [
       "packages/*"
     ],
     "nohoist": [
       "**/react",
       "**/react-dom",
       "**/react-native",
       "**/react-native/**",
+      "**/react-native-windows",
     ]
   }
 }
```

Then, from the `packages` directory, scaffold a new React Native for Windows project:

```bash
npx react-native init MyApp --template react-native@^0.65.0 && mv MyApp windows
```

Update `windows/package.json`:

```diff
 {
-  "name": "MyApp",
+  "name": "@my-app/windows",
   "version": "0.0.1",
   "private": true,
   "scripts": {
     "android": "react-native run-android",
     "ios": "react-native run-ios",
     "start": "react-native start",
     "test": "jest",
     "lint": "eslint ."
   },
   "dependencies": {
+    "@my-app/app": "*",
     "react": "17.0.2",
     "react-native": "0.65.1"
   }
```

Update `windows/index.js` to point to our `app` workspace:

```diff
 import { AppRegistry } from "react-native";
-import App from "./App";
+import App from "@my-app/app";
 import { name as appName } from "./app.json";

 AppRegistry.registerComponent(appName, () => App);
```

And finalize the Windows project setup:

- Install [all the required dependencies](https://microsoft.github.io/react-native-windows/docs/rnw-dependencies). Microsoft has done a phenomenal job here: you can check and install all the development dependencies [with a single script](https://microsoft.github.io/react-native-windows/docs/rnw-dependencies#install-the-development-dependencies).
- Install [the Windows extensions](https://microsoft.github.io/react-native-windows/docs/getting-started#install-the-windows-extension). This process will add the `windows` directory (with the native Windows SDK code) to the workspace and update the metro configuration to support the Windows platform.
- Remove the `ios` and `android` directories from the workspace.

Last but not least, use [`react-native-monorepo-tools`](https://github.com/mmazzarolo/react-native-monorepo-tools) to make metro compatible with Yarn Workspaces:

```diff
const path = require("path");
const exclusionList = require("metro-config/src/defaults/exclusionList");
const { getMetroConfig } = require("react-native-monorepo-tools");

+// Get the metro settings to make it compatible with Yarn workspaces.
+const monorepoMetroConfig = getMetroConfig({
+  reactNativeAlias: "react-native-windows",
+});

module.exports = {
  resolver: {
    blockList: exclusionList([
      // This stops "react-native run-windows" from causing the metro server to crash if its already running
      new RegExp(
        `${path.resolve(__dirname, "windows").replace(/[/\\]/g, "/")}.*`
      ),
      // This prevents "react-native run-windows" from hitting: EBUSY: resource busy or locked, open msbuild.ProjectImports.zip
      /.*\.ProjectImports\.zip/,

+     // Ensure we resolve nohoist libraries from this directory.
+     ...monorepoMetroConfig.blockList,
    ]),
+   // Ensure we resolve nohoist libraries from this directory.
+   extraNodeModules: monorepoMetroConfig.extraNodeModules,
  },
+ // Add additional Yarn workspace package roots to the module map.
+ // This allows importing from any workspace.
+ watchFolders: monorepoMetroConfig.watchFolders,
  transformer: {
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: true,
      },
    }),
  },
};
```

That should be it! We can now run `yarn windows` from the `windows` workspace to run the app.

## macOS

Like for Windows setup, to create the `macos` workspace we'll follow the same procedure we used for the `mobile` one.

The main difference here is that, as of today, **the latest stable version available for React Native for macOS is 0.63**.  
So we need to take into account that our app will run on two different React Native versions: `0.65` for Android, iOS, and Windows, and `0.63` for macOS.

Let's start by adding the `react-native-macos` library to the `nohoist` list in the root's `package.json`:

```diff
{
  "name": "my-app",
  "version": "0.0.1",
  "private": true,
  "workspaces": {
    "packages": [
      "packages/*"
    ],
    "nohoist": [
      "**/react",
      "**/react-dom",
      "**/react-native",
      "**/react-native/**",
+     "**/react-native-macos",
      "**/react-native-windows"
    ]
  }
}
```

Then, from the `packages` directory, scaffold a new React Native for macOS project:

```bash
npx react-native init MyApp --template react-native@^0.65.0 && mv MyApp macos
```

Update `macos/package.json`:

```diff
 {
-  "name": "MyApp",
+  "name": "@my-app/macos",
   "version": "0.0.1",
   "private": true,
   "scripts": {
     "android": "react-native run-android",
     "ios": "react-native run-ios",
     "start": "react-native start",
     "test": "jest",
     "lint": "eslint ."
   },
   "dependencies": {
+    "@my-app/app": "*",
     "react": "16.13.1",
     "react-native": "0.63.0"
   }
```

Update `macos/index.js` to point to our `app` workspace:

```diff
 import { AppRegistry } from "react-native";
-import App from "./App";
+import App from "@my-app/app";
 import { name as appName } from "./app.json";

 AppRegistry.registerComponent(appName, () => App);
```

And finalize the macOS project setup:

- Install [the macOS extensions](https://microsoft.github.io/react-native-windows/docs/0.63/rnm-getting-started#install-the-macos-extension). This process will add the `macos` directory (with the native macOS SDK code) to the workspace and update the metro configuration to support the macOS platform.
- Remove the `ios` and `android` directories from the workspace.

Last but not least, use `react-native-monorepo-tools` to make metro compatible with Yarn Workspaces:

```diff
const path = require("path");
const exclusionList = require("metro-config/src/defaults/exclusionList");
const { getMetroConfig } = require("react-native-monorepo-tools");

+// Get the metro settings to make it compatible with Yarn workspaces.
+const monorepoMetroConfig = getMetroConfig({
+  reactNativeAlias: "react-native-macos",
+});

module.exports = {
  transformer: {
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: true,
      },
    }),
  },
+ // Add additional Yarn workspace package roots to the module map.
+ // This allows importing from any workspace.
+ watchFolders: monorepoMetroConfig.watchFolders,
+ resolver: {
+   // Ensure we resolve nohoist libraries from this directory.
+   blacklistRE: exclusionList(monorepoMetroConfig.blockList),
+   extraNodeModules: monorepoMetroConfig.extraNodeModules,
+ },
};
```

Run `yarn macos` (from the `macos` workspace) et voilà, our React Native app is now running on macOS!

![](https://mmazzarolo.com/static/c618cffccc8665dcaf1fb392767b6810/7321b/macos.png)

## On supporting different React Native versions

Generally, supporting different React Native versions might sound complicated.  
From my experience, though, it will rarely be a problem. We only have to worry about breaking changes of React Native JavaScript API/components, which aren't **that** common nowadays.  
And, even if it happens, let's keep in mind that we can always [encapsulate platform-specific code in multiple ways](https://reactnative.dev/docs/platform-specific-code).

## Root-level scripts

Just like we did for the `mobile` package, I recommend adding a few scripts to the top-level `package.json` to invoke workspace-specific scripts (to avoid having to `cd` into a directory every time you need to run a script).

Add the following scripts to the Windows workspace:

```json:title=my-app/packages/windows/package.json
"scripts": {
  "start": "react-native start",
  "windows": "react-native run-windows"
},
```

And the following scripts to the macOS workspace:

```json:title=my-app/packages/macos/package.json
"scripts": {
  "macos": "react-native run-macos",
  "xcode": "xed macos",
  "start": "react-native start",
},
```

And then you can reference them from the project root this way:

```json:title=my-app/package.json
"scripts": {
  "macos:metro": "yarn workspace @my-app/macos start",
  "macos:start": "yarn workspace @my-app/macos macos",
  "macos:xcode": "yarn workspace @my-app/macos xcode",
  "windows:start": "yarn workspace @my-app/windows windows",
  "windows:metro": "yarn workspace @my-app/windows start"
},
```

## Compatibility and platform-specific code

React Native for Windows + macOS provides compatibility with the vast majority of React Native’s JavaScript API. Features deprecated in React Native should be considered unsupported in React Native for Windows + macOS.  
See ["API Parity"](https://microsoft.github.io/react-native-windows/docs/parity-status) for details.

Also, React Native provides two ways to organize your Windows-specific and macOS-specific code and separate it from the other platforms:

- Using the [`platform` module](https://reactnative.dev/docs/platform-specific-code#platform-module).
- Using [platform-specific file extensions](https://reactnative.dev/docs/platform-specific-code#platform-specific-extensions).

## Next steps

In the next step, we'll add support for the web to our monorepo.

Stay tuned!

- [Overview](https://dev.to/mmazzarolo/running-react-native-everywhere-4gpp)
- [Monorepo setup](https://dev.to/mmazzarolo/running-react-native-everywhere-yarn-workspaces-monorepo-3j5g)
- [Android & iOS](https://dev.to/mmazzarolo/running-react-native-everywhere-android-ios-3451)
- **Windows & macOS (☜ you’re here)**
- [The Web](https://dev.to/mmazzarolo/running-react-native-everywhere-the-web-h6d)
- [Electron & browser extension](https://dev.to/mmazzarolo/running-react-native-everywhere-browser-extensions-electron-46hh)

> _Originally published at [mmazzarolo.com](https://mmazzarolo.com/blog/2021-09-19-running-react-native-everywhere-windows-macos/)_