House
/
Blog
/
Setting up a Monorepo with React Native You.I and Yarn
React Native

Setting up a Monorepo with React Native You.I and Yarn

In order to simplify dependency management, code reuse, and collaboration across teams, many projects have sought out to use the Monorepo structure.

Setting up a Monorepo with React Native You.I and Yarn

In order to simplify dependency management, code reuse, and collaboration across teams, many projects have sought out to use the Monorepo structure. This post will outline how to achieve a Monorepo folder structure using React Native You.I alongside Yarn Workspaces.

Things this post covers:

- How Yarn Workspaces work.
- The errors Metro will throw.
- Jest Testing.
- A Possible Alternative.

Goal

Our goal is to have two apps in the same repository, and allow these two apps to use the same modules inside a directory named `shared`. We also want to be able to run tests separately for each app.

```jason
- apps
  - app_one
  - app_two

- shared
  - module_one
  - module_two
```

You can go ahead and try to do this yourself, we will generate the apps using `youi-tv cli`, however, if you are not using React Native You.I, you can use the `react-native-cli` instead.

To achieve this goal we will:

Create The Repository

In the location you want to create the project, run the following commands in your terminal line to create the folders, initialize git, initialize yarn, and generate the apps.

Create Folders

```bash
mkdir monorepo
cd monorepo && git init
yarn init
mkdir apps
mkdir shared
```

Generate Apps

```bash
cd apps && youi-tv init app_one
cd apps && youi-tv init app_two

```

Create Shared Modules

```bash
cd shared && mkdir module_one && cd module_one && yarn init
```

Paste the following in your terminal to create the` index.js` file:

```bash
echo "import React from ‘react’;
import { Text } from ‘react-native’;
import { FormFactor } from ‘[@youi/react-native-youi]

export function moduleOne() {
  return <Text>{FormFactor.isTV ? “Module one
for TV” : “Module one for others”}</Text>;
}
">> shared/module_one/index.js
```

```bash
cd shared && mkdir module_two && cd module_two && yarn init
```

Paste the following in your terminal to create the index.js file:

```bash
echo "
export function moduleTwo() {
  return "Module Two";
}
" >> shared/module_two/index.js
```

If you are following along, you should have the following structure by now:

Folder Hierarchy
Folder Hierarchy

Set Up Yarn Workspaces

According to the documentation we have to add the following configuration in our package.json file:

```json
{
  // ... the rest of your package.json is above this
 “workspaces”: {
    “packages”: [
         “shared/*”,    
          “apps/app_one”,    
          “apps/app_two”  
    ]
}
```

This will allow our apps to access the shared packages.

Fixing Yarn Workspaces

Once you have added the configuration, you will have to delete all of your node_modules and install them again.

Run the following command in your terminal line to find all nested node_modules, delete them, and run yarn again:

```bash
find . -type dir -name node_modules | xargs rm -rf && yarn
```

Now that this is done, technically we should be able to build and start our app.

If you remember the `youi-tv cli` output:

```bash
Run `youi-tv docs` to see next steps.
Or run the following (on platform OSX for example):
cd app_two
youi-tv build -p osx
./youi/build/osx/Debug/app_two
```

We should be able to do that and run the app. Let's try?

In `app_one` build an `osx` app by running: `youi-tv build -p osx` inside `apps/app_one`

Then, in that same directory, run `yarn start`

You will see the following error:

```bash
~/youi/monorepo/apps/app_one   master  yarn start
yarn run v1.19.1
$ node node_modules/react-native/local-cli/cli.js start
internal/modules/cjs/loader.js:895
throw err;
^

Error: Cannot find module
‘/Users/andrei/youi/monorepo/apps/app_one/node_modules/react-
native/local-cli/cli.js’
at Function.Module._resolveFilename
(internal/modules/cjs/loader.js:892:15)
at Function.Module._load (internal/modules/cjs/loader.js:785:27)
at Function.Module.runMain (internal/modules/cjs/loader.js:1143:12)
at internal/main/run_main_module.js:16:11 {
code: ‘MODULE_NOT_FOUND’,
requireStack: []
}
error Command failed with exit code 1
info Visit [<https://yarnpkg.com/en/docs/cli/run>](<https://yarnpkg.com/en/docs/cli/run>) for documentation
about this command.
```

What is happening?

If you look inside `<root>/apps/app_one/package.json` the `start` script tries to call `cli.js` with a relative path. And since we are using workspaces, all of our dependencies were lifted up to the root of the project, therefore, they are no longer inside `<root>/apps/app_one/node_modules` , they are instead in `<root>/node_modules`.

