The Easiest Way to Create Your First NPM Package

Written by gmakarov | Published 2023/12/15
Tech Story Tags: npm-package | frontend | react | microbundle | library | publish-npm-packages | npm-tutorial | hackernoon-top-story | hackernoon-es | hackernoon-hi | hackernoon-zh | hackernoon-fr | hackernoon-bn | hackernoon-ru | hackernoon-vi | hackernoon-pt | hackernoon-ja | hackernoon-de | hackernoon-ko | hackernoon-tr

TLDRLet’s talk a bit about it microbundle. I find it particularly effective for simple libraries because you don’t have to worry about configuration, allowing you to focus on developing your package. Here is a short list of its features: Built-in configs; all you have to do is add an “exports” field in package.json TypeScript support out of the box without tsconfig.json Multiple output formats (CJS, UMD, and ESM) Built-in Terser compression via the TL;DR App

This guide will walk you through the easiest process of building and releasing your NPM package, from start to finish, using a microbundle.

Let’s talk a bit about it microbundle. I find it particularly effective for simple libraries because you don’t have to worry about configuration, allowing you to focus on developing your package.

Here is a short list of its features:

  • Built-in configs; all you have to do is add an “exports” field in package.json
  • TypeScript support out of the box without tsconfig.json
  • Multiple output formats (CJS, UMD, and ESM)
  • Built-in Terser compression

Basically, microbundle is built on top of rollup.js. If you have more complex libraries to build than I will mention in this article, you might consider using a pure rollup.js configuration.

Initializing Your Package

As an example, let’s create a simple library for summing two numbers, which will export only one functionsum.

  1. Create a folder for the project, and run npm init with default values to generate package.json

  2. Create index.ts in src folder

    // src/index.ts
    export function sum(a: number, b: number) {
      return a + b;
    }
    
  3. Install microbundle

    npm i -D microbundle

  4. Updatepackage.json with the following values:

    // package.json
    ...
      "type": "module",
      "source": "src/index.ts", // your source code
      "exports": {
    		"types": "./dist/index.d.ts", // TypeScript declaration file
        "require": "./dist/index.cjs", // CommonJS entry point
        "default": "./dist/index.esm.js" // ES Module entry point
      },
      "main": "./dist/index.cjs", // where to generate the CommonJS bundle
      "module": "./dist/index.esm.js", // where to generate the ESM bundle
      "unpkg": "./dist/index.umd.js", // where to generate the UMD bundle
    	"types": "./dist/index.d.ts", // TypeScript declaration file for the package
    	"scripts": {
        "build": "microbundle", // compiles "source" to "main", "module", "unpkg"
        "dev": "microbundle watch" // re-build when source files change
      }
    ...
    
  5. Run the build script

    npm run build

    The output should contain exactly the files that we declared in package.json

And voilà, we made our first package. Let’s take a look at more complex scenarios.

Adding React Into Your Library

If you want to bring React to your library, you can still use itmicrobundle, but now, your build command should look like this:

microbundle --jsx React.createElement --jsxFragment React.Fragment --jsxImportSource react --globals react/jsx-runtime=jsx

Add the command to package.json into build script for future convenience:

// package.json
...
"scripts": {
  "build": "microbundle --jsx React.createElement --jsxFragment React.Fragment --jsxImportSource react --globals react/jsx-runtime=jsx"
}
...

Using Storybook for UI Components

While building a UI library, you might need a sandbox where you can develop, visualize components, and provide demo components for your documentation.

Here comes the storybook. It’s a sandbox with its own convenient interface and bundler, where you can easily describe various states of your components. Each capture of your component state is called a "story."

This picture, taken from their documentation, shows what it looks like:

Installing Storybook is quite simple; just run the command inside your library:

npx storybook@latest init

This command will install all required dependencies for Storybook, add scripts to run, and build Storybook into package.json , create a folder .storybook with default configuration, and add some examples of stories to the foldersrc/stories.

Integrating Styling Into Your Library

You can add styling in one of two ways: CSS file or CSS-in-JS. The CSS file allows easy customization but requires separate inclusion, whereas the CSS-in-JS simplifies styling but increases bundle size.

  • CSS file

    Create a CSS file in the src directory, and import it into the root react component file:

    // src/index.tsx
    
    import './styles.css';
    
    export const MySuperComponent = () => {
    	return (
        <h1 className="title">Hi there!</h1>
      )
    };
    

    So, let’s run the build command again.

    npm run build
    

    And your importedstyles.css file will be created in the dist folder:

    Great! We have obtained a CSS file with the necessary styles. However, there is a slight inconvenience with this solution. The CSS file needs to be added separately after the package is installed.

    Therefore, users of your library will need to use a CSS loader (e.g., a CSS-loader for the webpack) to handle your CSS file, and their usage will look like this:

    import { MySuperComponent } from 'my-super-library'; 
    import 'my-super-library/dist/styles.css';
    
    export const App = () => {
      return (
        <MySuperComponent />
      );
    }
    

  • CSS-in-JS

    You can use libraries like styled-components for this purpose. And it will look like this:

    import styled from 'styled-components';
    
    const Title = styled.h1`
      font-size: 30px;
    	font-weight: bold;
    `;
    
    export const MySuperComponent = () => {
    	return (
        <Title>Hi there!</Title>
      )
    };
    

    With this solution, users won’t need to import a CSS file and add a special loader for their project. After installing the library, the component will come with its own styling. However, this will increase the bundle size and make it more difficult for users to customize elements using CSS selectors.

