Component library setup with React, TypeScript and Rollup
Tags: javascript, react, typescript
Published: 2021-08-16 18:48:42

##Introduction

Component libraries are becoming more and more popular by the day, especially at organisations with multiple products and teams. Organisations are dedicating teams just to maintain the component library. The end goal here might be a *Design System*, with well thought our principles and practices. But, a *good* design system takes months or even years of research and a dedicated team which a lot of organisation cannot afford. Google's [Material design](https://material.io/design) and Atlassian's [Design system](https://atlassian.design/) are some of the excellent ones that come to mind. A good place to start for majority of teams is a component library. A collection of commonly used components which can help attain consistency across applications. We can start out with simple components like `button`, `inputs`, `modal` and add more along the way.

Let's try to build a simple component library from scratch using React, Typescript, and Rollup to bundle it, and learn a thing or two along the way.

###Initialise the project

Let's start by creating a directory and initialising an `npm` project called `react-lib`
```
mkdir react-lib
cd react-lib
npm init
```
You can fill out the questions or pass the `-y` flag to initialise with default values. We now have a `package.json` file in our project.

Since we're going to be using `react` and `typescript`, we can add those dependencies

```
npm i -D react typescript @types/react
```

Since we are going to be shipping this as a library, all our packages will be listed under `devDependencies`. Also, the app in which this library will be used will come with react, we don't have to bundle react along. So, we'll add `react` as a `peerDependency`. Our `package.json` looks like this now

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5e1ps2jsmfveyt73rge5.png)
 

###Adding components

My preferred way of organising components is inside `src/components` folder, where each component would have its own folder. For example, if we have a `Button` component, there would be a folder called `Button` in `src/components` with all the button related files like `Button.tsx`, `Button.css`, `Button.types.ts`, and an `index.ts` file to export the component

There are also a couple of index files along the way to export stuff. One is the main entrypoint to the project, at `src/index.ts`, and one which exports all the components at `src/components/index.ts`. The folder structure with the button component would look like this.
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qc03mm8zd7vwx978blac.png)

###Button component

Now, let's add the code for the `Button` component. I'm going with a very simple component as this is not really our concern right now.

`Button.tsx`
 
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3epx7wn96a3fp0zhp013.png)

`Button.css`

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hw3ytv6fohyo6l8zd2jk.png)

`Button.types.ts`
 
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n7kd958a5ocot9i7t5t2.png)

`Button/index.ts`
 
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k005963bxnmn55gchecg.png)

Now that we have our `Button` component, we can export it from components and from src.

`src/component/index.ts` 
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fumurod16fsf7u3rukq9.png)

`src/index.ts` 
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7vk16q2pkuva14dplnwj.png)
 
###TypeScript configuration

We've added our components and now in order to build our library, we need to configure Typescript. We've already installed the typescript dependency, now we need to add the `tsconfig.json`. We can do this by

```
npx tsc --init
```
This creates a `tsconfig.json` file with most of the available options commented. I use most of the defaults with some minor changes.

```
{
  "compilerOptions": {
    "target": "es5",
    "module": "esnext",
    "jsx": "react",
    "sourceMap": true,
    "outDir": "dist",
    "strict": true,
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
  }
}
```

Let's add a build script in our `package.json` to test this out.
```
"scripts": {
    "build": "tsc"
 },
```

If we run `npm run build`, we should see a `dist` folder with all our ts files transpiled into js files. If you notice, there are no css files in `dist` and they are not bundled by out ts compiler. Let's do that using **Rollup**

###Rollup configuration

We'll be using [Rollup](https://rollupjs.org/guide/en/) as the bundler of choice here. So, lets install it
```
npm i -D rollup
```

####Plugins

Rollup has a plugin system by which we can specify all the tasks that need to be performed during the bundling process. We'll need the following plugins

* `@rollup/plugin-node-resolve` - Resolve third party dependencies in `node_modules`
* `@rollup/plugin-commonjs` - To convert `commonjs` modules into ES6
* `@rollup/plugin-typescript` - To transpile our Typescript code in JS
* `rollup-plugin-peer-deps-external` -  To prevent bundling `peerDependencies`
* `rollup-plugin-postcss` - To handle our css
* `rollup-plugin-terser` - To minify our bundle

Lets install these plugins
```
npm i -D @rollup/plugin-node-resolve @rollup/plugin-commonjs @rollup/plugin-typescript rollup-plugin-peer-deps-external rollup-plugin-postcss rollup-plugin-terser
```
####rollup.config.js
The next step is to add the `rollup.config.js` file. This is where all our rollup configs live.

