Blog/NotesConcept

Part 1: From Zero to Published โ€” How I Built and Published My First React NPM Package

Learn how to build and publish your own NPM package with Rollup, testing, and troubleshooting. Stay tuned for part 2: building a React state management library!

Intermediate

Akash Deep Chitransh

Last Updated May 26, 2025


Advertisement

๐Ÿงฉ Introduction

Recently, I built and published my own npm package — a minimal state management library for React. It’s tiny, fast, supports selectors for optimal re-renders, and works seamlessly with TypeScript.

My goal wasn't just to create another state library — I wanted to learn the complete journey of publishing a package to npm, from writing the logic to bundling it properly and finally seeing it live on npmjs.com. I also needed a lightweight state manager for my personal projects — something simple and custom-tailored.

In this blog, I’ll walk you through everything I learned while preparing, testing, and publishing a React package to npm.

โŒ Why You Can’t Just Push Raw Code to NPM

When I first started, I assumed I could just write some React + TypeScript code and publish it directly to npm. But here's the reality:

NPM packages are meant to be consumed by many different projects, environments, and build systems — so they must be precompiled, optimized, and portable.

Here’s why raw code doesn’t work:

  • TypeScript Needs Compilation: You can't publish .ts or .tsx files directly — they must be compiled to JavaScript.

  • ES Modules, CJS, or UMD?: Different environments expect different module formats. Some tools prefer ESM (import), others expect CJS (require).

  • Multiple Files & Internal Structure: Consumers shouldn't need to know about your internal file structure — they just need a clean dist/ folder with index.js, index.d.ts, etc.

  • Tree-Shaking and Optimization: To reduce bundle size, your code needs to be treeshakable — meaning your package should only export what’s needed.

That’s where bundlers like Rollup come in — they take your raw TypeScript code and produce a clean, production-ready output.

๐Ÿ“ฆ Choosing a Bundler — Why I Picked Rollup

There are several bundlers out there like Webpack, Vite, and ESBuild. But for publishing libraries, Rollup stands out:

  • โœ… Tree-shaking by default — keeps the bundle lean

  • โœ… Simpler config for libraries compared to Webpack

  • โœ… Great plugin ecosystem — especially for TypeScript and JSX

  • โœ… Widely used for React/JS libraries (even used by major open-source projects)

So I chose Rollup to bundle my TypeScript React library into a small, optimized dist/ folder, ready for npm.


โš™๏ธ Setting Up Rollup for Packaging

To bundle my React + TypeScript library, I used Rollup—a lightweight module bundler perfect for libraries. Here's what my rollup.config.js looked like:

import typescript from "@rollup/plugin-typescript";
import dts from "rollup-plugin-dts";
import peerDepsExternal from "rollup-plugin-peer-deps-external";
import terser from "@rollup/plugin-terser";

export default [
  {
    input: "src/index.ts",
    output: [
      {
        file: "dist/index.js",
        format: "esm",
        sourcemap: true,
      },
    ],
    plugins: [peerDepsExternal(), typescript(), terser()],
    external: ["react", "react-dom"],
  },
  {
    input: "src/index.ts",
    output: {
      file: "dist/index.d.ts",
      format: "es",
    },
    plugins: [dts()],
  },
];
 
๐Ÿ” What Each Plugin Does
  • @rollup/plugin-typescript: Compiles .ts and .tsx files into JavaScript.

  • rollup-plugin-peer-deps-external: Automatically marks peerDependencies (like react) as external, so they aren't bundled.

  • @rollup/plugin-terser: Minifies the final output to reduce bundle size.

  • rollup-plugin-dts: Bundles all your .d.ts files into one clean index.d.ts for type support.

๐Ÿ“ฆ Output

  • dist/index.js: Your bundled library code (ESM format).

  • dist/index.d.ts: A single declaration file for TypeScript consumers.

  • โœ… Sourcemaps included for better debugging.


๐Ÿ“ฆ Crafting the Perfect package.json for an NPM Library

