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
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
You can check that it works by running the npm run storybook
command.
Install and configure Semantic Release
npm install --save-dev semantic-release
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"
],
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 .
Install styled components
Styled Components is going to be used to style our components:
npm install styled-components @types/styled-components
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;
`;
Still inside the components
folder add an index to export this and future components:
index.ts
export * from "./Button";
Add index to src
folder
index.ts
export * from "./components";
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>;
Check that the new component is being displayed in Storybook
npm run storybook
You should now see the Default
and Primary
buttons being displayed in Storybook under the Button story.
Create a Github repository
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
Commit and push changes
git add .
git commit -m "feat: Add button component"
git push
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
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
Commit and push changes
git add .
git commit -m 'feat: Add github actions'
git push
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.
Github Release
If everything went well, you should see in the action results that every step was succesfully executed.
And in the code tab you can see now that a new release has been created.
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"
]
}
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",
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
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
Then install the dependency:
npm install storybook-npm
Edit App.js
in order to test it:
import { Button } from 'storybook-npm';
...
<Button>Test</Button>
And start the app:
npm start
You should now see the button in the main page.
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)
Can anyone help me figure out what I'm missing here? The
npm build
command which is used in the GitHub action creates abuild
folder.The
dist
folder, which is where thepackage.json
defines where the exports come from, is setup inside thetsconfig.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.Did you find a solution for this?
I did, I looked through the github repository and noticed the
npm run build
command was replaced bytsc
, instead of the originalreact-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.
Indeed that helped!
One more thing I had to change was the
noEmit
setting in "tsconfig.json" tofalse
, as explained here: stackoverflow.com/questions/590557...my actions didnt run
i have added css and it says you need additional loaders for it
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?
And I also can't run your project from github locally, have tis error: "ERROR in ./.storybook/generated-entry.js"
There is a flow without TypeScript?