In this lesson we'll use yarn and yarn workspaces to create a monorepo that includes a web creat-react-app, a react-native app, and shared code. We'll use react-app-rewired
and react-app-rewire-babel-loader
to rewire the create-react-app configuration to allow babel to process the shared code from workspace. We'll also use crna-make-symlinks-for-yarn-workspaces
and metro-bundler-config-yarn-workspaces
to configure the react-native app to be able to find the shared code in the workspaces.
Instructor: [00:00] To get started, we're going to create a directory called packages. Let's cd into packages. We'll create a directory called shared. This will act as a shared library between our web and our Native code. We'll cd into our shared directory that we just created and type yarn init -y. This will automatically create a package.json for us.
[00:29] Now that we have our package.json initiated, we'll do a touch and say index.js. This will create an index.js file. We're going to set up some basic information that will display shared across the web and the Native applications. We're going to say const shared = hey. We'll do export default shared.
[00:56] Now that we have our shared code, let's create the other two projects. We're going to cd up to our packages. I'm going to type create-react-app. We're going to call it web. Now that we have all three directories created, we're going to cd up to our main directory and then run yarn init.
[01:17] The reason we did this is that sometimes there are issues when there's a master package.json, and you're installing children that also have package.jsons. This way, we create the children with their package.jsons and then create our master package.json that will define the workspaces.
[01:35] Now that we're in the root directory, we type yarn init. It will walk us through. When we get to private, we want to make sure that we set private to true. Otherwise, our workspaces will not work. With this package.json created here in the root, we need to define our workspaces. We won't need any of this unless you want to identify yourself. We won't have an index.js.
[02:04] All we need to now do is define our workspaces. This is going to take an array. That array is going to be a glob. Rather than defining each individual package, we can say packages/*, and any package that is created in here will be a workspace for Yarn.
[02:28] We'll first get the web version working. We're going to need to use react-app-rewired. This will allow us to add in custom configurations to the React app. We're going to say yarn workspace. We'll specify the workspace that we want to add the dependency to, so that'll be web. Then we'll say add react-app-rewired as well as react-app-rewire-babel-loader. We'll save those as dev.
[03:07] Now that that's installed, we can go take a look at our web package.json and see that we've added our two dependencies here.
[03:15] What we'll need to do is rewire our startup scripts. Instead of react-scripts, we're going to replace it with react-app-rewired. Now we need to create our config, that file specifically named config-overrides.js. This will allow us to receive the config from create-react-app and modify it however we want.
[03:47] Rather than typing a bunch of code, we can go take a look at the example of react-app-rewire-babel-loader. We will copy and paste this into our file here. We'll reconfigure it a little bit. We'll remove some comments. We'll fix this to be the path of the package that we want to process with Babel.
[04:15] The reason that we have to do this is that create-react-app will not modify and babelify any code outside of source. However, our shared code is outside of source, so we need to set up to include a path for Babel to compile. That'll be the path to our shared folder. From our config.overrides, we're going to go up one directory and say shared.
[04:45] Now the only thing to do is to add shared as a dependency to web. If we go here, we can say yarn workspace, specify the workspace that we want to add a dependency to -- it's going to be web. We'll add the name of our dependency, which is shared.
[05:05] We're going to specify the version. Otherwise, Yarn will not install the local version. It will install an npm dependency called shared. We're going to say @^1.00The other way to do this is to edit the package.json manually and then later run a yarn install in the root.
[05:32] Now that we have the web functioning, we need to get our Native to work. We're going to need to add two dependencies that will help us in resolving dependencies that it's a sibling node_modules, but it's in the node_modules for the entire project.
[05:54] How we do that is we're going to do yarn workspace. We'll specify workspace as Native. We'll then say add. We're going to add crna-make-symlinks-for-yarn-workspaces. We'll also add metro-bundler-config-yarn-workspaces. I'm going to go ahead and add those as dev dependencies.
[06:30] Inside of our Native folder, we have a package.json. It links to an entry point. If we went and looked at this entry point, that's going to be inside of node_modules here. However, our node_modules is all the way up here.
[06:54] What we need to do is create a new entry point that maps correctly to our dependencies. We're going to create inside of our Native folder here a new file called AppEntry.js. This file is merely an entry point that will register the root component with Expo. If we look here, we're merely exporting a default app. We aren't actually registering it with React Native.
[07:30] In order to do that, we're going to import KeepAwake and registerRootComponent. We'll import our app. We can see that we're telling it to import this app. If we're in dev mode, we'll keep everything awake. Then we'll register our app.
[07:48] We'll also need to create two configuration files. The first one will be rn-cli.config.js. This one will register the path to the node_modules for the RN packager. Here we'll import path. We'll also import getConfig from our metro-bundler-config-workspaces.
[08:21] We need to inform our config of where our node_modules are located. We're going to say const options = nodeModules. Then we'll do a resolve path, so say path.resolve. From our current directory, we're going to go up two directories -- up one to packages and then up to where our node_modules is located.
[08:51] Now we can do module.exports = getConfig. We'll pass in the current directory name as well as there are options of where our node_modules are located. This will instruct the Metro Bundler packager of where it should look for our dependencies.
[09:12] The other file we need to make is called link-workspaces. Here, we're going to require our crna-make-symlinks. That will return a function that we're going to call with the current directory name.
[09:30] What this is going to do is it's going to traverse through until it hits our package.json that says workspaces, it's going to find all of the workspace packages, and then it's going to inform the Bundler that it needs to include these other packages that are inside of here as a place to search for code.
[09:56] Part of the reason this is so complicated is because the Metro Bundler that powers React Native does not actually support symlinks. We must provide projectRoots, which are like pseudo-symlinks. That is exactly what this crna-make-symlinks-for-yarn-workspaces package is doing. It's automatically traversing and figuring that out for us.
[10:20] Now we need to wire everything up. The first thing that we're going to do is we're actually going to delete our main from here. We're going to create a prestart function, so say prestart. We're going to say node. Then we're going to run our link-workspaces.js function. This will run before the react-native-scripts start function runs. This will then appropriately link everything up.
[10:52] The other thing we need to do is edit our app.json. This is the config for Expo. It determines on how the packager will run. It gets fed into the packager. We're going to say entry point. We're going to supply our ./AppEntry.js.
[11:13] We're also going to add two different configurations -- one is called ignoreNodeModulesValidation, so it won't look in the current directory for the node_modules to validate, as well as the packager options that we want to provide.
[11:28] We're going to pass in our config and then also clear out our projectRoots. The link-workspaces and the rn-cli.config is what's actually wiring up all the stuff to the Metro Bundler to tell it what the projectRoots are and where the node_modules lives.
[11:49] The other settings aren't necessarily important. This just unlists it from being public on Expo, defines our platform's version, and that our application will only work in portrait mode.
[12:06] We did miss one thing. We forgot to add yarn workspace. We forgot to add it to our Native. We're going to add the shared @^1.00We're going to add our shared code dependency to the Native code.
[12:23] From here, we should go and edit the web. We're going to say source app.js. We're going to import our shared code from shared. Because the shared code was just a string that we exported, we're going to render shared code right there.
[12:48] We can test if it works by going into our packages, going into web, and running yarn start. We can see that it's actually imported our shared code. We're able to use shared code inside of our create-react-app web application.
[13:14] If we switch back to our code in our terminal, we can kill that. We can go up here and go to our Native application. Inside of our Native application, we're going to want to edit that code.
[13:29] Inside of our app.js, we'll do an import shared code from shared. We'll render it as some text. We'll verify our package.json to make sure that everything is spelled correctly. We'll fix node link-workspaces, make sure it's linking to the correct file.
[13:56] We can go to our terminal, type yarn start. Now that the packager has successfully started, we can press i to open the iOS simulator. Now that our simulator has started up, we can see that we also have the same exact code shared across web and Native.
Is there any way to do the same for react/ react-native cli (without expo)?
Yeah you should be able to. The CRNA linking stuff that gets setup is just adding projectRoots
since the metro
bundler doesn't support symlinks. So you would likely just need to create and modify an rn-cli.config.js
for normal React.
Can you point me to any source that explains how to do versions and tags in workspaces/monorepo right? It's not clear to me if its better to keep versions (the versions in the package.json files in each workspace) in sync or manage them independently and in the latter case how to create meaningful tags in a Git repo.
At 01:09, how is the native
folder being created? I'm assuming that's with create-react-native-app
, but in the video it appears that running create-react-app web
created both the web
and native
folders.
Yes, this uses CRNA
Is there any way to do the same for react/ react-native cli (without expo)?