Your package.json is the blueprint of your library—it tells the world (and npm) how your package works, what it needs, and how to use it. Here's a breakdown of the important fields in mine:

๐Ÿ”‘ Key Fields

"name": "@akash_deep_chitransh/react-mini-state",
"version": "1.0.3",
"description": "A lightweight and minimal state management solution for small react apps.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": ["dist"],
"type": "module"
 
  • name: Scoped name (@your-username/package-name) helps avoid name conflicts.

  • version: Semver-compliant version number.

  • description: Shows up on npm and GitHub.

  • main: Entry point of the library.

  • types: Entry point for TypeScript types.

  • files: Ensures only the dist folder gets published.

  • type: module: Marks output as ES Modules (used with modern bundlers).

๐Ÿ›  Scripts

"scripts": {
  "build": "rollup -c",
  "test": "vitest run",
  "test:watch": "vitest"
}
 
  • build: Bundles the code using Rollup.

  • test / test:watch: Runs unit tests using Vitest.

๐Ÿค Peer Dependencies

"peerDependencies": {
  "react": ">=18.0.0",
  "react-dom": ">=18.0.0"
}
 

This tells npm:

"Hey, the consuming project must install React—I'll use whatever version they have."

This avoids bundling React into your library, which can lead to duplicate React copies and broken apps.

๐Ÿงช Dev Dependencies

"devDependencies": {
  ...
  "rollup": "^4.41.1",
  "typescript": "^5.8.3",
  "vitest": "^3.1.4",
  ...
}
 

These are tools used only during development:

  • Rollup plugins for bundling

  • TypeScript for typing

  • Vitest + React Testing Library for unit testing

๐Ÿ”— Repository and Metadata

"repository": {
  "type": "git",
  "url": "https://github.com/CodeChitra/react-mini-state"
},
"license": "MIT",
"keywords": ["react", "state-management", "store", "typescript"]
 
  • Helps developers find and contribute to your library.

  • Keywords improve discoverability on npm.

  • Open-source license is a must.


๐Ÿงช How to Test Your NPM Package Locally Before Publishing

Before pushing your package to the world, it’s critical to test it locally—both through automated unit tests and by consuming it in a real app.


โœ… 1. Write Unit Tests with Vitest

We used Vitest because it’s fast, simple, and works great with TypeScript and React.

npm run test         # Run all tests once
npm run test:watch   # Re-run tests on file changes

Make sure your test files are placed correctly (__tests__, .test.ts, etc.), and your assertions verify your library's core logic (e.g., store creation, state updates, selectors).


๐Ÿ”— 2. Test in a Real React App Using npm link

Sometimes unit tests aren't enough. You want to see how the package behaves inside a real React app. That’s where npm link helps.

๐Ÿ“ฆ In your library project

npm run build     # Ensure the dist folder is ready
npm link          # Makes your local package globally linkable

๐Ÿš€ In your test React app

npm link @akash_deep_chitransh/react-mini-state

Now your test app will use the local version of your package—perfect for development and debugging.

Tip: After making changes in your package, just re-run npm run build and refresh your app!


๐Ÿ”„ Undoing the Link

Once you're done testing:

npm unlink @akash_deep_chitransh/react-mini-state
npm install        # Reinstalls the original dependency if needed

๐Ÿš€ Publishing to NPM (and the Issues I Faced)

Once I verified everything was working locally, it was time to share my library with the world via npm. Here’s how I did it — along with a few common gotchas you might face too.


๐Ÿ“ Step 1: Update package.json

Make sure you’ve set these fields correctly:

{
  "name": "@akash_deep_chitransh/react-mini-state",
  "version": "1.0.0",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "files": ["dist"],
  "license": "MIT",
  "peerDependencies": {
    "react": ">=18.0.0",
    "react-dom": ">=18.0.0"
  }
}
 
  • name: Scoped name (@your-username/package-name) helps avoid conflicts.

  • files: Ensures only the dist folder is published.

  • main/types: Points to the correct entry file and declarations.


๐Ÿ” Step 2: Login to NPM

