DEV Community

Gabriel Pfeffer
Gabriel Pfeffer

Posted on

How to create a React Component Library with Storybook + PostCSS

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: Bundles the component library's modules.

  • Babel: Converts ECMAScript 2015+ code into a backward-compatible version of JavaScript that can be run by older JavaScript engines.

  • Storybook: User interface development environment and playground for UI components.

  • PropTypes: 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: 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: 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!


PostCSS

Run this command on terminal:

   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
Enter fullscreen mode Exit fullscreen mode

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.

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: {},
 },
};
Enter fullscreen mode Exit fullscreen mode

Babel

Run this command on terminal:

npm install --save-dev @babel/core @babel/preset-env @babel/preset-react autoprefixer babel-loader
Enter fullscreen mode Exit fullscreen mode

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

{
   "presets": ["@babel/preset-env", "@babel/preset-react"]
}
Enter fullscreen mode Exit fullscreen mode

Custom Webpack Bundling

Run this command on terminal:

npm install --save-dev clean-webpack-plugin webpack webpack-cli webpack-node-externals path mini-css-extract-plugin css-loader
Enter fullscreen mode Exit fullscreen mode

Add the following script to package.json:

"build": "webpack --mode production && npm version patch"
Enter fullscreen mode Exit fullscreen mode

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:

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'
     }
   ]
 }
}
Enter fullscreen mode Exit fullscreen mode

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:

npm run build 
Enter fullscreen mode Exit fullscreen mode

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:

npm install --save-dev @storybook/addon-knobs @storybook/react
Enter fullscreen mode Exit fullscreen mode

Add the following script to package.json:

"start": "start-storybook -s ./src"
Enter fullscreen mode Exit fullscreen mode

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

main.js
Enter fullscreen mode Exit fullscreen mode

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:

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;
 },
};
Enter fullscreen mode Exit fullscreen mode

src/index.js

Import Button from './components/Button';

Export { Button };
Enter fullscreen mode Exit fullscreen mode

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:

"deploy": "npm publish"
Enter fullscreen mode Exit fullscreen mode

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

import Button from './Button.js'

export default Button;
Enter fullscreen mode Exit fullscreen mode

src/components/Button/Button.js

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
Enter fullscreen mode Exit fullscreen mode

src/components/Button/Button.pcss

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

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

.btn-simple {
 background-color: white;
}
Enter fullscreen mode Exit fullscreen mode

src/components/Button/Button.stories.js

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
 })
Enter fullscreen mode Exit fullscreen mode

Once the component is set up using this structure, run

npm run start
Enter fullscreen mode Exit fullscreen mode

in the terminal to visualize it.

It should look something like this:

Screenshot 2020-12-21 at 16.42.22

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

npm run deploy
Enter fullscreen mode Exit fullscreen mode

Component Library Target Project

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

    npm install gs-component-library
Enter fullscreen mode Exit fullscreen mode

Now, we have to import the shared component library main design file (main.css) into the main app.js file:

    import 'gs-component-library/lib/main.css';
Enter fullscreen mode Exit fullscreen mode

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

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

   <Button type="download">Download</Button>
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (1)

Collapse
 
reymon359 profile image
reymon359

Great article, well done mate! 🔝 🔝