DEV Community

Cover image for Publish Storybook components to NPM using Semantic Release and Github Actions
amalv
amalv

Posted on

Publish Storybook components to NPM using Semantic Release and Github Actions

Overview

In this guide you'll learn how to publish your Storybook components to NPM. In this way the components can be easily distributed and then consumed by the client apps.

Semantic Release will be used in combination with Github Actions in order to automate the release versioning.

Basic React Setup

First Create React App must be installed. The following command will generate a Create React App with Typescript support and NPM as the package manager:

npx create-react-app storybook-npm --template typescript --use-npm
Enter fullscreen mode Exit fullscreen mode

Note that instead of storybook-npm you'll have to choose your own unique name to publish to NPM or use the scoped package approach.

Initialize Storybook

Add Storybook to the project:

cd storybook-npm
npx -p @storybook/cli sb init --story-format=csf-ts
Enter fullscreen mode Exit fullscreen mode

You can check that it works by running the npm run storybook command.

Install and configure Semantic Release

npm install --save-dev semantic-release
Enter fullscreen mode Exit fullscreen mode

Semantic Release has a perfectly fine out of the box default config, the only thing we need to do here is to add the plugins we want to use in the package.json:

  "plugins": [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    "@semantic-release/changelog",
    "@semantic-release/github",
    "@semantic-release/npm",
    "@semantic-release/git"
  ],
Enter fullscreen mode Exit fullscreen mode

Clean up files

Since this project is not going to be used as a client, let's clean up a little bit and remove all the unnecessary files:

cd src
rm -rf stories/*
git rm -rf .
Enter fullscreen mode Exit fullscreen mode

Install styled components

Styled Components is going to be used to style our components:

npm install styled-components @types/styled-components
Enter fullscreen mode Exit fullscreen mode

Add button component

As an exportable component example we are going to create a button.

In the src folder create a new components folder.

Inside the components folder add the Button component:

Button.tsx:

import styled from "styled-components";

export interface ButtonProps {
  primary?: boolean
}

export const Button = styled.button<ButtonProps>`
  /* Adapt the colors based on primary prop */
  background: ${props => props.primary ? "palevioletred" : "white"};
  color: ${props => props.primary ? "white" : "palevioletred"};

  font-size: 1em;
  margin: 1em;
  padding: 0.25em 1em;
  border: 2px solid palevioletred;
  border-radius: 3px;