npm login
 

Enter your username, password, and email.


๐Ÿ“ฆ Step 3: Publish the Package

npm publish --access public
 
  • Use --access public if you're publishing a scoped package (@scope/package-name), or it will fail with a 403.


โŒ Common Errors I Faced

1. 403 Forbidden Errors

npm ERR! 403 Forbidden - You do not have permission to publish "package-name"
  • The package name may already be taken. Try using a scoped name like @your-username/package-name.

2. Invalid Tag Name

npm ERR! Invalid tag name "^>=18.0.0"
  • Caused by incorrect peerDependencies version syntax.

  • Fix: use valid semver:
    โœ… "react": ">=18.0.0"
    โŒ "react": "^>=18.0.0"


โœ… Success!

Once published, your package will be available at:

https://www.npmjs.com/package/@akash_deep_chitransh/react-mini-state

You can install it like this:

npm install @akash_deep_chitransh/react-mini-state

๐Ÿง  Conclusion & Key Takeaways

Building and publishing my own NPM package was an incredibly rewarding experience. Not only did I learn how to structure and bundle a reusable library, but I also got hands-on with tools like Rollup, TypeScript, and Vitest—and even tackled real-world issues like dependency management and NPM errors.

Here are the key lessons I took away:

  • โœ… Keep your package.json clean and precise – especially when it comes to main, types, and peerDependencies.

  • ๐Ÿงฉ Bundlers like Rollup help create optimized builds and clean .d.ts files, critical for library consumers.

  • ๐Ÿ” Test locally before publishing using npm link or local installs.

  • ๐Ÿšซ Expect some publishing hurdles, especially with permissions and naming. But every error is a learning moment.

This blog was just the first part. In the next one, I’ll dive into how I actually built my own React state management library, including the API design, React hooks, selectors, and how it compares to other tools like Zustand and Redux.

Thanks for reading! ๐Ÿ™Œ
If you found this helpful or are building something similar, let’s connect on LinkedIn.

 

Share this post now:

Advertisement

๐Ÿ’ฌ Comments (0)

Login to comment

Advertisement

Flaunt You Expertise/Knowledge & Help your Peers

Sharing your knowledge will strengthen your expertise on topic. Consider writing a quick Blog/Notes to help frontend folks to ace Frontend Interviews.

Advertisement


Other Related Blogs

Master Hoisting in JavaScript with 5 Examples

Alok Kumar Giri

Last Updated May 2, 2025

Code snippet examples which will help to grasp the concept of Hoisting in JavaScript, with solutions to understand how it works behind the scene.

What is CORS ? Cross-Origin Resource Sharing Explained [For Interviews]

Anuj Sharma

Last Updated Dec 10, 2024

A brief explanation of Cross-Origin Resource Sharing (CORS) concept to enable client application accessing resources from cross domain and HTTP headers involved to enable resource access.

HTTP/2 vs HTTP/1.1: What's the Key Difference?

Anuj Sharma

Last Updated Jan 29, 2025

Understand the difference between HTTP/2 vs HTTP/1.1 based on the various parameters, which helps to understand the improvement areas of HTTP/2 over HTTP 1.1

Understand JavaScript Date Object with Examples (for JavaScript Interviews)

Anuj Sharma

Last Updated Jan 9, 2025

Go through different ways to display dates using javascript date object. It covers examples of date object usage to understand the main concepts of javascript date object.

Promise.all Polyfill in JavaScript - Detailed Explanation [For Interviews]

Anuj Sharma

Last Updated Jan 16, 2025

Deep dive into promise.all polyfill in javascript will help to understand the working of parallel promise calls using Promise.all and its implementation to handle parallel async API calls.

How to Format Phone Number in JavaScript (JavaScript Interview)

Anuj Sharma

Last Updated Jan 9, 2025

Learn the best & quickest way to format phone number in JavaScript with or without country codes. This will help websites to show the phone numbers in a more human-readable format.

FrontendGeek
FrontendGeek

ยฉ 2024 FrontendGeek. All rights reserved