The entrypoint to our library is the `src/index.ts` file and we'll be bundling our library into both `commonjs` and `es modules` formats. If the app using this library supports esmodules, it will use the `esm` build, otherwise `cjs` build will be used.

`rollup.config.js`
```
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import { terser } from 'rollup-plugin-terser';
import external from 'rollup-plugin-peer-deps-external';
import postcss from 'rollup-plugin-postcss';

const packageJson = require('./package.json');

export default {
    input: 'src/index.ts',
    output: [
        {
            file: packageJson.main,
            format: 'cjs',
            sourcemap: true,
            name: 'react-lib'
        },
        {
            file: packageJson.module,
            format: 'esm',
            sourcemap: true
        }
    ],
    plugins: [
        external(),
        resolve(),
        commonjs(),
        typescript({ tsconfig: './tsconfig.json' }),
        postcss(),
        terser()
    ]
}
    
```
We have defined the `input` and `output` values for our `cjs` and `esm` builds. 

####Putting it all together
Notice that we have specified the `file` option in `output` from `package.json`. Let's go ahead and define these two values in `package.json`

```
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",

```

Now that we've configured Rollup, we can use it in our build script in `package.json` instead of the `tsc` command before.

```
"build": "rollup -c"
```
If we run `npm run build` now, we can see that there is a `dist` folder created with our library output.
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mm9b1m184q6ibpdwio2j.png)

The `cjs` folder contains the `commonjs` bundle and `esm` folder contains modern `esmodules` bundle. 

We have our own library which can now be published to the npm registry or used with other applications locally as well.

####Testing it out

We can test out our library locally by using [npm pack](https://docs.npmjs.com/cli/v7/commands/npm-pack) or [npm link](https://docs.npmjs.com/cli/v7/commands/npm-link).

### Bundling types

If you notice in our `dist` folder after running `npm run build`, we can see that we are not bundling our types. The advantage of using TS here is that code editors can pick up the types and provide Intellisense and static type-checking, which is super useful. It also reduces the need to look at documentation often.

We need a add a few options in our `tsconfig.json` to generate types.
```
"declaration": true,
"declarationDir": "types",
"emitDeclarationOnly": true
```
Adding this would add a types folder in our `cjs` and `esm` folders in `dist`.

We can further improve this by providing a single file which would contain all the types used in our library. For this, we are going to be using a Rollup plugin called [rollup-plugin-dts](https://github.com/Swatinem/rollup-plugin-dts) which takes all our `.d.ts` files and spits out a single types file.

```
npm i -D rollup-plugin-dts
```
We can add another entrypoint in our `rollup.config.js` to add our types config.

```
{
        input: 'dist/esm/types/index.d.ts',
        output: [{ file: 'dist/index.d.ts', format: "esm" }],
        external: [/\.css$/],
        plugins: [dts()],
},
```

What this does is take the `index.d.ts` file from our esm bundle, parse through all the types file and generate one types file `index.d.ts` inside our `dist` folder.

Now we can add a `types` entry in our `package.json` 

```
"types": "dist/index.d.ts"
```

The entire `rollup.config.js` looks like this now

```
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import typescript from '@rollup/plugin-typescript';
import { terser } from 'rollup-plugin-terser';
import external from 'rollup-plugin-peer-deps-external';
import postcss from 'rollup-plugin-postcss';
import dts from 'rollup-plugin-dts';

const packageJson = require('./package.json');

export default [
    {
        input: 'src/index.ts',
        output: [
            {
                file: packageJson.main,
                format: 'cjs',
                sourcemap: true,
                name: 'react-ts-lib'
            },
            {
                file: packageJson.module,
                format: 'esm',
                sourcemap: true
            }
        ],
        plugins: [
            external(),
            resolve(),
            commonjs(),
            typescript({ tsconfig: './tsconfig.json' }),
            postcss(),
            terser()
        ],
    },
    {
        input: 'dist/esm/types/index.d.ts',
        output: [{ file: 'dist/index.d.ts', format: "esm" }],
        external: [/\.css$/],
        plugins: [dts()],
    },
]

```

Now, if we use our library in other projects, code editors can pick up the types and provide Intellisense and type checking.

##Conclusion

This is by no means a comprehensive or perfect way to setup a component library. This is just a basic setup to get started and learn about bundling in the process. The next step in this process would be to add tests and tooling like [Storybook](https://storybook.js.org/) or [Styleguidist](https://react-styleguidist.js.org/).

The source code can be found here [react-ts-lib](https://github.com/sidv93/react-ts-lib)

Thanks for reading!
Cheers!