Sharing components using a monorepo built with Lerna and Yarn workspace

In this tutorial, we are going to build 2 CRA projects and share components between them. The solution is to have a mono repository or monorepo built with Lerna and Yarn workspace.

Lerna and Yarn workspace are complementary tools to build monorepo. Lerna allows to execute npm scripts in package.json for all sub-projects. Yarn workspace allows to manage dependencies and reference types from other sub-projects.

The source code for this tutorial is in the github repo. Let’s get started by building it step by step.

A) Configure Lerna and Yarn workspace

Create a folder named monorepo (any name is fine). Within the folder, initialise a project using yarn init -y. The yarn init command creates a package.json.

Create a folder named packages where we can host all our React projects.

Install lerna as a dev dependency. yarn add lerna --dev.

Configure lerna using yarn lerna init. This command creates a lerna.json.

Paste the following text into lerna.json

{
  "packages": [
    "packages/*"
  ],
  "version": "0.0.0",
  "npmClient": "yarn",
  "useWorkspaces": true
}

The above configuration is self-explanatory. The packages property specifies where lerna should look for sub-projects. We also tell lerna to use Yarn as the package manager and use Yarn workspace for dependencies.

Configure Yarn workspace in the package.json.

  "private": true,
  "workspaces": {
    "packages": [
      "packages/*"
    ]
  }

We mark the root project as private for Yarn workspace to function properly. In addition, we specify the location of sub-projects for Yarn workspace.

B) Create two CRA projects

Next, we create two CRA projects as follows:

cd packages
yarn create react-app project1 --template typescript
yarn create react-app project2 --template typescript

Notice the benefit of Yarn workspace. The dependencies are hoisted to the root node_modules folder so that there is a single copy of dependencies at the root.

We use yarn build to build any CRA project. Instead of building each project one-by-one, Lerna allows to build all sub-projects at once.

In the root package.json, add a build script as follows:

  "scripts": {
    "build": "lerna run build"
  }

C) Create a shared project for writing reusable components

Change directory to packages folder. cd packages.

Create a new folder: shared that will have all our shared components.

Change directory to the newly created shared folder.

Create package.json using yarn init -y.

Install dev dependencies.

yarn add --dev typescript react @types/react

Paste the following text into a tsconfig.json file:

{
    "compilerOptions": {
        "outDir": "lib",
        "module": "esnext",
        "target": "esnext",
        "declaration": true,
        "moduleResolution": "node",
        "allowSyntheticDefaultImports": true,
        "jsx": "react"
    },
    "include": ["src"],
    "exclude": ["node_modules", "lib"]
}

The tsconfig.json file is the place where we set the options for typescript build command. There is a compilerOptions property which has various settings. In addition, it specifies include folders and exclude folders.

Create two folders: src and lib. The src folder is where we write all our source code. And lib folder is where typescript compiler will emit all its output JavaScript files.

Add build script to the package.json.

  "scripts": {
    "build": "tsc",
    "dev": "tsc -w"
  }

It’s time to write our generic Greeting component that will be used by the two CRA projects.

In Greeting.tsx file, paste the following code:

import React from 'react';

interface GreetingProps {
    whom?: string
}

const Greeting: React.FC<GreetingProps> = ({ whom = 'World' }) => {
    return (
        <h1>Hello {whom}</h1>
    );
}

export default Greeting;

Add an index.ts file where we export all our generic components. Now, we have one. But let’s pretend as if there are many.

import Greeting from './Greeting';
export { Greeting };

Build the shared project using yarn build.

Notice how all the output files are emitted in the lib folder. Change the main file in package.json to lib/index.js.

  "main": "lib/index.js",

D) Namespace all the packages

For all our packages to look professional, we have to namespace it. My initials is VT. So, I am using the namespace of @vt for my package names.

Open the root package.json and name it @vt/monorepo:

  "name": "@vt/monorepo",

Open the project package.json and name it @vt/shared for the shared project, @vt/project1 for the project1 project and @vt/project2 for the project2 project.

For our tutorial, the only package that should be namespaced is the shared project. But, we are doing the namespacing all over to be consistent.

IMPORTANT: After changing the name of the packages, we have to go to the root folder and run yarn again. This will ensure that we can reference the components in other projects correctly.

E) Use the Greeting component from CRA projects

Go to project1 and open App.tsx.

Import Greeting component as follows:

import { Greeting } from '@vt/shared';

Within the App component, use the Greeting component:

<Greeting />

Using Greeting without any props will display ‘Hello world’ as a heading.

Within the project1 folder in the terminal, start the app using yarn start. This will open the app in localhost:3000. Notice the ‘Hello world’ heading.

Now open the App.tsx file in the project2 folder.

Import the Greeting component like before.

Within the App component, use the Greeting component with the whom prop:

<Greeting whom="People" />

Within the project2 folder in the terminal, start the app using yarn start. Notice the ‘Hello People’ heading displayed in the browser.

Summary

In this tutorial, we have created three projects in a monorepo: Two projects with CRA and one shared project for sharing components between two. With a few settings for Lerna and Yarn workspace, we have seen how we can easily setup the monorepo. Yarn workspace also allows import of a component from the shared project as if the project is already deployed to NPM. The entire source code is available in a github repo.

Related Posts

2 thoughts on “Sharing components using a monorepo built with Lerna and Yarn workspace

Leave a Reply

Your email address will not be published.