How to Efficiently Manage JavaScript Monorepos With Lerna

Written by thepeterayeni | Published 2022/09/18
Tech Story Tags: javascript | javascript-development | javascript-frameworks | javascript-tutorial | javascript-top-story | software-development | software-engineering | web-development

TLDRManage JavaScript'monorepos' with Lerna, Build a React Icon Library & Publish to NPM. Using TypeScript, Vite, Rollup, better Git commit with Commitizen, auto-generate changelog, tag release, and semantic versioning. This article is not aimed at beginners; intermediate React knowledge and TypeScript basics are required. To get started with React as a beginner, I recommend checking out the new [React documentation](https://www.reactjs.org/learn)via the TL;DR App

Manage JavaScript 'monorepos' with Lerna, Build a React Icon Library & Publish to NPM

Using TypeScript, Vite, Rollup, better Git commit with Commitizen, auto-generate changelog, tag release, and semantic versioning with Lerna.

Photo by Harpal Singh on Unsplash

Introduction

In this article, we will be building a React Icon Library the essence of this tutorial is to break down the process of managing a ‘monorepo’ with Lerna. This article is not aimed at beginners; intermediate React knowledge and TypeScript basics are required. To get started with React as a beginner, I recommend checking out the new React documentation:

Monorepo

In version control systems, a monorepo is a software development strategy where code for many projects is stored in the same repository. — Wikipedia

A ‘monorepo’ allows us to have multiple projects in one repository for our case of an Icon Library; we can have the library target different frameworks, React, Vue.js, and Vanilla HTML, CSS, & JS projects. We can also have playgrounds to test each implementation in the same repository, with a documentation website. All of this will be demonstrated in the coming sections.

Technologies

Lerna

Lerna is a fast modern build system for managing and publishing multiple JavaScript/TypeScript packages from the same repository. — Lerna Official

TypeScript

TypeScript is JavaScript with syntax for types.

TypeScript is a strongly typed programming language that builds on JavaScript, giving you better tooling at any scale. — TypeScript

Rollup

Rollup is a module bundler for JavaScript which compiles small pieces of code into something larger and more complex, such as a library or application. It uses the new standardized format for code modules included in the ES6 revision of JavaScript, instead of previous idiosyncratic solutions such as CommonJS and AMD. ES modules let you freely and seamlessly combine the most useful individual functions from your favorite libraries. This will eventually be possible natively everywhere, but Rollup lets you do it today. — Website

Docusaurus

Build optimized websites quickly, focus on your content — Website

Vite

Vite Next Generation Frontend Tooling Get ready for a development environment that can finally catch up with you. — Website

Storybook

Build component-driven UIs faster Storybook is an open-source tool for building UI components and pages in isolation. It streamlines UI development, testing, and documentation. — Website

Installations

To continue with this tutorial, you need to have the following installed on your machine:

Node.js/NPM:

https://nodejs.org/en/download/

Yarn

Install Yarn version 1.

https://classic.yarnpkg.com/lang/en/docs/install/#mac-stable

Code Editor

For this Tutorial I will be using VSCode it’s a free and Open Source IDE follow this link to download or use any IDE of your choice.

https://code.visualstudio.com/

Setup

Create a directory called ‘icons’ that is what I will be naming our Icon Library you can name it whatever you like.

mkdir xicons && cd xicons

The above makes a directory called ‘xicons’ and cd into that directory.

yarn init -y

When you run the above, a package.json file is generated with the following content:

{

"name": "xicons",

"version": "1.0.0",

"main": "index.js",

"license": "MIT"
}

Let’s initialize Lerna by running npx lerna init:

npx lerna init Need to install the following packages:lerna@5.5.0Ok to proceed? (y) y

Running the above generates the above files we will go into the details soon. lerna.json contains configuration for Lerna. The packages folder is where each of our libraries lives. More on that soon.

// lerna.json

{

"$schema": "node_modules/lerna/schemas/lerna-schema.json",

"useNx": true,

"useWorkspaces": true,

"version": "0.0.0"

}

Update the generated lerna.json to include the following:

// lerna.json

{"$schema": "node_modules/lerna/schemas/lerna-schema.json",

"useNx": true,

"useWorkspaces": true,

"version": "0.0.0",

"npmClient": "yarn",

"stream": true,

"packages": ["packages/*"]

}

Lerna works with Yarn workspace that’s why we have the npmClient set to yarn. We will notice some updates to our package.json

{

//newly added

"name": "@xicons/core",

"version": "1.0.0",

"main": "index.js",

"license": "MIT",

// added by lerna

"workspaces": ["packages/*"],

"devDependencies": {"lerna": "^5.5.0"},

// newly added

"private": true}

I updated the name to match the namespace we will be using on NPM for publishing our packages. @xicons will be our organization's name, and whatever comes after the ‘/’ will be that package name. @xicons/core is our parent package, and this is for management purposes only and will not be published to NPM.

That’s why we set "private": true. Lerna will ignore it during publishing to NPM. When we initialized Lerna, it added the `workspaces` key to our package.json and added an array of directories — currently just the package’s directory.

Now let’s create the rest of the folder structure as designed in our architecture.

After creating the above architecture, our project looks as shown below:

You will notice we have documentation and playgrounds on the same level as packages, which are also added to the workspace array.

For this article, we will be focusing on the React package the same idea applies to other packages. If you feel adventurous, you can create the Vue.js and HTML versions of the icons. Before we move forward, let's set up a better way to commit using ‘committed’.

yarn add -D commitizen -W

Notice the -w flag is important so the package is added to the workspace.

"scripts": {"commit": "cz"},

We also added a command to run the commit to the scripts:

yarn add -D cz-conventional-changelog -W

Lerna will use Conventional Changelog to generate the changelog from our commits for every release.

We will update the lerna.json.

"Command": {"publish": {"conventionalCommits": true}}

And add the above; this will allow us to generate a changelog every time we publish.

Our updated lerna.json:

{

"$schema": "node_modules/lerna/schemas/lerna-schema.json",

"useNx": true,

"useWorkspaces": true,

"version": "0.0.0",

"npmClient": "yarn",

"stream": true,

"packages": ["packages/*"],

"command": {"publish": {"conventionalCommits": true}}}

Finally, update the package.json

//

// xicons/package.json

"config": {"commitizen": {"path": "cz-conventional-changelog"}},

React Icon Library

Now that we have all the foundation of our Monorepo setup let's proceed to set up our React Icon Library. We will be using TypeScript and Rollup as our bundlers. cd packages/reactIcon and then run yarn init -y to initialize a new project.

yarn add -D react rollup typescript rollup-plugin-typescript2 @types/react

Install all the above libraries and update ourpackage.json as shown below:

{"name": "@xicons/reactIcon",

"version": "1.0.0",

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

"license": "MIT",

"scripts": {

"build": "rollup -c",

"dev": "rollup -c -w"

},

"files": ["lib"],

"devDependencies": {

"react": "^18.2.0",

"rollup": "^2.79.0",

"rollup-plugin-typescript2": "^0.33.0",

"typescript": "^4.8.2"}

}

Rollup configuration

import Ts from "rollup-plugin-typescript2";export default {input: ["src/index.ts"],output: {dir: "lib",format: "esm",sourcemap: true,},plugins: [Ts()],preserveModules: true,external: ["react"],};

File structure for the reaction:

All our icon library code goes into the src. The icons folder will house all the icons’ components and the index.ts export everything. For this article, we are using the community version of SexyIcons, which you can find here:

You can select icons and export them as SVG to experiment with. We will be converting the Aid icon to React component.

‘src/helper/withSVG.tsx’

The HOC above adds style to the SVG and abstracts out the <svg></svg> markup to avoid repetition.

‘src/icons/Aid.tsx’

The icon has four sets — Line, Solid, Lineal, and Bulk. We allow users to choose the set they want to render and make Line set the default if no set is selected.

The API to use the Icon will look like this:

<Aid />

<Aid color="purple" secondaryColor="violet" />

<Aid set="bulk" color="purple" secondaryColor="violet" />

In a React App

import React from "react";
import { Cart } from "@xicons/reactIcon";
const App = () => {
  return <Cart />
};
export default App;

React Playground

Now that we have our icon library set up, it’s time for us to set up a playground to test it even before deployment to NPM to be certain the API and libraries are working as expected. For the playground, we will be using Vite with React TypeScript Template.

cd playgrounds/reactyarn create vite . --template react-ts

Run:

yarn yarn devVITE v3.0.9  ready in 1183 ms➜  Local:   http://127.0.0.1:5174/➜  Network: use --host to expose

So use our icon library in our playground; we need to build it first. To do that, we can go to the React Icon directory and run. yarn build.

yarn run v1.22.18$ rollup -csrc/index.ts → lib...created lib in 4.2s✨ Done in 5.74s.

Our build file is then output into the lib folder like in our Rollup configuration.

And our Icon is now rendering on our playground directly from our icon library.

We can even put Lerna to good use by allowing it to run our build or dev scripts across every package in our monorepo.

"build": "yarn lerna run build",
"dev": "yarn lerna run dev",

By adding the above to the core package.json, it will run any build or dev scripts across every package.json in the monorepo.

And publish our library is as simple as running:

"publish": "yarn build && yarn lerna run publish",

Publishing

Before you publish to NPM you need to visit the website and signup for an account if you don’t already have one. And also create organizations that match the namespace in your core packages.json.

Then to authenticate NPM on your machine you will need to run:

npm login

Then you can go ahead to publish your npm package. Lerna handles everything for you — creates a tag and changelog on Github, increases the version number across every package — and it all works like magic.

yarn publish

Code Repository:

NPM

Storybook

Conclusion

There are a lot of OpenSource Node.js and JavaScript libraries in the wild using Monorepo and Lerna to manage it, like Create React APP, NestJS, Chakra UI, etc. I hope this gives you an excellent introduction to the monorepo architecture if you have JavaScript projects in different repos and you think can live together to make team collaboration more manageable, you can give this a try to bring them all under one repository. Suppose you want to try it for your next projects. Even better.

See you next time, and happy coding :)

Also Published here


Written by thepeterayeni | Frontend Engineer. I change the world by helping people get started in Tech and Social Entrepreneurship.
Published by HackerNoon on 2022/09/18