Last updated on November 07, 2024

Create a React NPM Package with Vite and Storybook

Over the years, we have seen many tools, frameworks, and libraries built to improve the developer experience. Among these tools, we have Vite, which gained great popularity for its performance, customization, and seamless integration with libraries/frameworks like React, Vue, and Svelte.

In this tutorial, we will learn how to build and publish a simple React npm package using Vite and Storybook as our build and local development environment tools.

Project Setup

Let's start by creating a project folder called vite-react-npm-package. Inside, we will initialize our npm package with the command npm init -y, which will create a new file in our project folder called package.json and will contain the following code:

package.json
{
  "name": "vite-react-npm-package",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": ""
}

Let's go through a brief explanation of each field added in our new package.json file:

  • name is the name that identifies the package. It should be unique among all the registered packages in npm.
  • version is the version of the package. It follows the format 1.0.0 (also known as semantic versioning).
  • main is the file definition that will execute the package. The default value is index.js, but we will update it to a new file path later in this tutorial.
  • scripts is a list of commands that can be executed in our project. They allow us to perform tasks such as starting a server, generating a production build, running the development environment, and much more.
  • keywords are a list of words that describe the package.
  • author is the creator of the package. It can be specified as a single author or an array of multiple authors.
  • license defines how the package can be used, modified, and shared. By default, it comes with the value ISC, but you can check other types of licenses here.
  • description is a small description of the package.

To complete the initial setup of our package.json file, we will add a new field called type:

package.json
{
  "name": "vite-react-npm-package",
  "version": "1.0.0",
  "type": "module",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": ""
}

type defines how our project should import modules. By setting the type field to the value module, we will enable ECMAScript modules in our package.

Dependencies

Now that we have completed the initial setup of our package.json file, we will explore two new fields: dependencies and devDependencies.

dependencies are the packages our project needs to work correctly. Each dependency follows the structure "package-name": package-version. To add a new dependency to our project, we need to run the command npm add package-name. This command will download all the new package files to our project (stored in a folder called node_modules) and automatically add the package to the dependencies field of our package.json file. Here is an example of the dependencies field:

package.json
{
  "name": "vite-react-npm-package",
  "version": "1.0.0",
  "type": "module",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "dependencies": {
    "date-fns": "^4.1.0"
  }
}

devDependencies are almost identical to the dependencies field. The main difference is that dependencies are packages that our project needs to work (which will be bundled inside the production build of our package), while devDependencies are packages only required for development.

A good example would be the use of a linting package like eslint. This package is excellent for development since it analyzes our code and searches for problems, but our project does not need this package to work correctly; because of this, we can add it to our devDependencies field.

Here is an example of how the package.json file would look with the devDependencies field:

package.json
{
  "name": "vite-react-npm-package",
  "version": "1.0.0",
  "type": "module",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "devDependencies": {
    "eslint": "^9.13.0"
  }
}

Build the React Component

Now that we have completed the initial setup of our project, we will define our React component. For this, we will create a new folder inside our project called src, and inside, we will create a file called index.jsx where we will make a simple React component that increments/decrements a counter. Let's add to our index.jsx file the following code:

src/index.jsx
import React, { useState } from 'react';
 
export function Component() {
  const [count, setCount] = useState(0);
 
  return (
    <div>
      <p>Currently, the count is {count}</p>
      <button onClick={() => setCount(count - 1)}>Subtract</button>
      <button onClick={() => setCount(count + 1)}>Add</button>
    </div>
  );
}

