Component library setup with React, TypeScript and Rollup
Siddharth Venkatesh
Posted on August 16, 2021
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 and Atlassian's Design system 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
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.
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
Button.css
Button.types.ts
Button/index.ts
Now that we have our Button
component, we can export it from components and from src.
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 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 innode_modules
-
@rollup/plugin-commonjs
- To convertcommonjs
modules into ES6 -
@rollup/plugin-typescript
- To transpile our Typescript code in JS -
rollup-plugin-peer-deps-external
- To prevent bundlingpeerDependencies
-
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.
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 or 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 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 or Styleguidist.
The source code can be found here react-ts-lib
Thanks for reading!
Cheers!
Posted on August 16, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
September 16, 2024
October 16, 2024