`;

Enter fullscreen mode Exit fullscreen mode

Still inside the components folder add an index to export this and future components:

index.ts

export * from "./Button";
Enter fullscreen mode Exit fullscreen mode

Add index to src folder

index.ts

export * from "./components";
Enter fullscreen mode Exit fullscreen mode

This will export our components in order to allow clients to consume them.

Add the Button stories inside the stories folder

import React from 'react';
import { action } from '@storybook/addon-actions';
import { Button } from "../components/Button";

export default {
  title: 'Button',
  component: Button,
};

export const Default = () => <Button onClick={action('clicked')}>Default Button</Button>;
export const Primary = () => <Button primary onClick={action('clicked')}>Primary Button</Button>;
Enter fullscreen mode Exit fullscreen mode

Check that the new component is being displayed in Storybook

npm run storybook
Enter fullscreen mode Exit fullscreen mode

You should now see the Default and Primary buttons being displayed in Storybook under the Button story.

Alt Text

Create a Github repository

https://github.com/new

In this example I called it the same name as the package: storybook-npm

Link local repository to Github repository

git remote add origin git@github.com:<username>/<repository-name>.git
git push -u origin master
Enter fullscreen mode Exit fullscreen mode

Commit and push changes

git add .
git commit -m "feat: Add button component"
git push
Enter fullscreen mode Exit fullscreen mode

Github and NPM tokens

We need to get Github and NPM tokens. This is needed in order for Semantic Release to be able to publish a new release for the Github repository and for the NPM registry.

You can read here how to create a token for Github. You need to give the token repo scope permissions.

And here you can read how to create a token in NPM. You need to give the token Read and Publish access level.

Once you have the two tokens, you have to set them in your repository secrets config:

https://github.com/<username>/<repositoryname>/settings/secrets
Enter fullscreen mode Exit fullscreen mode

Use GH_TOKEN and NPM_TOKEN as the secret names.

Setup Github Actions

Inside the root of the project, create a .github folder, and inside the .github folder, add a main.yml file with the following content:

name: Semantic release 

on: push
jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Setup Node.js
        uses: actions/setup-node@v1
        with:
          node-version: 12
      - name: Install dependencies
        run: npm install 
      - name: Build app
        run: npm run build 
      - name: Semantic release
        env:
          GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
        run: npx semantic-release

Enter fullscreen mode Exit fullscreen mode

Commit and push changes

git add .
git commit -m 'feat: Add github actions'
git push
Enter fullscreen mode Exit fullscreen mode

Because of the config previously added, the push will trigger Github Actions which runs Semantic Release. You can see the results in your repository action tab.

Alt Text

Github Release

If everything went well, you should see in the action results that every step was succesfully executed.

Alt Text

And in the code tab you can see now that a new release has been created.

Alt Text

However, the NPM package has not been published, in order to fix this, a couple of changes need to be made.

NPM Release

Update the tsconfig.json file:

{
  "compilerOptions": {
    "outDir": "dist",
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "commonjs",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": false,
    "jsx": "react"
  },
  "include": [
    "src"
  ]
}

Enter fullscreen mode Exit fullscreen mode

You'll also need to remove the private property from package.json in order to be able to publish to NPM and add the files and main entries:

  "files": [
    "dist"
  ],
  "main": "dist/index.js",
Enter fullscreen mode Exit fullscreen mode

file will indicate to NPM that dist is the folder to be included when the package is installed as a dependency.

main represents the dependency entry point.

Commit and push changes:

git add .
git commit -m "Enable NPM registry support"
git push
Enter fullscreen mode Exit fullscreen mode

This should trigger again Github Actions and this time the package will be published to the NPM registry.

Use the dependency with a client app

To try the NPM package, we'll create a new Create React App:

npx create-react-app storybook-consumer --use-npm
Enter fullscreen mode Exit fullscreen mode

Then install the dependency:

npm install storybook-npm
Enter fullscreen mode Exit fullscreen mode

Edit App.js in order to test it:

import { Button } from 'storybook-npm';
...
<Button>Test</Button>
Enter fullscreen mode Exit fullscreen mode

And start the app:

npm start
Enter fullscreen mode Exit fullscreen mode

You should now see the button in the main page.

Alt Text

Conclusion

Having a good strategy for releasing your Storybook components can make things easier to maintain. Semantic Release in combination with Github Actions automates the release process so you only have to worry about choosing the appropiate commit messages.

Tools such as commitizen or commitlint can be used to enforce valid commit messages.

You can find the complete code for this guide in the github repository

Top comments (9)

Collapse
 
martinmikusat profile image
Martin Mikušát

Can anyone help me figure out what I'm missing here? The npm build command which is used in the GitHub action creates a build folder.

The dist folder, which is where the package.json defines where the exports come from, is setup inside the tsconfig.json file but I don't see when is that in any way triggered.

Right now, the files I need when I'm importing from the library don't exist. All there is is a build folder with the React app itself.

Collapse
 
lmorado profile image
lmorado

Did you find a solution for this?

Collapse
 
martinmikusat profile image
Martin Mikušát

I did, I looked through the github repository and noticed the npm run build command was replaced by tsc, instead of the original react-scripts build. That's not mentioned in the article.

I don't quite remember if that's all that fixed it though, I think it should. Looking through the repo certainly helped.

Thread Thread
 
sfmskywalker profile image
Sipke Schoorstra

Indeed that helped!
One more thing I had to change was the noEmit setting in "tsconfig.json" to false, as explained here: stackoverflow.com/questions/590557...

Collapse
 
usmaniqbal998 profile image
Muhammad Usman

my actions didnt run

Collapse
 
usmaniqbal998 profile image
Muhammad Usman

i have added css and it says you need additional loaders for it

Collapse
 
maxizhukov profile image
Maksym Zhukov

When I created a Button.stories.ts I has a lot of errors like: "Individual declarations in merged declaration 'Button' must be all exported or all local.
"
Do you know, how I can solve it?

Collapse
 
maxizhukov profile image
Maksym Zhukov

And I also can't run your project from github locally, have tis error: "ERROR in ./.storybook/generated-entry.js"

Collapse
 
perymimon profile image
pery mimon

There is a flow without TypeScript?