Creating a reusable Component Library with React, Storybook, and Webpack
Tags: tutorial, beginners, react, webpack
Published: 2020-04-11 14:01:33


When working across large codebases using modern JavaScript libraries like React it becomes important to split our UI into small, manageable components, which can easily reused and distributed. Doing this can help us deliver a cohesive user experience at scale.  A nice example of a modern component library created by one of the teams at the BBC is [the iPlayer component library, which can be seen here](https://www.bbc.co.uk/iplayer/storybook/index.html?path=/story/style-guide--colours).

In this guide I'll be outlining the steps to get you set up with your own basic React component library, so you can start reusing components across your React apps. To achieve this we will use React, Sass, Storybook, and Webpack.

---

##Initialising the project
First off, let's create a new directory, cd into it and run npm init to get started. Then we need to install the following dependencies

```
yarn add react react-dom 

yarn add --dev @babel/core @babel/preset-env @babel/preset-react babel-loader node-sass sass-loader @storybook/react @storybook/addon-knobs webpack webpack-cli webpack-node-externals clean-webpack-plugin path
```

##Project Structure
The directory structure of our components library will be very simple. We will have a src directory, a .storybook directory, and a dist directory. It will end up looking a bit like this
```
/.storybook
  - main.js
/src
  - index.js
  /components
    /Button
      - index.js
      - button.scss
      - button.stories.js
/dist
  - index.js
- webpack.config.js
- package.json
```

If we wanted to structure our project with scaleability in mind, we could follow the [Atomic design pattern](https://bradfrost.com/blog/post/atomic-web-design/), and break our components down into Atoms, Molecules and Organisms. But in this tutorial we're just going to keep things simple.

##Creating our React component/s
Now we need to create our first React component. In the root of the project create a directory called src and inside the src directory, create another directory called components. Inside the components directory, create another directory which will be the name of your component; and inside this create an index.js file, which is where your component's code will live. I will be creating a simple component called Button. Here's what my Button component looks like

```
// /src/components/Button/index.js
import React from 'react'

const Button = ({message = 'Hello world'}) => (
   <button>{message}</button>
)

export default Button
```

##Storybook
Storybook makes it easy to visualize your components as you develop them, it's really simple to set up, and we've already installed the dependencies we need (@storybook/react and @storybook/addon-knobs). Inside the root of the project create a directory called .storybook, and inside this create a main.js file. Inside the main.js file add the following config, which tells Storybook where to look for the Storybook files, and to use the knobs addon (which is useful for when working with props).

```
// /.storybook/main.js
module.exports = {
  stories: ['../src/components/**/*.stories.[tj]s'],
  addons: ['@storybook/addon-knobs/register']
}
```

Storybook will now automatically pull in each Storybook file we create inside the components directory, so we should create a Storybook file for every component. This is the Storybook file for the Button component I created earlier. It's telling Storybook to render my Button component and add an editable prop called "message", which you can edit directly within Storybook.

```
// /src/components/Button/button.stories.js
import React from 'react'
import Button from './index.js'
import { withKnobs, text } from '@storybook/addon-knobs'

export default { title: 'Button component', decorators: [withKnobs]}

export const button = () => {
  const message = text('Text', 'Click here now!')
  return <Button message={message}></Button>
}
```

Now we need to add a Storybook script to the package.json. 

```
...
"scripts": {
  "start": "start-storybook"
},
...
```

Once we've added this we should be able to run yarn start/npm run start to start Storybook, which will pull in the component we just created. 

##Styling our components
There are many choices for styling components with React, in this tutorial we'll be using Sass. To setup Storybook to work with Sass we need to edit the Storybook config we created earlier (/.storybook/main.js) and import the path package, and then add a Webpack rule to tell Storybook to load Sass files. Your Storybook config should now look like this

```
// /.storybook/main.js
const path = require('path');

module.exports = {
  stories: ['../src/components/**/*.stories.[tj]s'],
  addons: ['@storybook/addon-knobs/register'],
  webpackFinal: async (config, { configType }) => {
    config.module.rules.push({
      test: /\.scss$/,
      use: ['style-loader', 'css-loader', 'sass-loader'],
      include: path.resolve(__dirname, '../src'),
    });
  
    return config;
  }
}
```

Now we've setup Storybook to import the Sass files we can add styling to the component we created previously. To do this we need to create a .scss file in the component's directory, so in /src/components/Button create a file called button.scss. In /src/components/Button/index.js we need to import the file we just created import './button.scss'. Now when we add styling to button.scss, we should be able to see these styles in Storybook 🎊

##Bundling the project with Webpack
To be able to import the components we've created into other React apps we need to bundle the code using Webpack. Inside the src directory we need to create an index.js file, which will be used to declare and export all of the components, which will look like this

```
// /src/index.js
const { Button } = './components/Button'
export { Button }
```

Inside the package.json, we need to add a build script, for building the components using Webpack. Our  package.json should contain the following 
scripts

```
...
"scripts": {
  "start": "start-storybook",
  "build": "webpack -- mode production"
},
...
```

Finally we need to setup the Webpack config to build the components library. In the root directory create a file called webpack.config.js and add the following

```
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const nodeExternals = require('webpack-node-externals');

module.exports = {
  entry: './src/index.js',
  externals: [nodeExternals()],
  output: {
    filename: 'index.js',
    path: path.resolve(__dirname, 'dist'),
    library: '',
    libraryTarget: 'commonjs'
  },
  plugins: [new CleanWebpackPlugin()],
  module: {
    rules: [
     {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: ['babel-loader']
      },
      { 
        test: /\.scss$/,
        use: ['style-loader', 'css-loader', 'sass-loader'],
        include: path.resolve(__dirname, './src')
      }
    ]
  }
}
```

This config is telling Webpack to clean the dist directory (using CleanWebpackPlugin) before loading the JavaScript/Sass contents of src/index.js and outputting it to the dist directory.

##Publishing our components library
Publishing the components library we just created to NPM and importing it into other React apps is really straightforward. Once the code has been built using the yarn build command we created earlier, ensure you are logged into NPM and run npm publish, to publish the components library to NPM. 

Using the components in your other React apps is then as simple as running yarn add yourPackageName inside your other React apps and then importing it into your React app like this

```import { Button } from 'yourPackageName'```

If you don't want to publish your components library to NPM, you can [import it directly from Github instead](https://stackoverflow.com/questions/17509669/how-to-install-an-npm-package-from-github-directly).


---

I have published my code from this guide [on Github](https://github.com/andypotts/components-library-template). If you found this useful, have any questions, or want more content like this, feel free to [follow me on twitter](https://twitter.com/AndyPotts_)!