Using Create-React-App In A Monorepo

How to do it without pulling your hair out

Malcolm Laing
Feb 22 · 5 min read
Image for post
Image for post
Photo by Markus Spiske on Unsplash

Monorepos are all the rage right now. They provide us with one place to store all of our configurations. They make dependency management easier. And you can make changes that span across multiple packages and applications with one commit.

Create-React-App is a tool made by Facebook that allows you to create modern react applications without having to worry about build configurations. It’s the most popular way to create a new react application. But does it play nicely with monorepos? Is it possible to use create-react-app inside of a monorepo?

Let’s find out!

Building the monorepo

We want to create a new monorepo with the following structure.

packages
components
app

Start by creating a new directory, and running npm init.

mkdir monorepo-example
cd monorepo-example
npm init

Next, we create the directory where our packages will live.

mkdir packages

Creating our first package

Now, let’s scaffold our frontend application with create-react-app.

npx create-react-app packages/fe

Finally, we will use yarn workspaces to turn our directory into a monorepo. Make the following modification to the root package.json.

{
"name": "monorepo",
"private": "true",
"version": "1.0.0",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"workspaces": [
"packages/*"
],
"license": "MIT"
}

Make sure that private is set to true, and that we add the field workspaces. Now we can run yarn from the root directory.

Notice how our node_modules have moved from inside of packages/fe into the root?

Let’s run our CRA app to make sure everything is working. Run the following command from the root directory. Our app should compile and open in the browser.

yarn --cwd packages/fe start

Adding another package to the monorepo

Now that we have created our first package, it’s time to add another. Let’s create our components package. Like before, we need to make create a directory and run npm init.

mkdir packages/components 
cd packages/components
npm init

Now let’s add the dependencies that our components package will require. We will need react and react-dom.

yarn add react react-dom

So, let’s create our first component! Create a file called Button.js and Button.module.css.

import React from "react";
import styles from "./Button.module.css";
export const Button = props => (
<button className={styles.button} onClick={props.onClick} {...props}>
{props.children}
</button>
);

Create a file called index.js and add the following export

export { Button } from "./Button";

Finally, we update our package.json as follows

{
"name": "@monorepo/components",
"version": "1.0.0",
"description": "",
"main": "index.js",
"module": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MIT",
"dependencies": {
"react": "^16.12.0",
"react-dom": "^16.12.0"
}
}

As you can see, we gave it the name @monorepo/components, and we added index.js to the main and module field.

Next, modify App.js in our create-react-app application to use our new component.

import React from "react";
import { Button } from "@monorepo/components";
import "./App.css";
function App() {
return (
<div className="App">
<Button>Hello world</Button>
</div>
);
}
export default App;

But when we run the application, we see an error!

Image for post
Image for post
Error when we try to consume Button in our create-react-app package

Why are we getting this error? CRA is telling us there is an Unexpected token in the code we are importing from our components package. This is due to the fact our components package is exporting JSX. We could solve this in two different ways.

  1. Transpile our components package with babel and consume that transpiled code within our frontend application.
  2. Convince our CRA application to run babel on our components package.

I prefer the second solution because we can work seamlessly with both packages without having to constantly retranspile our components package.

So how can we set up our CRA application to properly transpile our components package? Again, there’s two possible solutions.

1. Eject

We could eject our application. When we eject our application, all of the code and configuration from the react-scripts repo gets copied into our project. This means we will need to take care of our own build, lint, and test configurations. It also means that we can’t easily take advantage of updates to the react-scripts package.

For new frontend developers, ejecting a create-react-app application can be daunting. Dealing with multiple webpack complex configurations is a thing of nightmares for junior developers.

2. Extend the create-react-app webpack configuration

We know that create-react-app does not support custom webpack configurations. So in order to get this to work, we will need to turn to the community.

Up until version 2 of create-react-app, the package react-app-rewired was a good choice. However, since version 2, it’s only lightly maintained and doesn’t work properly. Luckily we can use the package craco to customize our webpack configuration.

Let’s start by installing the package in our fe package.

yarn add @craco/craco

Next, we modify scripts in package.json to use craco instead of react-scripts.

"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "craco eject"
},

Finally, we create our craco configuration file called craco.config.js. In this file, we will specify the customizations we want to apply to create-react-app.

The change we need to make is to add our components folder to the include configuration of the babel-loader.

const path = require("path");
const { getLoader, loaderByName } = require("@craco/craco");
const absolutePath = path.join(__dirname, "../components");module.exports = {
webpack: {
alias: {},
plugins: [],
configure: (webpackConfig, { env, paths }) => {
const { isFound, match } = getLoader(
webpackConfig,
loaderByName("babel-loader")
);
if (isFound) {
const include = Array.isArray(match.loader.include)
? match.loader.include
: [match.loader.include];
match.loader.include = include.concat[absolutePath];
}
return webpackConfig;
}
}
};

The code is pretty straightforward. We take the existing include and we add our components folder to it.

Now we can run our application and see that our customization worked. We were able to modify our application's webpack configuration without ejecting. For full documentation on how craco can be used to customize and extend your create-react-applications, check out it out on github.

Frontend Digest

Anything and everything frontend. JavaScript, CSS and HTML.

Sign up for Highlights

By Frontend Digest

The latest and greatest in frontend development Take a look

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Malcolm Laing

Written by

JavaScript Consultant. Senior React developer. Still makes silly mistakes daily.

Frontend Digest

Anything and everything frontend. JavaScript, CSS and HTML.

Malcolm Laing

Written by

JavaScript Consultant. Senior React developer. Still makes silly mistakes daily.

Frontend Digest

Anything and everything frontend. JavaScript, CSS and HTML.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app