Bundle and Publish TypeScript Package using Rollup
Tags: javascript, npm, typescript, node
Published: 2024-06-27 08:33:15

Last couple of days I was investing most of my time in the Open Source Community. I published few packages to npm and also contributed to some Open Source Projects as well. 
Lets see how I bundle my TypeScript package using Rollup. 

As you know Rollup is a JavaScript bundler and it is quite popular to Open Source who basically want to build some kind of packages because by bundling through Rollup, we have almost zero boilerplate code as compared to some other bundlers e.g. Webpack. So the bundle size would be quite small. That's why many many developers prefer to choose Rollup over other JavaScript bundlers.

## Package written in TypeScript

Lets say this is the code you have and you want to share with other developers by publishing this as a package to npm registry. 

```ts
export const sum = (a: number, b: number) => {
  return a + b;
};

export const subtract = (a: number, b: number) => {
  return a - b;
};
```

## Configure tsconfig.json

First you need to configure your tsconfig.json file so that our IDE understand our TypeScript code and also it can generate typescript types automatically based on your written typescript code and 

Here's is the tsconfig.json file code:
```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Node",
    "esModuleInterop": true,
    "outDir": "./dist/esm",
    "strict": false,
    "declaration": true,
    "declarationDir": "./dist/types",
    "paths": {
      "rollup/parseAst": ["./node_modules/rollup/dist/parseAst"]
    }
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}
```

Just ignore the `paths`, the reason I added here because I added unit tests using vitest and vitest also uses rollup under the hood so vitest is throwing an error so that's why I added it here. So just ignore it. You don't need this line anyway. I just added it for my future reference 😀.

## Installing Dependencies
First you need to install rollup as a dev dependency because you don't need to ship this dependency to your production code. Use the following command to install the package

```sh
npm i -D rollup @rollup/plugin-typescript typescript
```

and because we are using typescript we also need to install rollup typescript plugin as well. And one more dependency to install i.e. typescript. 

> Let me clear one thing: we'll generate typescript types using tsc compiler, we can generate types from rollup but I'll explain later why I am not using rollup to generate typescript types out of the code.

## Configure Rollup
Thats a fun part. We have two options to configure rollup in order to bundle your files. 

1. use cli directly - I mean add the commands to your script file in the package.json and then run that script (which I would not recommend).
2. add all the rollup configurations in some kind of config file (we are using this).

Now create a rollup.config.js file in the root of the project. You can use either cjs or mjs way to define all the rollup configurations.

> By the way if anyone don't know what is the heck is cjs & mjs `cjs` is commonjs module system which is using `require` or `module.exports` to import and export modules. `mjs` is a modern es6 way to import and export module or code.

Here's the rollup.config.js code.
```js
const typescript = require("@rollup/plugin-typescript");

const typescriptOptions = {
  exclude: ["tests/**/*"],
  compilerOptions: { declaration: false },
};

const data = [
  {
    external: ["typeorm", "sinon"],
    input: "src/index.ts",
    output: { file: "dist/esm/index.js", format: "esm" },
    plugins: [typescript(typescriptOptions)],
  },
  {
    external: ["typeorm", "sinon"],
    input: "src/index.ts",
    output: { file: "dist/cjs/index.js", format: "cjs" },
    plugins: [typescript(typescriptOptions)],
  },
];

module.exports = data;
```

I am using cjs way to define my configurations. You can choose mjs its totally upto you. 

The configuration is very straight forward. If we want to publish our code as a package, we atleast need two formats 

1. cjs
2. esm

So if anyone is using cjs, then we have that package bundle as well and same for esm. 

I am using just one plugin that will basically compile our typescript code to javascript. And as you can see I disable the option to create typescript types out of the code. The reason is if we don't do like this Rollup will create types folder in each bundle separately instead of one in the dist folder. 

We can add other plugins as well to make our code minify using terser plugin but I don't need that one. If you do, use any much plugins as you want based on your needs. 

## Create TypeScript types

As you know we are not using rollup to create typescript types, so do we need to manually create the types? No, we'll use TypeScript compiler for that. Here's how you can do this

```sh
"build:types": "tsc -p tsconfig.json --emitDeclarationOnly",
```

`tsc` using tsconfig.json file by default. If you want to change to different file just use flags as you can see I am doing here. As you know we just need to create types so we can pass some other arguments e.g. `--emitDeclarationOnly`.

## Setup package.json
This is the final step, now we need to configure package.json. Every package must have package.json file. This is kind of metadata file that npm will use to extract package name, version, author, dependencies, scripts etc.

```json
{
  "name": "jazim-package",
  "version": "1.0.0",
  "main": "./dist/cjs/index.js",
  "module": "./dist/esm/index.js",
  "types": "./dist/types/index.d.ts",
  "exports": {
    ".": {
      "require": "./dist/cjs/index.js",
      "import": "./dist/esm/index.js"
    }
  },
  "scripts": {
    "build:types": "tsc -p tsconfig.package.json --emitDeclarationOnly",
    "build": "rimraf ./dist && npm run build:types && rollup -c",
    "prepublishOnly": "npm run build",
  }
  ...
}
``` 

Lets understand these properties. 

`main`: if someone is using cjs e.g. require to import the package then it will use the cjs bundle automatically.
`module`: if someone is using mjs or es6 in their code then it will use our esm bundle. 
`types`: we need to tell the path where we added our typescript types.

> `main`, `module` is for those who are using old versions of npm and nodejs. So we need to support those as well. 

`exports`: this is very straight forward, if someone using require then use cjs bundle and if someone is using import then use esm bundle. This is a new way if someone using new versions of npm and nodejs.

Next part is `scripts`:
`build:types`: this will generate typescript types.
`build`: this will bundle our code and we added multiple commands seperated them using `&&`.
`prepublishOnly`: this will run before when we publish our package.

There is other command as well e.g. `prepublish`, the problem of using this command, it will be run before publishing the package as well as when we install the package. 

## .npmignore
As the filename suggests, many times we don't need to ship our typescript code or some other files so we can simply put all those folders and files into that file. This serves the same purpose as .gitignore

```
/node_modules
/src
rollup.config.js
tsconfig.json
.gitignore
```

If you don't wanna use this file, we can simply put the folders and files to the `files` field in the package.json file e.g.

```json
{
  "files": ["dist", ...]
}
```

---

That's pretty much it. If I made some mistake, please correct me. Please let me know your thoughts on this in the comment section. 

<a href="https://www.buymeacoffee.com/jazimabbas" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>

So if you enjoy my work and found it useful, consider buying me a coffee! I would really appreciate it.