How to Publish a React Component as an npm Package Using Vite

May 29, 2025
~3 min read
ReactViteLibrarynpm

Introduction

Have you ever built a cool React component and wanted to share it with the world? In this guide, I'll show you how I published @janardanpethani/genmoji — a mood-based emoji generator — as an npm package, starting from a Vite React app.

Why Use a lib Directory for Components?

Organizing your reusable code in a lib/ directory keeps your published package clean and focused. It separates library code from demo or app code, makes exports easier to manage, and follows best practices used by many open source libraries.

1. Create a Vite React App

bash
1npm create vite@latest <your library name> -- --template react-ts
bash
1cd <your library name>
bash
1npm install

2. Develop Your Component

Write your component as you would in any React app. For example, in lib/components/Chat.tsx:
tsx
1import React from "react";
2
3type ChatProps = {
4  // Add any props you want to support
5};
6
7export const Chat: React.FC<ChatProps> = (props) => {
8  return <button>Open Genmoji Chat</button>;
9};
Create index.ts in lib/components and export everything from it:
ts
1export * from "./Chat";
Create index.ts in lib and export everything from it:
ts
1export * from "./components";

3. Configure Vite for Library Build

ts
1import { defineConfig } from "vite";
2import react from "@vitejs/plugin-react";
3import dts from "vite-plugin-dts";
4
5// https://vite.dev/config/
6export default defineConfig({
7  // Configure plugins for Vite
8  plugins: [
9    // React plugin for Vite
10    react(),
11    // Plugin to generate TypeScript declaration files (d.ts)
12    dts({
13      // Specify the path to the build-specific tsconfig file
14      tsconfigPath: "./tsconfig.build.json",
15    }),
16  ],
17  // Build configuration options
18  build: {
19    // Library build options
20    lib: {
21      // Entry point for the library build
22      entry: "lib/index.ts",
23      // Function to determine the output file name based on the format
24      fileName: (format) => `genmoji.${format}.js`,
25      // Output formats for the library
26      formats: ["es", "cjs"],
27    },
28    // Rollup specific options (used under the hood by Vite)
29    rollupOptions: {
30      // Specify dependencies that should not be bundled into the library
31      external: ["react", "react-dom"],
32      // Output options for Rollup
33      output: {
34        // Configure global variables for external dependencies
35        globals: {
36          react: "React",
37          "react-dom": "ReactDOM",
38        },
39      },
40    },
41    // Empty the output directory before building
42    emptyOutDir: true,
43    // Do not copy the public directory to the output directory
44    copyPublicDir: false,
45    // Minify the output code
46    minify: true,
47  },
48})
You need to install vite-plugin-dts to generate TypeScript declaration files.
bash
1npm install vite-plugin-dts

4. Update package.json

json
1{
2  ...
3  "type": "module",
4  "main": "dist/genmoji.cjs.js",
5  "module": "dist/genmoji.es.js",
6  "types": "dist/index.d.ts",
7  "exports": {
8    ".": {
9      "types": "./dist/index.d.ts",
10      "import": "./dist/genmoji.es.js",
11      "require": "./dist/genmoji.cjs.js"
12    }
13  },
14  "files": [
15    "dist"
16  ],
17  "publishConfig": {
18    "access": "public"
19  },
20  ...
21  "dependencies": {
22    "framer-motion": "^12.15.0"
23  },
24  "peerDependencies": {
25    "react": "^19.1.0",
26    "react-dom": "^19.1.0"
27  },
28  ...
29}

Why These package.json Fields?

FieldDescription
main/module/typesEnsures compatibility with all bundlers and editors.
exportsModern way to define entry points for different environments.
filesOnly publish what's needed (keeps npm package small and clean).
peerDependenciesPrevents multiple React versions in the consumers app.
publishConfigThis setting is specifically relevant for scoped packages (packages whose names start with @scope/, like yours: @janardanpethani/genmoji). By default, scoped packages are published as private. Setting "access": "public" ensures your package is published publicly, making it available for anyone to install from the npm registry.
scripts.buildRuns both TypeScript and Vite build for a complete output.
For more, see npm package.json docs.

5. Build Your Package

bash
1npm run build
This will output your library to the dist/ folder.

6. Publish to npm

bash
1npm login
bash
1npm publish --access public

7. Usage Example

tsx
1
2import { Chat } from "@janardanpethani/genmoji";
3function App() {
4  return <Chat />;
5}

Conclusion

Vite makes it easy to go from a React app to a reusable npm package. You can see the full source and real-world example here: github.com/JanardanPethani/genmoji

References