TL;DR
Third part of the "Running React Native everywhere" 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 brings React Native support for the Windows SDK as well as the macOS 10.13 SDK. With this, you can use JavaScript to build native Windows apps for all devices supported by Windows 10 and higher 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, 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:
.
βββ <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
:
{
"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:
npx react-native init MyApp --template react-native@^0.65.0 && mv MyApp windows
Update windows/package.json
:
{
- "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:
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. Microsoft has done a phenomenal job here: you can check and install all the development dependencies with a single script.
- Install the Windows extensions. 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
andandroid
directories from the workspace.
Last but not least, use react-native-monorepo-tools
to make metro compatible with Yarn Workspaces:
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
:
{
"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:
npx react-native init MyApp --template react-native@^0.65.0 && mv MyApp macos
Update macos/package.json
:
{
- "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:
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. 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
andandroid
directories from the workspace.
Last but not least, use react-native-monorepo-tools
to make metro compatible with Yarn Workspaces:
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!
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.
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:
"scripts": {
"start": "react-native start",
"windows": "react-native run-windows"
},
And the following scripts to the macOS workspace:
"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:
"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" 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. - Using platform-specific file extensions.
Next steps
In the next step, we'll add support for the web to our monorepo.
Stay tuned!
- Overview
- Monorepo setup
- Android & iOS
- Windows & macOS (β youβre here)
- The Web
- Electron & browser extension
Originally published at mmazzarolo.com
Top comments (0)