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:
{
"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 format1.0.0
(also known as semantic versioning).main
is the file definition that will execute the package. The default value isindex.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 valueISC
, 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
:
{
"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:
{
"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:
{
"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:
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:
{
"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:
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:
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:
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 commandnpm run build
, it will generate a new folder in our project calleddist
. Inside, it will generate two files:index.js
andindex.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 tomain
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 themain
andmodule
fields). Even though both sections represent the same functionality, Vite recommends defining them both.
Our package.json
file should now have the following code:
{
"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:
{
"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.