We can fix this by using the symlink of `React Native` provided by `yarn`

Replace both `start` and `react-native` scripts with the following:

```bash
“start”: “react-native start”
“react-native”: “react-native”,
```

Note: The react-native script here is required because when we bundle the JavaScript code, our Cmake config runs yarn react-native bundle to achieve this. If you are using just **React Native *omit that script.

Does yarn start work now?

Yes, it should. You can run `yarn start` to test it. Now since you generated the `osx` app, you can run it directly from the terminal line with the following command inside the `app_one` directory:

`./youi/build/osx/debug/app_one`

You will notice it attaches to the Metro packager, however, it fails with a similar message to this:

```bash
Loading dependency graph, done. Error: Unable to resolve module
`./index.youi` from `/Users/andrei/youi/monorepo/.`: The module
`./index.youi` could not be found from
`/Users/andrei/youi/monorepo/.`. Indeed, none of these files exist:
* `/Users/andrei/youi/monorepo/index.youi(.native||.youi.js|.native.js
|.js|.youi.json|.native.json|.json|.youi.ts|.native.ts|.ts|.youi.tsx
|.native.tsx|.tsx)`   * `/Users/andrei/youi/monorepo/index.youi/index(.native||.youi.js|.nat
ive.js|.js|.youi.json|.native.json|.json|.youi.ts|.native.ts|.ts|.yo
ui.tsx|.native.tsx|.tsx)`
```

It can’t find your `index.youi` in `/Users/andrei/youi/monorepo/`

This expected since `index.youi.js` is in `app_one`

To solve this we have to extend Metro’s config to also add this new root which is the `apps/app_one`

Replace `rn-cli.config.js` in `app_one` for the following `metro.config.js`

Note: rn-cli was deprecated in favor of metro.config

```bash
const blacklist = require('metro-config/src/defaults/blacklist')
const path = require('path');

module.exports = {    
      // Add additional folders to watch that are outside app's root
     watchFolders: [path.resolve(__dirname, '../../node_modules'),
path.resolve(__dirname, '../../shared')],
      // Black list build folders to not resolve any JavaScript files
in there.    
      resolver: {
blacklistRE: blacklist([/\\/youi\\/build\\/.*/])
       }
};
```

By putting this `metro.config.js` in `app_one` Metro understands the new `root` of the project.

Problems with hoisting

Once you have overcome the issues with the root of the project. You can try running `yarn start` again and connect your app. You will run into the following problem:

```bash
Loading dependency graph, done. error: bundling failed: Error:
Unable to resolve module Composition from
/Users/andrei/youi/monorepo/node_modules/@youi/react-native-
youi/Libraries/react-native-youi/react-native-youi-
implementation.js: Module Composition does not exist in the Haste
module map
```

or

```bash
Unable to resolve module `AccessibilityInfo` from
`/Users/andrei/youi/monorepo/node_modules/react-
native/Libraries/react-native/react-native-implementation.js`:
Module `AccessibilityInfo` does not exist in the Haste module map
```

The following error is related to how the workspace lifts up your dependencies to the root of your project. We can disable this feature per module by changing the configuration of the workspace as follows:

```json
"workspaces": {
          "nohoist": [
              "**react-native**"    
            ],
               "packages": [
              "shared/*",
               "apps/app_one",
               "apps/app_two"    
            ]  
      },
```

Note: Adding "react-native" includes both react-native and the react-native-youi folder to the no hoist rule.

A Working App

And now you should be able to run your app and see it connect correctly.

App One Running in Monorepo Project
App One Running in Monorepo Project

Jest Testing

The last piece of the puzzle is to make sure the tests work. Let's create the following test inside `app_one`

You can create a file in `<root>/apps/app_one/app.test.js` with the following test:

```bash
import React from 'react';
import { View } from 'react-native';
import { FormFactor } from '[@youi/react-native-youi]

describe ('App', () => {    
     it('should work', () => {
       expect(true).toBe(true);
     })        
     it('should have react-native modules', () => {
       expect(View).toBeTruthy();
     })
it('should have @youi/rect-native-youi modules', () => {
       expect(FormFactor).toBeTruthy();    
      })
})
```

Then inside `app_one` folder you can run:

`yarn test`

Output:

```bash
✘  ~/youi/monorepo/apps/app_one   master  yarn test

yarn run v1.19.1
$ jest [
‘/Users/andrei/youi/monorepo/apps/app_one/node_modules/react-
native/’,
‘/Users/andrei/youi/monorepo/apps/app_one/node_modules/@youi/react-
native-youi/’ ] FAIL ./app.test.js ● Test suite failed to run
/Users/andrei/youi/monorepo/apps/app_one/node_modules/@youi/react-
native-youi/jest/setup.js:328 import { WebSocket } from ‘mock-
socket’; ^^^^^^ SyntaxError: Cannot use import statement outside a
module at ScriptTransformer._transformAndBuildScript
(../../node_modules/@jest/transform/build/ScriptTransformer.js:537:1
7) at ScriptTransformer.transform
(../../node_modules/@jest/transform/build/ScriptTransformer.js:579:2
5) Test Suites: 1 failed, 1 total Tests: 0 total Snapshots: 0 total
Time: 0.32s Ran all test suites. error Command failed with exit code
1. info Visit (<https://yarnpkg.com/en/docs/cli/run>) for documentation
about this command.
```

This occurs because `react-native` and `@youi/react-native-youi` ship uncompiled ES6 code. For Jest to compile this code you must whitelist it in the `transformIgnorePatterns` option in your `package.json` as follows:

```bash
“transformIgnorePatterns”: [ “/node_modules/(?!react-native|@youi)” ],
```

After making this change, you can run `yarn test` again.

You should see the following problem:

```bash
yarn workspace v1.19.1
yarn run v1.19.1
$ jest
[
'/Users/andrei/youi/monorepo/apps/app_one/node_modules/react-native/','/Users/andrei/youi/monorepo/apps/app_one/node_modules/@youi/react-native-youi/'
]
FAIL  ./app.test.js
App
✕ should work (7ms)
● App › should work
Configuration error:
Could not locate module React mapped as:
/Users/andrei/youi/monorepo/apps/app_one/node_modules/react.
Please check your configuration for these entries:
{
"moduleNameMapper": {
"/^React$/":
"/Users/andrei/youi/monorepo/apps/app_one/node_modules/react"
},
"resolver": null
}
at createNoMappedModuleFoundError (../../node_modules/jest-
resolve/build/index.js:501:17)

at Object.<anonymous> (node_modules/react-
native/Libraries/Components/View/View.js:51:20)
```

It is now complaining of a the `moduleNameMapper` configuration set by our `@youi/react-native-youi` preset.

There are two ways to fix this issue. One is overriding the preset configuration by making a custom preset and either pasting the preset in `@youi/react-native-youi` or spreading it over our JSON object. The second is also not hoisting the `React` package. We will choose the second option since that has been our way to go with the other modules as well.

Add `React` to the `nohoist` option:
```json
"workspaces": {    
       "nohoist": [
         "**react**",
         "**react-native**"    
        ],
          "packages": [
            "shared/*",      
            "apps/app_one",
            "apps/app_two"    
         ],
},
```

Result of `yarn test`

yarn test

Alternative

As you can see the usage of `Yarn Workspaces` has proven to be quite burdensome to maintain. Overall, as your codebase progress and new updates occur, you might need to update and fix this `Monorepo` structure. To avoid having this kind of issue, and to also enjoy similar benefits. You could instead structure your `Monorepo` without symlinks created by `yarn`.

How could we structure a `Monorepo` without `Yarn Workspaces` ?

Under the same repository, you could have multiple independent projects linked by installing it via a relative path.

```bash
- apps  
  - app_one  
   - app_two
- shared  
   - module_one  
   - module_two
```

`app_one` and `app_two` can consume the `shared` module by installing it as follow:

`yarn add file:../../shared/module_one`

This will add the following to your `package.json`

```bash
”module_one”: “file:../../shared/module_one”,
```

Note: To avoid reinstalling the same dependencies make sure to declare modules as `peerDependencies` inside your shared modules if you also use them.

This approach has the benefit of being compatible with NPM as well.

Possible Problems

If your shared modules have external dependencies and for some reason, you install them by running `yarn` inside `shared/module` , you have to always remember to remove the `node_modules` folder before running your app, else you will have name collisions.

Important Notes

- With `Yarn Workspaces` your shared packages will need to have `react` as a dependency.
- With `Yarn Workspaces` your shared packages should not have `react-native` and `@youi/react-native-youi` as dependencies, this can cause collision conflicts.

React

Looking for the best pre-vetted developers?

We have React, React Native, and Node.js developers available on a contract or full-time basis.

Group

Related Articles