Automating React Storybook: Generate, version upgrade & publish to npm.

Have you ever wondered how libraries like Material UI and Tailwind CSS are built and published?

In this guide, we'll walk you through creating Storybook components using React and Vite, publishing them to npm, and automating version updates with semantic-release.

What We'll Cover

  • Introduction to Storybook

  • Generating a boilerplate Storybook for React, TypeScript, and Vite

  • Setting up the project and adding a simple custom button component to Storybook

  • Compiling TypeScript files

  • Bundling using Rollup

  • Publishing to npm

  • Automating the entire package release workflow to npm via GitHub Actions and semantic-release

What is Storybook?

Storybook is a frontend workshop for building UI components and pages in isolation. It helps you develop and share hard-to-reach states and edge cases without needing to run your whole app. It's open source and free.

Setting up the Project

mkdir storybook-react-components
cd storybook-react-components
npx storybook@latest init

  • Choose the project template as React + Vite (TS). Wait for a while, and Storybook will open at http://localhost:6006 by default. Below is the folder structure.

  • Clean up the boilerplate code and organize the folders as we typically do in React projects. After cleaning up, the folder structure will look like this:

  • Update the tsconfig.json with below settings

  •   {
        "compilerOptions": {
          "useDefineForClassFields": true,
          "lib": [
            "ES2020",
            "DOM",
            "DOM.Iterable",
            "esnext"
          ], //  Includes necessary libraries.
          "allowJs": true, // Allows processing of JavaScript files.
          "esModuleInterop": true, // Enables ES module interoperability.
          "allowSyntheticDefaultImports": true, // Allows default imports.
          "forceConsistentCasingInFileNames": true, // Enforces consistent file name casing.
          "skipLibCheck": true, // Skips type checks for libraries.
          "resolveJsonModule": true, // Resolves JSON modules.
          "moduleResolution": "node", // Uses Node.js module resolution.
          "strict": true, // Enables strict type-checking.
          "noUnusedLocals": true,
          "noUnusedParameters": true,
          "noFallthroughCasesInSwitch": true, // Prevents fall-through cases in switch statements.
          "target": "ES6",
          "module": "ES6",
          "jsx": "react", // Uses React JSX factory.
          "outDir": "./dist",
          "rootDir": "./src",
          "noEmit": true, // Prevents emitting JavaScript files.
          "isolatedModules": true: // Isolates each module.
        },
        "include": [
          "src/**/*"
        ],
        "exclude": [
          "node_modules",
          "dist"
        ]
      }
    
  • Update the index.ts file with the following code, which will include reusable components. Here, I will add the Button and Header components generated by the Storybook boilerplate code.

  •   import Button from "./Button/Button";
      import Header from "./Header/Header";
    
      import type { ButtonProps } from "./Button/Button";
      import type { HeaderProps, User } from "./Header/Header";
    
      export { Button, ButtonProps, Header, HeaderProps, User };
    
  • After updating the code, run the below command to check and ensure if storybook is running without errors.

      npm run storybook
    
  • Now create new repository in your GitHub and push the code to main branch.

Bundling Using Rollup

Now that we have compiled our code, we need to bundle it for different environments.

  1. Install Rollup:

     npm install rollup
    
  2. Install additional plugins: save as dev dependencies

     npm install @rollup/plugin-node-resolve @rollup/plugin-commonjs @rollup/plugin-typescript rollup-plugin-peer-deps-external @rollup/plugin-terser rollup-plugin-dts rollup-plugin-postcss -D
    
  • @rollup/plugin-node-resolve: Resolves dependencies in your code.

  • @rollup/plugin-commonjs: Allows use of CommonJS modules.

  • @rollup/plugin-typescript: Enables TypeScript support.

  • rollup-plugin-peer-deps-external: Manages peer dependencies.

  • @rollup/plugin-terser: Minifies JavaScript code.

  • rollup-plugin-dts: Generates type declarations.

  • rollup-plugin-postcss: Install the postcss plugin