Choose the option that suits you best. I prefer to use the CSS file because it allows users to customize any element with CSS selectors, doesn’t affect the bundle size, and works faster.

Developing a Detailed README.md File

A README.md file provides information about your library, how to install it, its basic usage, and the features it has. This is often the first file that developers read when they encounter your repository or NPM package, so it should be concise and informative.

I like to create a structure in the following order:

  1. Title
  2. Super short package description
  3. Fancy statistic badges (shields.io)
  4. If your library is a UI component, include a screenshot or provide a demo link on CodeSandbox
  5. Features list
  6. Installation guide
  7. Code fragments with usage
  8. Options and props that your library accepts for configuration

You can refer to examples ofREADME.md files from my packages, such as dot-path-value and react-nested-dropdown, for inspiration.

Navigating Dependency Management

This is an important part because if you do it wrong, users may face version conflicts or other problems, and they will have to remove your library. So, let's take a look at the main differences between dependency types.

  • “devDependencies” are only for development and will not be included in your bundle. For example, if you have themicrobundle package installed, which users don’t need to use, keep it in devDependencies, and it won’t appear in the bundle.

  • "dependencies" will be installed along with the package. Include dependencies that your package needs to work in users’ projects. Note that some dependencies, such as “react,” might already be installed in the user’s project. Having a duplicate “react” in your package could increase the bundle size. This is where “peerDependencies” come in handy.

  • “peerDependencies” are dependencies that your package uses but won’t include in your bundle. Your package will utilize the version of the dependency that the user has in their project.

    Basically, we should specify a peer dependency if we are creating a library for its ecosystem. For example, if you are creating a React component, set React as a peer dependency. If developing a React component with a calendar, add React and a date calculation library (e.g., date-fns) as peerDependencies.

    If the user doesn’t have the specified peer dependency in their project, the npm install command will display a warning, but it will not automatically install the dependency.

Just a small example of how it looks:

// package.json
...
"dependencies": { // libraries which will be installed along with your library
    "clsx": "^1.2.1" // just library for className combining
  },
"peerDependencies": { // user should have these packages installed
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0" // user should have react 16.8+ version
},
"devDependencies": { // won't be in user's bundle, these libraries just for your developing needs
    "@types/react": "^18.2.33",
		"react": "^18.2.0", // in case if we are using storybook, we need actual react library to render our components there
    "microbundle": "^0.15.1",
},
...

Using GitHub for Your Package

If you are publishing an NPM package, it means it will be publicly accessible (if you have a free account). To gather feedback from users, you can create a GitHub repository for your original code. People can create issues and communicate with you about your package there. You can also describe your releases and get some stars!

You can certainly skip this step, but it is an integral part of the developer culture and can be a valuable contribution to open-source.

Publishing and Maintaining the Package

Before you can publish your package, it's essential to ensure that your package.json file is properly configured. Here are some important steps to follow:

  1. Name and try to describe the core functionality of your library. For example:

    "name": "react-color-picker"
    

  2. Add GitHub repository information (if it exists):

    ...
    "homepage": "https://github.com/{your_repository}",
    "repository": {
        "type": "git",
        "url": "https://github.com/{your_repository}"
      },
    "bugs": {
        "url": "https://github.com/{your_repository}/issues"
      },
    ...
    

  3. Configure the files :

    ...
    "files": [
        "dist",
    ],
    ...
    

    You should specify the files that will be included in node_modules, when your library is installed. Usually, including the dist folder is sufficient.

  4. Add keywords :

    Keywords are an array of strings that describe your package and are used by NPM for searches and recommendations. Choose words relevant to your package that you anticipate people will use when searching. Let’s create keywords for our sum library:

    ...
    "keywords": ["typescript", "math", "sum", "numbers", "arithmetic", "calculator", "calculation"]
    ...
    

    It’s important to specify your technologies because users often search for terms like “typescript library for math” or “react calendar picker.”

  5. Create an NPM account if you haven’t already, and run npm login in your terminal; follow the prompts to authenticate your account. By default, the version of your package will be 1.0.0; you can check it in the package.json file. I recommend changing it to 0.0.1.

  6. Run npm publish, and you're done! To update the version in the future, use the command npm version patch to increment the version, and then publish the updated package with npm publish.

Conclusion

As you can see, creating your own library is very easy! Essentially, this is all you need for creating and maintaining the package. If you struggle with limiting your library with microbundle, I recommend using rollup.js with a more specific configuration.

Creating NPM packages and contributing to open-source is a rewarding and valuable experience for developers of all skill levels. It allows you to share your code with the community, gain a lot of experience, and build a portfolio of your work.


Written by gmakarov | Senior Software Engineer | Frontend | React | TypeScript
Published by HackerNoon on 2023/12/15