Building component library with Docz and Lerna
Tags: react, lerna, docz, componentlibrary
Published: 2020-01-19 17:21:34

_The article was originally posted on [my personal blog](https://claritydev.net/blog/building-component-library-with-docz-and-lerna/)._

Component libraries are all the rage these days, with many companies rolling out their own solutions or sticking to a bunch of open source alternatives. Leveraging a component library for UI development, particularly in large teams, has a lot of cool benefits. It allows to take full advantage of modular and reusable UI components, which brings increased speed of development and unifies styles across multiple teams and apps. Combine that with a robust design system, and the handover from design to development teams becomes smooth and more efficient. 

Frameworks/libraries like React, Vue, etc are perfectly suited for this purpose since there are designed to be highly modular. In this post React and Styled components are used as main tools of choice for developing components.

There are also some helpful tools, that could be leveraged to speed up the development process and deployment of the library. Embracing the modular approach, it would make sense that each component would be an own npm package, the whole library being a monorepo. That's where [Lerna](https://github.com/lerna/lerna) will be used to manage multiple packages inside the project, as well as to keep track of their versioning and publishing process. 

In order to test and document the components, [Docz](https://github.com/doczjs/docz) is used (as an alternative to [Storybook](https://github.com/storybookjs/storybook)). It allows documenting components with [MDX](https://mdxjs.com/), which is a format that combines JSX and Markdown, basically making it possible to import React components inside Markdown files. Moreover, Docz version 2 runs on [GatsbyJS](https://www.gatsbyjs.com/), which brings increased development and build speeds and enables access to Gatsby's vast network of plugins and tools.

## Lerna setup

We'll start by creating a new project, titled **uikit**, and installing the required dependencies.
``` bash
    $ npm i -g lerna
    $ mkdir uikit && cd $_
    $ yarn add docz react react-dom styled-components
```

With the core dependencies installed, it's time to initialize the Lerna project.
``` bash
    $ lerna init
```

This will create the following project structure: 
```
    ui-kit/
      packages/
      package.json
      lerna.json
```
The UI components will be stored in the `packages` folder.

Now let's examine the generated `lerna.json`, which serves as a configuration file for Lerna. By default there isn't much going on, and after a few customizations the config will look as follows.
``` json
    {
      "npmClient": "yarn",
      "version": "independent",
      "packages": [
        "packages/*"
      ],
      "useWorkspaces": true
    }
```
The most important changes here are selecting `yarn` as npm client, specifying `independent` versioning, so the package versions can be changed independently of each other, and enabling [Yarn workspaces](https://yarnpkg.com/lang/en/docs/workspaces/).  The `packages` option points to the location of our library packages, for which we'll keep the default setting. The more extensive list of configuration options is available on Lerna's [Github page](https://github.com/lerna/lerna#lernajson).

Additionally, we'll need to add workspaces-related options to the root `package.json.`
```json
    {
      "name": "uikit",
      "license": "MIT",
      "workspaces": {
        "packages": [
          "packages/*"
        ]
      },
      "private": true,
      "dependencies": {
        "docz": "^2.2.0",
        "lerna": "^3.20.2",
        "react": "^16.12.0",
        "react-dom": "^16.12.0",
        "styled-components": "^5.0.0"
      },
      "devDependencies": {
        "prettier": "^1.19.1"
      }
    }
```
Here we specify the path to `workspaces`, which is the same as the one in `lerna.json`. Also we have to make the package private, otherwise workspaces won't work.

## Creating the first component

To kick things off with the dev work, let's add the first package - `Typography`, with the necessary base font components. As a result, the project's structure will be updated as follows.
```
    ui-kit/
      packages/
        typography/
          src/
            index.js
          CHANGELOG.md
          package.json
      package.json
      lerna.json
```
Before actually writing the font components, let's make a few modifications to the typography's `package.json`.
``` json
    {
      "name": "@uikit/typography",
      "version": "1.0.0",
      "description": "Base fonts",
      "main": "dist/index.js",
      "module": "src/index.js",
      "files": [
        "dist",
        "CHANGELOG.md"
      ],
      "author": "",
      "license": "MIT"
    }
```
The most interesting here are `main`, `module` and `files` fields. We'll point `main` to the `dist` folder, where the transpiled files will be stored and later used in the installed package. The `module` will point to the `src` folder, so the packages can be imported directly from the source folder during development and the changes will be reflected immediately without needing to bootstrap packages again or run build script. Finally the [files](https://docs.npmjs.com/files/package.json#files) property contains the list of the files, which will be included in the published package.

Now we can setup some basic font styles in `typography`'s `index.js`. Those will be made as styled components.
``` javascript
    // typography/src/index.js

    import styled, { css } from "styled-components";

    const fontFamily = "sans-serif";
    const fontWeights = {
      light: 300,
      regular: 400,
      bold: 600
    };

    const baseStyles = css`
      font-family ${fontFamily};
      margin: 0;
      padding: 0; 
      -webkit-font-smoothing: antialiased;
      font-weight: ${({ fontWeight }) => fontWeights[fontWeight] || fontWeights.regular};
    `;

    export const H1 = styled.h1`
      ${baseStyles};
      font-size: 62px;
      letter-spacing: -3px;
      line-height: 62px;
    `;

    export const H2 = styled.h2`
      ${baseStyles};
      font-size: 46px;
      letter-spacing: -3px;
      line-height: 46px;
    `;

    export const H3 = styled.h3`
      ${baseStyles};
      font-size: 30px;
      letter-spacing: -2px;
      line-height: 30px;
    `;

    export const H4 = styled.h4`
      ${baseStyles};
      font-size: 24px;
      letter-spacing: -1.5px;
      line-height: 24px;
    `;

    export const H5 = styled.h5`
      ${baseStyles};
      font-size: 20px;
      letter-spacing: -1px;
      line-height: 20px;
    `;

    export const H6 = styled.h6`
      ${baseStyles};
      font-size: 18px;
      letter-spacing: 0;
      line-height: 18px;
    `;

    export const Text = styled.p`
      ${baseStyles};
      font-size: 16px;
      letter-spacing: 0;
      line-height: 16px;
    `;

    export const SmallText = styled.small`
      ${baseStyles};
      font-size: 12px;
      letter-spacing: 0;
      line-height: 12px;
    `;
```
Note that `css` helper from `styled-components` is used to define reusable parts of the styles, which are then extended by other components. The components also accept a `fontWeight` property for customization, which defaults to `regular`. 

## Trying out Docz's playground

This seems like a good time to try these components out in action and that's where `Docz` will be used to document their usage. In order to do that, we'll need to add an `.mdx` file somewhere in the project with the component documentation, and one of those files needs to point to `route: /` and will be used as the front page. Let's create this `index.mdx` in the root of the `packages`.
``` markdown
    // index.mdx

    ---
    name: Welcome
    route: /
    ---

    # Welcome to the awesome UI Kit

    Select any of the components from the sidenav to get started. 
```
After running `yarn docz dev`, we can navigate to `localhost:3000` and see the front page of the library.

![](https://i.imgur.com/JvfxuGw.png)

To add documentation to the typography, we'll create a `docs` folder inside the package and add `typography.mdx` there.
```
    ui-kit/
      packages/
        typography/
          docs/
            typography.mdx 
          src/
            index.js
          CHANGELOG.md
          package.json
      package.json
      lerna.json
```
To document components, we'll use a special docz component, called `Playground`. Wrapping it around the components will allow editing them right below where they are displayed.
``` markdown
    ---
    name: Typography
    menu: Components
    ---

    import { Playground } from 'docz';
    import { H1, H2, H3, H4, H5, H6, Text, SmallText } from '../src/index';

    # Base Typography
    <Playground>
        <H1>Heading 1</H1>
        <H2>Heading 2</H2>
        <H3>Heading 3</H3>
        <H4>Heading 4</H4>
        <H4 fontWeight='bold'>Heading 4 bold</H4>
        <H5>Heading 5</H5>
        <H6>Heading 6</H6>
        <Text>Text</Text>
        <SmallText>SmallText</SmallText>
    </Playground>
```
After refreshing the page, or restarting dev sever if necessary, we'd be able to see our typography components. And the best thing is that we can directly edit the code on the page and see the updated results immediately! 

![](https://i.imgur.com/Syv5tLE.png)

## Adding custom fonts

This works well for built-in fonts, but what if we want to load a custom font, say from Google fonts? Unfortunately, since v2 of Docz has been released quite recently and due to it being a major rewrite of v1, there's still no clear, documented way to do that. However, there's one [solution](https://github.com/doczjs/docz/issues/944#issuecomment-514684671), which also nicely demonstrates the extendability of Gatsby configuration and a concept, known as [Component shadowing](https://www.docz.site/docs/component-shadowing).

For Gatsby-specific components we'll need to create a `src` folder in the root of the project, where the theme-specific components, among others, will be stored. Since we're extending `gatsby-theme-docz`, a folder with this name needs to be created inside the `src`. Lastly, we'll create a `wrapper.js` file inside of it to have the following project structure.
```
    ui-kit/
      packages/
        typography/
          docs/
            typography.mdx
          src/
            index.js
          CHANGELOG.md
          package.json
      src/
        gatsby-theme-docz/
          wrapper.js
      package.json
      lerna.json
```
Inside `wrapper.js` we'll add a very simple component, the only task of which is to pass down its children.
``` javascript
    // src/gatsby-theme-docz/wrapper.js

    import React, { Fragment } from "react";

    export default ({ children }) => <Fragment>{children}</Fragment>;
```
It seems quite pointless to make a component which only forwards the children, however the reason for this is that we can now include `css` styles in this component, which will be applied globally. For that, let's create `styles.css` alongside `wrapper.js` and import there one of the selected fonts. In this tutorial, we'll be using Montserrat. 
``` css
    /* src/gatsby-theme-docz/styles.css */

    @import url('https://fonts.googleapis.com/css?family=Montserrat:300,400,600&display=swap');
```
Now we just need to import this file into `wrapper.js` and update the `fontFamily` constant for the typography.
``` javascript
    // src/gatsby-theme-docz/wrapper.js

    import React, { Fragment } from "react";
    import "./style.css";

    export default ({ children }) => <Fragment>{children}</Fragment>;
```
``` javascript
    // ./packages/typography/src/index.js

    import styled, { css } from "styled-components";

    const fontFamily = "'Montserrat', sans-serif";

    // ...
```
The changes should be visible immediately (if not, might need to restart the dev server). This might not be the cleanest approach, but it gets the job done, and since it's no longer possible to load custom fonts via `doczrc.js`, this might be one of the few viable solutions. 

## Customizing the documentation site

Talking about `doczrc.js`, which is used to configure a Docz project. The list of configuration options can be found on the project's [documentation site](https://www.docz.site/docs/project-configuration). Since we're now using Montserrat font for UI kit's typography, it would make sense if our documentation website used the same font. To do that, we'll add a `themeConfig` property to the `doczrc.js`, where the styles for the most commonly used text elements will be applied.
``` javascript
    const fontFamily = "'Montserrat', sans-serif";

    export default {
      title: "UI Kit",
      description: "UI Kit - Collection of UI components",
      themeConfig: {
        styles: {
          h1: {
            fontFamily: fontFamily
          },
          h2: {
            fontFamily: fontFamily
          },
          body: {
            fontFamily: fontFamily
          }
        }
      }
    };
```
Since we need to keep our project configuration separate from the components, we'll have to declare the font family separately here and use it for specific text elements. Additionally, we can customize the project title and description here. The default `themeConfig` can be found on the [Docz's Github page](https://github.com/doczjs/docz/blob/master/core/gatsby-theme-docz/src/theme/index.js). More options to customize the project, like adding a custom logo, are described in the [documentation](https://www.docz.site/docs/gatsby-theme).

## Adding Buttons

Finally it's time to add a React component, `Buttons,` which will also make use of the typography for better illustration of how components can be used together. As before, we'll make a new package, so the project's structure will be as follows.
```
    ui-kit/
      packages/
        typography/
          docs/
            typography.mdx
          src/
            index.js
          CHANGELOG.md
          package.json
        buttons/
          docs/
            buttons.mdx
          src/
            index.js
            Buttons.js
          CHANGELOG.md
          package.json
      src/
        gatsby-theme-docz/
          style.css
          wrapper.js
      package.json
      lerna.json
```
The `package.json` for `buttons` will look almost identical to the one from `typography`, with a few small exceptions. The most notable one is that `buttons` has `typography` package as a dependency.
``` json
    {
      "name": "@uikit/buttons",
      "version": "1.0.0",
      "description": "Button components",
      "main": "dist/index.js",
      "module": "src/index.js",
      "files": [
        "dist",
        "CHANGELOG.md"
      ],
      "dependencies": {
        "@uikit/typography": "^1.0.0"
      },
      "author": "",
      "license": "MIT"
    }
```
Now, after we run `lerna bootstrap`, it will install all the required packages and symlink the dependencies inside the `packages` folder. One nice benefit of this is that if we make any changes to the `typography` package and use that package inside `buttons`, the changes will be immediately reflected in both packages without needing to rebuild or publish any of them. This makes the development experience really fast and efficient! 

After all the dependencies have been installed, we can start writing code for the buttons.
``` javascript
    // packages/buttons/src/Buttons.js

    import React from "react";
    import styled from "styled-components";

    import { SmallText } from "@uikit/typography";

    export const ButtonSmall = ({ text, ...props }) => {
      return (
        <Button {...props}>
          <SmallText>{text}</SmallText>
        </Button>
      );
    };

    export const Button = styled.button`
      border-radius: 4px;
      padding: 8px 16px;
      color: white;
      background-color: dodgerblue;
      border-color: dodgerblue;
    `;

    // packages/src/buttons/index.js

    export * from "./Buttons";
```
Here we define two very basic button components. The `Button` component has a few base styles, which could be further extended. `ButtonSmall` has a predefined text component and therefore accepts button text as a separate prop. Additionally we export everything from `Buttons.js` inside `index.js` as a convenience. This will ensure a single point of export for each package, particularly helpful when there are multiple files per package. Now let's try these new components out in the playground.
```
    // packages/buttons/docs/buttons.mdx

    ---
    name: Buttons
    menu: Components
    ---

    import { Playground } from 'docz';
    import { Button, ButtonSmall } from '../src/index';

    # Buttons

    ## Base button
    <Playground>
        <Button>Test</Button>
    </Playground>

    ## Small button
    <Playground>
        <ButtonSmall text='Click me'/>
    </Playground>
```
Navigating back to `localhost:3000` we can confirm that the buttons work as expected. With that we have a properly documented, functioning component library, which can be easily extended. 

## Deploying the docs and publishing packages

So far the we have been focusing mostly on development side of the component library, however there are a few other important steps that need to happen before the library becomes usable. 

### Publishing packages

To publish all the packages that have been changed since the last publishing took place (and after they have been transpiled with [Babel](https://babeljs.io/docs/en/)), we can use `lerna publish` command. It will prompt to specify versioning for each package before publishing them. The version can be specified directly with the `publish` command, which will apply the same versioning to all the changed packages and will skip the prompts, e.g. `lerna publish minor`. For publishing to work, a `registry` needs to be added in `lerna.json`.
``` json
     "command": {
        "publish": {
          "registry": "https://mypackageregistry/"
        }
      }
```
### Building the docs and serving them

Docz comes with a few built-in scripts that make it easier to view and deploy the documentation. It can be built and served locally by running `yarn docs build && yarn docz serve`. To deploy the documentation online Docz's site has a handy example of [doing it with Netlify](https://www.docz.site/docs/deploying-your-docs). After Netlify site has been setup, deploying is easy via running `netlify deploy --dir .docz/dist.`

If you want to have a look at the boilerplate code for the component library, it's available on my [Github](https://github.com/Clarity-89/ui-kit).