Create rollup.config.js in your root directory:

  • Add following keys to package.json

  • Remove "private": true and "type": "module" from package.json

      {
            ...
            "name": "storybook-react-components",
            "description": "A collection of reusable components for React Storybook",
            "main": "dist/index.js",
            "module": "dist/index.mjs",
            "types": "dist/index.d.ts",
            "files": ["dist/**/*", "README.md", "*.js", "*.d.ts"],
            "scripts": {
              "dev": "storybook dev -p 3001",
              "build": "storybook build",
              "lint": "eslint .",
            },
            "exports": {
              ".": {
                "types": "./dist/index.d.ts",
                "node": "./dist/index.js",
                "require": "./dist/index.js",
                "import": "./dist/index.mjs"
              },
              "./manager": "./dist/manager.mjs",
              "./preview": "./dist/preview.mjs",
              "./package.json": "./package.json"
            },
            "bundler": {
              "exportEntries": [
                "src/index.ts"
              ],
              "managerEntries": [
                "src/manager.ts"
              ],
              "previewEntries": [
                "src/preview.ts"
              ]
            },    
            ...
          }
    
  • Writing the config (rollup.config.js)

      import resolve from "@rollup/plugin-node-resolve";
      import commonjs from "@rollup/plugin-commonjs";
      import typescript from "@rollup/plugin-typescript";
      import dts from "rollup-plugin-dts";
      import terser from "@rollup/plugin-terser";
      import peerDepsExternal from "rollup-plugin-peer-deps-external";
      import postcss from "rollup-plugin-postcss";
      const packageJson = require("./package.json");
    
      export default [
        {
          input: "src/index.ts",
          output: [
            {
              file: packageJson.main,
              format: "cjs", // Output as CommonJS module.
              sourcemap: true,
            },
            {
              file: packageJson.module,
              format: "esm", // Output as ES module.
              sourcemap: true,
            },
          ],
          plugins: [
            peerDepsExternal(),
            resolve(),
            commonjs(),
            typescript({ tsconfig: "./tsconfig.json", jsx: "react" }),
            terser(),
            postcss(),
          ],
          external: ["react", "react-dom"], // Excludes specified dependencies from the bundle.
        },
        {
          input: "src/index.ts",
          output: [{ file: packageJson.types, format: "esm" }],
          plugins: [dts.default()],
          external: [/\.css$/],
        },
      ]
    
  • Build Script: update the package.json file

      {
         ...
         "scripts": {
           "rollup": "rollup -c --bundleConfigAsCjs",
           ...
         }
       }
    

    Explanation of the Build Script

  • "rollup": This is the name of the script. You can run it using npm run rollup.

  • "rollup -c --bundleConfigAsCjs": This command runs Rollup with the specified options:

    • -c: Tells Rollup to use a configuration file (rollup.config.js by default).

    • --bundleConfigAsCjs: Ensures the output is bundled as a CommonJS module.

Publish to NPM

  1.  npm run build
    
  2. This will create a dist folder in your root directory.

  3. Finally, you can publish your package to NPM by following these steps:

    • Create a account in npm if you don't have a account.

    • Create a .npmignore file in root directory.

    •   node_modules
        src
        rollup.config.js
        tsconfig.json
        .gitignore
      
    •    npm login
      
  4. Publish the package:

      npm publish
    
  5. And that's it! You've successfully created and published your React TypeScript storybook component to NPM.

