Creating a Design System with Monorepo

Koba
Koba
Dec 17, 2019 · 6 min read

Image for post
Image for post
A simple vision of a Design System

In my job, we’re creating a Design System and recently we’re trying to change our poly-repos into a Monorepo. In this article, I’ll show a few concepts about what is a Monorepo and Design System and why use both together, but the main point is how to implement in React using Lerna and Yarn.

Design System

A Design System is a collection of reusable components, guided by clear standards, that can be assembled together to build any number of applications. — Design Better Invision.

As a simple way to see it for developers, it’s a collection of reusable components, that could be built in any language or framework, but delivers a UI library to our other developers colleagues.

Here’s a nice link to go deeper into this topic.

So, what is Monorepo?

Image for post
Image for post
Comparisons with Monolith and Multi-repo

The image above translates what is a Monorepo. It’s a git repository containing smaller project repositories. It facilitates the code reusability, has simplified package management (thanks to Lerna and Yarn Workspaces) and, in our specific case, could ease versioning of our DS’s packages.

I’ll not go deeper into this comparison because it has a lot of use cases to discuss. I will discuss the Design System case.

A Design System is mainly composed of:

  • Design Tokens (foundation specs, like colors, spacing, typos)
  • UI Components
  • Documentation
  • Icons
  • Tools

We’re going through the Monorepo’s path because we want to organize our packages isolated but still with scalability to maintain them tied up together.

Let’s Code!

  • Node/NPM: our package manager.
  • React: our framework to create the UI Library.
  • Storybook: a nice tool to document components.
  • Rollup: a nice, simple and fast bundler.

And our Monorepo will be constructed with:

1) First steps

// Install lerna
npm install -g lerna
// Init git repository and move into it
git init design-system && cd design-system
// Init lerna configurations
lerna init

As you can see, Lerna generates some config files. You can see more about lerna.json config here.

2) Create Design Tokens package

// This command creates a package already linked with lerna
lerna create mono-ds-tokens

It will make some package.json configuration questions like the author, description, etc. Just set the entry point to src/index.js .

Some files were created in our packages/mono-ds-tokens . Let's remove packages/mono-ds-tokens/lib/ folder and create our src folder with an index.js file inside.

So, let’s deliver some tokens, go to packages/mono-ds-tokens/src/index.js

Now, we have a palette and a spacing configuration!

So, let’s publish it!

We’ll use NPM Registry, so you should sign up there and do npm login in your terminal. Make sure that your package name (package.json -> "name" property) does not already exist by checking at https://npmjs.org/{package-name}.

With Lerna, you can publish all your packages in just one command.

So, let’s put in the terminal:

lerna publish

You will be asked about the package’s versioning. Choose what you want.

Congratulations! If all goes right, your package has been published and you can see it at https://npmjs.org/{package-name} . For more information about the publication of public packages, go here.

3) UI Library

Let’s create another package, I’ll call it mono-ds-components :

// Let's create adding our tokens package as dependency
lerna create mono-ds-components --dependencies mono-ds-tokens

Again, set entry point to src/index.js , remove lib/ folder and create src folder with index.js file inside.

To create a React UI component library, we’ll add some packages. For that, you can go directly in the package folder and run npm install, but I'll show lerna add command (Obs: this command is not capable of adding multiple packages, see this issue):

lerna add react --scope=mono-ds-component --dev
lerna add rollup --scope=components --dev
lerna add rollup-plugin-babel@latest --scope=mono-ds-components --dev
lerna add @babel/core --scope=mono-ds-components --dev
lerna add @babel/preset-react --scope=mono-ds-components --dev

Did you see all the repeated commands? This is one of the reasons that Monorepos includes Yarn Workspaces, so let’s include it too. Followed by this guide.

Install Yarn with npm install -g yarn

Add npmClient: "yarn" and useWorkspaces: true at lerna.json

Add workspaces property at package.json

Now, we can add all packages in one command ❤. Look at this:

yarn workspace mono-ds-components add --dev react rollup rollup-plugin-babel@latest @babel/core @babel/preset-react

We aren’t using NPM as a package manager because it hasn’t had the Workspace functionality.

First component

Let’s create our Button component. Note that we are using our tokens package:

packages/mono-ds-components/src/index.js

To compile this file, we’ll need to config Babel at .babelrc:

packages/mono-ds-components/.babelrc

And our rollup configurations in rollup.config.js :

packages/mono-ds-components/rollup.config.js

Basically, it sets Babel plugin, an input file, and compiled output files.

To run rollup, add a script at package-name/package.json :

"scripts": {
...
"compile": "rollup -c"
}

And run:

lerna run compile --stream

NOTE: lerna run will execute compile command in all of your packages, and --stream flag only outputs the command log in the terminal. See more details here.

4) Documentation — Storybook

lerna create mono-ds-docs --dependencies mono-ds-components

Following this guide, we’ll do the Storybook’s setup:

yarn workspace mono-ds-docs add react react-dom
yarn workspace mono-ds-docs add --dev @storybook/react babel-loader @babel/core

Create a .storybook/config.js :

import { configure } from '@storybook/react';configure(require.context('../src', true, /\.stories\.js$/), module);

Add the storybook script to package-name/package.json :

"scripts": {
...
"storybook": "start-storybook"
}

And finally, create our Button story at package-name/src/index.stories.js

Let’s run!

lerna run storybook --stream

stream: output command logs in the terminal.

Image for post
Image for post
Storybook running

5) Hot Reload

To do that, we need to structure our changes. Let’s add a new command in our mono-ds-docs/package.json :

"scripts": {
"storybook": "start-storybook",
"docs:dev": "npm run storybook"
}

docs:dev command only runs the script above storybook , I'd recommend that you create it in order to separate commands responsibilities. In the next step, you will know why.

Now, we need to add to our mono-ds-components/package.json the rollup watch mode and docs:dev command running our watch mode:

"scripts": {
"compile": "rollup -c",
"compile:watch": "rollup -c -w",
"docs:dev": "npm run compile:watch"
},

As I explained before, lerna run is capable to run your command in each package.

So, you can run Storybook in watch mode:

lerna run docs:dev --parallel

--parallel flag outputs each package command in your terminal disregarding concurrency, see more here.

Test it! Run this command and modify some changes in our Button component and you'll see an isolated development environment with hot reload to develop your commands and document at the same time!

Conclusion

Before I started this task I thought that it would be hard to setup all of this, but as you can see with some small configurations, it became rather trivial.

For now, I’d recommend you see the Lerna’s commands to familiarize with the versioning and publishing.

See the final version in this repository.

Also, I’m creating a boilerplate with Typescript!

Loft

Espaço para compartilhar o que acontece de inovação e…

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store