To use the package in other projects, we need to run the command npm add vite-react-npm-package (remember to use your package's unique name). Once the package is installed, we should import and use it in the following way:

import { Component } from 'vite-react-npm-package';
 
export default function App () {
  return (
    <Component />
  );
}

Storybook Development Environment

Next, we will create our project's development environment. For this, we will use Storybook, a tool that creates a workshop for building our components.

Let's install Storybook in our project with the command npm add -D @storybook/react-vite. Then, we will add the script "storybook": "storybook dev" to our package.json file, whose main purpose is to automatically set up and run the development environment.

Our package.json file should have the following code:

package.json
{
  "name": "vite-react-npm-package",
  "version": "1.0.0",
  "type": "module",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "storybook": "storybook dev"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "devDependencies": {
    "@storybook/react-vite": "^8.4.2"
  }
}

Let's create our Storybook configuration file. We will create a new folder inside our project called .storybook, and inside, we will create a file called main.js, which defines the location of our project's stories and the framework used in our project. Let's add to our main.js file the following code:

.storybook/package.json
export default {
  stories: ['./*.stories.jsx'],
  framework: { name: '@storybook/react-vite' },
};

To complete our Storybook setup, we will create a story for our React component. A story is made by a default export (which describes the component) and named exports (which capture a specific state of the component).

For this, we will create a new file inside our .storybook folder called component.stories.jsx, and inside, we will add the following code:

.storybook/component.stories.jsx
import { Component } from '../src/index.jsx';
 
export default {
  title: 'Component',
  component: Component,
};
 
export const Example = {};

To start our development environment, we need to run the command npm run storybook. By default, a browser will open with the Storybook interface, where we can see our component and their states.

Vite Setup

Let's set up Vite as our project's build system. For this, we will install Vite as devDependencies with the command npm add -D vite. To configure our build process, we need to create a configuration file inside our project folder called vite.config.js, and inside, we will add the following code:

vite.config.js
import { resolve } from 'path';
import { defineConfig } from 'vite';
 
export default defineConfig({
  build: {
    lib: {
      entry: resolve(__dirname, 'src/index.jsx'),
      name: 'index',
      fileName: 'index',
    },
    rollupOptions: {
      external: ['react'],
      output: {
        globals: {
          react: 'React',
        },
      },
    },
  },
})

Here is a brief explanation of each element added in our new vite.config.js file:

  • build is where we can customize our project's build configuration.
  • build.lib is Vite's library mode, which is made to bundle our package for distribution. Here, we define our component's file location and build/file name.
  • build.rollupOptions is where we add custom configurations to our build. These will be merged with Vite's internal Rollup options (Rollup is the bundler used behind the scenes in Vite).
  • build.rollupOptions.external defines our project's dependencies that should not be added in our production build (in our case, react).
  • build.rollupOptions.output provides global variables for our external dependencies.

Now, we will update our package.json file with four new changes:

  • We will add the script "build": "vite build", which will generate the production build of our package. Once we start the build with the command npm run build, it will generate a new folder in our project called dist. Inside, it will generate two files: index.js and index.umd.cjs (representing an ECMAScript and CommonJS version of our project).
  • We will update the main field to the value ./dist/index.umd.cjs. Its purpose is to define the file that will execute our package (using the default CommonJS version).
  • We will add a new field called module with the value ./dist/index.js. This field is similar to main but represents the ECMAScript version that will execute our package.
  • We will create a new exports field that defines both the CommonJS and ECMAScript versions of our package (similar to the main and module fields). Even though both sections represent the same functionality, Vite recommends defining them both.

Our package.json file should now have the following code:

package.json
{
  "name": "vite-react-npm-package",
  "version": "1.0.0",
  "type": "module",
  "main": "./dist/index.umd.cjs",
  "module": "./dist/index.js",
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "require": "./dist/index.umd.cjs"
    }
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "storybook": "storybook dev",
    "build": "vite build",
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "devDependencies": {
    "@storybook/react-vite": "^8.4.2",
    "vite": "^5.4.10"
  }
}

Publish

Now that we have completed the development of our package, we will learn how to publish it into the npm registry. For this, we need to access our npm account (if you don't have one yet, you can sign up here) with the command npm login.

Once we are logged in, we need to update the version number of our package in the package.json file. Then, we can generate the build of our project and publish it with the command npm publish --access public (with an npm subscription, we can also publish private packages).

To simplify the publication process, we can add the script "publish": "npm run build && npm publish --access public" to our package.json file. Then, to publish a new version of our package, we only need to update its version number and run the command npm run publish.

Our package.json file should now have the following code:

package.json
{
  "name": "vite-react-npm-package",
  "version": "1.0.0",
  "type": "module",
  "main": "./dist/index.umd.cjs",
  "module": "./dist/index.js",
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "require": "./dist/index.umd.cjs"
    }
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "storybook": "storybook dev",
    "build": "vite build",
    "publish": "npm run build && npm publish --access public"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "devDependencies": {
    "@storybook/react-vite": "^8.4.2",
    "vite": "^5.4.10"
  }
}

Conclusion

We have now completed the setup of our React npm package with Vite and Storybook. If you would only like to see the completed code of this tutorial, you can find it here.