Automate version increments of your npm package

  • The above steps for publishing to npm are manual. Each time you change the version in package.json, you have to run the commands again. This makes it difficult to maintain our package when new components are added.

      {
         ...
         "version": "1.2.0", // This you will be changing manually before 
                             // publishing
         ...
       }
    
  • Now most exciting part of this blog, we will automate the entire package release workflow, including determining the next version number, generating the release notes, and publishing the package. Follow the steps below to achieve this:

    1. Install semantic-release

       npm install --save-dev semantic-release
      
    2. Install some plugins for semantic-release :

       npm install @semantic-release/changelog @semantic-release/git -D
      
    3. Create a .releaserc.json file in the root directory and add the following configuration code:

       {
         "branches": ["main"],
         "plugins": [
           "@semantic-release/commit-analyzer",
           "@semantic-release/release-notes-generator",
           [
             "@semantic-release/changelog",
             {
               "changelogFile": "CHANGELOG.md"
             }
           ],
           "@semantic-release/npm",
           "@semantic-release/github",
           [
             "@semantic-release/git",
             {
               "assets": ["package.json", "CHANGELOG.md"],
               "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
             }
           ]
         ]
       }
      
    4. This configuration tells semantic-release to run only on the main branch and sets up several plugins for us. You see more plugins defined here than we installed earlier because the core semantic-release package already includes some plugins like commit-analyzer, release-notes-generator, npm, and GitHub. We only needed to install the ones that aren't pre-installed.

    5. When we push to main or merge a new PR, a GitHub action will run (we’ll configure this soon). Our commit messages will be analyzed, the correct new version will be determined using semantic versioning, and then a new git tag, GitHub release, and NPM package will be created for that version.

    6. GitHub Action Configuration

      To set up the GitHub Action, create a .github folder at the root of your repository. Inside this folder, create another folder named workflows. Then, in the workflows folder, create a new file called release.yml and paste the following code into it.

       name: Release
       on:
         push:
           branches:
             - main
      
       jobs:
         release:
           name: Release
           runs-on: ubuntu-latest
      
           steps:
             - name: Checkout
               uses: actions/checkout@v2
               with:
                 fetch-depth: 0
      
             - name: Setup Node.js
               uses: actions/setup-node@v2
               with:
                 node-version: "lts/*"
      
             - name: Install dependencies
               run: npm install
      
             - name: Run Rollup
               run: npm run rollup
      
             - name: Release
               env:
                 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
                 NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
               run: npx semantic-release
      
    7. This action is triggered by pushes to the main branch (including PR merges). The workflow then checks out the latest code, sets up Node.js, and installs our dependencies using npm install. After that, it runs Rollup with npm run rollup. Finally, it runs the semantic-release package using npx semantic-release. We provide our GITHUB_TOKEN and NPM_TOKEN as environment variables so it can carry out the necessary actions we described above.

    8. The GITHUB_TOKEN is automatically created for us in GitHub Actions, so we don’t need to configure it. However, we do need to add the NPM_TOKEN to our repository secrets so it can be used in the action when it runs. Let’s configure that now.

    9. NPM Setup and Token
      To set this up, you need an NPM account. Go to their website and sign in if you already have an account, or create a new one if you don’t. Once logged in, click on your avatar/icon in the top right corner and select "Access Tokens" from the dropdown menu.

    10. Click the “Generate New Token” button, then select "Classic Token" and choose the “Automation” option. Name your token and press “Generate Token”. Copy the token you just created and go back to your GitHub repository.

    11. On your repository page, go to the settings page. Under Actions ==> General, find Workflow Permissions, select the Read and Write permissions radio button, and then click save.

    12. On the same page, under Security, click on "Secrets and variables ==> Actions". Then, click "New repository secret", name it NPM_TOKEN, and paste the token you just created into the “Secret” field. Your NPM_TOKEN is now configured and ready to be used.

    13. With our NPM token, semantic-release package, and GitHub Action all set up, we are ready for the big moment: releasing v1.0.0 of our new package to GitHub and NPM. If you’ve been working locally on the main branch, commit your changes using the conventional commits specification and then push them to GitHub. If you’ve been working on a separate branch, commit your changes the same way, push them to GitHub, and then create a PR into main.

    14. Once your code is on main, the GitHub action should automatically trigger. When it completes successfully, v1.0.0 should be released. You can confirm this on the sidebar of the “Code” tab on GitHub. Under “Releases”, you should see 1 as the total and your latest release as v1.0.0. You should also now have a CHANGELOG.md in the root of your repository, which contains an automatically generated changelog from the commit messages, and your package.json version number should have been updated to 1.0.0.

    15. Finally, the moment you’ve been waiting for: head to your package on NPM by going to https://www.npmjs.com/package/YOUR_PACKAGE_NAME_HERE and check out your new NPM package published for the world to see. Now, whenever you commit to your repository using a fix, feat, or breaking change commit, a new patch, minor, or major version (the version bump is controlled by the commit type) will be released, tagged, and published to NPM without you needing to do a thing.

Testing

  • Create a new React app using CRA, Vite, etc.

  • Go to the project folder

  •   npm install <your-package-name-from-npm>
    
  • To verify if your package is working properly, go to node_modules and search for the folder with the same name as your package. Inside, you will find the dist folder, which contains all your code in a compressed format.

  • Now, go to the App.tsx file and import the Button component from your package.

  • Run the application: You should see your Storybook button.

Optional:

Deploy your Storybook to Netlify as a website so you can share your components with the developer community. Go to Netlify and import your project from GitHub. If you want, you can add a custom site name.

Under Build settings, change the Publish directory to storybook-static.

Then click on deploy.

Conclusion

In this guide, we've explored the process of creating and publishing a React Storybook component library using modern tools like Vite, TypeScript, and Rollup. We went through the steps of setting up Storybook, bundling the components, and publishing them to npm. Furthermore, we automated the versioning and release process using GitHub Actions and semantic-release, making it seamless to maintain and update your package. By following these steps, you can now efficiently build, version, and share your components with the wider developer community, saving time and ensuring consistency across your projects.

0
Subscribe to my newsletter

Read articles from Sidhant Kumar Sahoo directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sidhant Kumar Sahoo
Sidhant Kumar Sahoo