How to create a React Component Library with Storybook + PostCSS
Tags: react, postcss, storybook, webpack
Published: 2021-02-12 12:13:54


A component library is a cloud-based folder that consists of styled pieces of software or parts of a website. In this case, we are going to develop React components. Component libraries are great to help designers and developers maintain design guidelines and, when done correctly, increases time efficiency tremendously from reutilising code rather than rewriting it.
For this component library, the following technologies/packages will be used:

- [Webpack](https://webpack.js.org/): Bundles the component library's modules.

- [Babel](https://babeljs.io/): Converts ECMAScript 2015+ code into a backward-compatible version of JavaScript that can be run by older JavaScript engines.

- [Storybook](https://storybook.js.org/): User interface development environment and playground for UI components.

- [PropTypes](https://www.npmjs.com/package/prop-types): Checks the types passed in the props object against a specification we set beforehand. Raises a warning if the props passed to the component do not match the expected data type.

- [Classnames](https://www.npmjs.com/package/classnames): Joins CSS class names based on a set of conditions set beforehand. It helps the logic needed to pass CSS classes to the component.

- [PostCSS](https://postcss.org/): Converts modern CSS into something modern browsers understand and determines the polyfills needed based on the targeted browsers or runtime environments.


Feel free to check the code in the library repository [here](https://github.com/guidesmiths/gs-component-library/)!

---

## PostCSS

Run this command on terminal:

   ```bash
   npm install --save-dev lost postcss-css-variables postcss- 
   import postcss-inherit postcss-loader postcss-mixins 
   postcss-nested postcss-preset-env postcss-reporter postcss- 
   custom-properties postcss-custom-media
   ```

Create a _postcss.config.js_ file in the root folder of the project and add the code below. This is used to specify all the necessary plugins Webpack will need during bundling.

```javascript
module.exports = {
 plugins: {
   "postcss-import": {},
   "postcss-preset-env": {
     stage: 0,
   },
   "postcss-mixins": {},
   "postcss-css-variables": {},
   "postcss-nested": {},
   "postcss-inherit": {},
   "postcss-reporter": {},
   "postcss-custom-properties": {},
   "postcss-custom-media": {},
   lost: {},
 },
};
```

---

## Babel

Run this command on terminal: 

```bash
npm install --save-dev @babel/core @babel/preset-env @babel/preset-react autoprefixer babel-loader
```

Create a _.babelrc_ file in the root directory with the following config:

```javascript
{
   "presets": ["@babel/preset-env", "@babel/preset-react"]
}
```


---

## Custom Webpack Bundling


Run this command on terminal: 

```bash
npm install --save-dev clean-webpack-plugin webpack webpack-cli webpack-node-externals path mini-css-extract-plugin css-loader
```

Add the following script to _package.json_:

```json
"build": "webpack --mode production && npm version patch"
```

In the root directory of the component library, create a _webpack.config.js_ file to specify how to bundle the components. The following should look like this:

```javascript
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const nodeExternals = require('webpack-node-externals')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
 entry: './src/index.js',
 externals: [nodeExternals(), 'react'],
 output: {
   filename: 'index.js',
   path: path.resolve(__dirname, 'lib'),
   library: '',
   libraryTarget: 'commonjs'
 },
 plugins: [
   new MiniCssExtractPlugin({
     filename: '[name].css',
     chunkFilename: '[id].css',
     ignoreOrder: false
   }),
   new CleanWebpackPlugin()
 ],
 module: {
   rules: [
     {
       test: /\.(js|jsx)$/,
       exclude: /node_modules/,
       use: ['babel-loader']
     },
     {
       test: /\.(css|pcss)$/i,
       use: [
         {
           loader: MiniCssExtractPlugin.loader,
           options: {}
         },
         {
           loader: 'css-loader',
           options: {
             importLoaders: 1,
             sourceMap: true,
             modules: {
               localIdentName: '[path]__[name]__[local]--[hash:base64:5]'
             }
           }
         },
         {
           loader: 'postcss-loader',
           options: {
             sourceMap: 'inline',
             config: {
               path: path.resolve(__dirname, './config/postcss.config.js')
             }
           }
         }
       ],
       include: path.resolve(__dirname, './src')
     },
     {
       test: /\.(png|jpe?g|gif)$/i,
       use: [
         {
           loader: 'file-loader'
         }
       ]
     },
     {
       test: /\.(woff|woff2|eot|ttf|otf)$/,
       use: 'file-loader'
     }
   ]
 }
}
```

Above, custom configurations are specified for the component library export bundle. These are similar to the Storybook configs.
Here, we are defining the loaders each file type will require, the plugins necessary for bundling, and the entry/outputs points of the bundle.
Every time a new component is created, and you want it to be included in the next library version, you must run:

```bash
npm run build 
```

---

## Setting up Storybook
Storybook works as a UI interface to develop components without adding them to any specific project. It is great to isolate them from custom project bundling and focus on the sole component.

Run this command on terminal: 

```bash
npm install --save-dev @storybook/addon-knobs @storybook/react
```

Add the following script to _package.json_:

```json
"start": "start-storybook -s ./src"
```

Create a folder with the name _.storybook_ in the root directory of the project and within it, a file with the name:

```javascript
main.js
```


_main.js_ holds the configuration to bundle our components as well as specify the files from which our components will be rendered for Storybook. Use the code below:

```javascript
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
 stories: ['../src/components/**/*.stories.[tj]s'],
 addons: ['@storybook/addon-knobs/register'],
 webpackFinal: async (config, { configType }) => {
   config.plugins.push(
     new MiniCssExtractPlugin({
       filename: '[name].css',
       chunkFilename: '[id].css',
       ignoreOrder: false,
     })
   );

   config.module.rules.push({
     test: /\.(css|pcss)$/i,
     use: [
       {
         loader: MiniCssExtractPlugin.loader,
         options: {},
       },
       {
         loader: 'css-loader',
         options: {
           importLoaders: 1,
           sourceMap: true,
           modules: {
             localIdentName: '[path]__[name]__[local]--[hash:base64:5]',
           },
         },
       },
       {
         loader: 'postcss-loader',
         options: {
           sourceMap: 'inline',
           config: {
             path: path.resolve(__dirname, './config/postcss.config.js'),
           },
         },
       },
     ],
     include: path.resolve(__dirname, '../src'),
   });
   return config;
 },
};
```

---

## src/index.js

```javascript
Import Button from './components/Button';

Export { Button };
```

This is where _webpack_ will read all of the components to be included in the library. _src/index.js_ is the entry point for the custom bundle. Components not included in this file, will not be bundled.

---

## Deploy to NPM

Add the following script to _package.json_:

```json
"deploy": "npm publish"
```

If this is your first time running this command, it will ask you to login. Enter your credentials when prompted and deployment to npm should begin! 

---

## Sample Component

Use the following code blocks as an example of a sample component for this library.

#### src/components/Button/index.js
```javascript
import Button from './Button.js'

export default Button;
```

#### src/components/Button/Button.js

```javascript
import React from 'react'
import PropTypes from 'prop-types'
import cs from 'classnames'

import s from './Button.pcss'

const styleLookup = {
 download: 'btn-download',
 simple: 'btn-simple'
}

const Button = ({ type, text }) => (
 <button className={cs(s.btn, { [s[styleLookup[type]]]: type })}>
   {text}
 </button>
)

Button.propTypes = {
 type: PropTypes.string,
 text: PropTypes.string
}

export default Button
```

#### src/components/Button/Button.pcss

```css
.btn {
 border: 1px solid black;
 border-radius: 0.25em;
}

.btn-download {
 background-color: orange;
 text-transform: uppercase;
}

.btn-simple {
 background-color: white;
}
```
 
#### src/components/Button/Button.stories.js

```javascript
import React from 'react'
import { storiesOf } from '@storybook/react'

import Button from './'

storiesOf('Buttons', module)
 .add('Simple Button', () => {
   const component = <Button type="simple" text="Download" />
   return component
 })
 .add('Download Button', () => {
   const component = <Button type="download" text="Download" />
   return component
 })
```

Once the component is set up using this structure, run 
```bash
npm run start
```
in the terminal to visualize it.

It should look something like this:

![Screenshot 2020-12-21 at 16.42.22](https://dev-to-uploads.s3.amazonaws.com/i/pqe4d0c2ng8nvhlpi59m.png)
 

 
To publish your package to NPM, login via Command Line to successfully run:

```bash
npm run deploy
```

---

## Component Library Target Project

Please access the target project directory in the terminal and run:

```bash
    npm install gs-component-library
```

Now, we have to import the shared component library main design file (_main.css_) into the main _app.js_ file:

```bash
    import 'gs-component-library/lib/main.css';
```

You can now import the desired component into your work area:

```javascript
   import { Button } from 'react-component-library';

   <Button type="download">Download</Button>
```

Following these steps, you should be able to create your react component library in no time!
Please check out my sample react component library repository [here](https://github.com/guidesmiths/gs-component-library/)!