⚠️ IMPORTANT STEPS

If you are checking out this code to test without using this guide, please remember to follow these important steps in order:

npm install in Lerna root directory

npm install in the packages/my-react-app directory

npx babel-loader-lerna-cra in the Lerna root directory

The Case For Monorepos

Imagine a scenario where you are building a suite of three React apps that share the same architecture, design patterns, components and styles. Now imaging making an update to a low-level component like a Button that is used in all three apps, as well as one sub-component.

In this scenario, you would be forced into a process like this:

Update the Button code in the Button's git respository. (Component #B in the diagram above)

Create a Pull Requst in the Component #B repo and get the new code into master.

Publish the Component #B Button code on a public or private NPM service.

Go into React Component #C repo that uses the Button and update the package.json dependancies.

Create a second Pull Request in the Component #C, repo and get that new code into master.

Publish the component to the NPM repo.

Go into React App #1

Update the dependencies.

Republish the package on npm service.

Submit a new PR.

Deploy

Go into React App #2

Update the dependencies.

Republish the package on npm service.

Submit a new PR.

Deploy

Go into React App #3

Update the dependencies.

Republish the package on npm service.

Submit a new PR.

Deploy

That is five pull requests for a change to one button component!

Clearly this is less than ideal.

A Simpler Solution

Now imagine using a single repo for the same update. If we use a Monorepo tool like Lerna, the update process will look more like this:

Update the Button code in the Button's git directory. (Component #B in the diagram above)

Run lerna bootstrap to crosslink the Button Component #B into all the sub dependancies.

Run lerna publish to update the packages in your privite NPM service.

Create a Pull Requst in the Monorepo repo and get the new code into master.

Re-deploy the apps with the updated package.json version numbers.

Now everything is done in one Pull Request.

This is why large organizations like Facebook and Google make good use of Monorepos. This process can be simplified to use a single shared repo for all the depenencies and apps. The Monorepo scales up without losing as much engineering velocity and reduces human error lost from switching contexual focus.

The following guide will show you how to set up a such Monorepo for a React project.

Prerequisites

$ npm i -g lerna

$ npm i -g create-react-app

Create a directory for your Monorepo project.

$ cd ~/repos
$ mkdir monorepo-react
$ cd monorepo-react

Setup Lerna

Note: In order restart these this guide at any time, you remove the following files and directories:

The React App is failing to compile because Create-React-App's Webpack config is unaware of the any external modules. This means Webpack can not tell Babel-Loader about your component directories, and the sources do not get transpiled.

It seems like this will problem may go away with future versions of Create-React-App, although this may require Yarn Workspaces. So make sure you check the GitHub Issue Create-React-App-Lerna-Support to see if this feature os landed before using the following work-around.

Rewire Your React App for Lerna

I created a small Work-around Node Module to override Create-React-App Webpack configs inside Lerna projects, called Babel-Loader-Lerna-CRA. It's pretty simple. It just updates the Webpack paths for Babel-Loader.

You can install this package using NPM:

npm i -D babel-loader-lerna-cra

Now lets update the package.json in our Lerna root with glob patterns that describe the relationship between our components and our app.

The imports refer to components that the React app will neeed to transpile.

The apps inform babel-loader-lerna-cra where the Webpack overrides will need to happen.

Now lets bootstrap the Webpack configs in our React app with babel-loader-lerna-cra:

$ npx babel-loader-lerna-cra

You should see the following output:

Now lets try running your React App again:

$ cd ~/repos/monorepo-react/packages/my-react-app
$ npm run start

You should now see the React App launch in a browser with your CompButton component rendering with the text "Foorbar!"

So what did we get out of this work-around?

Auto Transpilation of Lerna Siblings

Our React App can now import sibling Lerna depedencies and transpile then when needed.

React App Hot Reloading

When we change our React component file, will hot-update the app without having to add any global watchers to the Lerna project to kick of a transpile.

Here is our CompButton component being Hot-Reloaded as it is being updated:

Storybook Hot Reloading

Nothing special here, but it's worth noting that our Storybook still hot-reloads too.

Conclusion

I think this is as far as I would like to take this in a single article. I hope someone else finds this setup useful. If people express interest, I will follow up with a Part 2 on how to setup CI to ship multiple React Apps from this Monorepo setup.