My notes of how to compile a typescript library
Tags: javascript, typescript
Published: 2022-11-30 09:00:00

## Introduction

Recently I started developing an internal typescript library for my current company and while I was looking for the best way to transpile and distribute the code for all the clients that will soon implement it, I found these two options:

- tsc
- rollup

Here is my experience with both options…

## Code we will use

To prove the point of this post, we will use this dummy project where

- The folder `no-export-please`

  In this folder, we have code that is not supposed to be exported outside the library

- The folder `not-used`

  Here we have a function that is not used anywhere, so ideally it should not be included in the final transpiled code


![Source code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zhb0lhjxuqfcu9m3rvnu.png)



<https://stackblitz.com/edit/typescript-vys5ns?file=src/index.ts>

### The tsconfig file

``` json
{
  "compilerOptions": {
    "target": "esnext",
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "declaration": true,
    "outDir": "dist",
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
    "emitDeclarationOnly": false,
    "strict": true,
    "esModuleInterop": true
  },
  "include": [
    "src"
  ],
  "exclude": [
    "node_modules"
  ]
}
```

## Using `tsc`

`tsc` is the typescript *Command Line Interface* `CLI` tool that gets installed in your machine when you install `typescript`.

The first option I found was to use `tsc` to transpile the typescript code to javascript files and generate the types

I think this is a good option when you have a very simple typescript file and you only need to transpile it to vanilla javascript, but as the project grows, you start to see problems like:

### It exposes implementation details

One of the problems I found while using `tsc` was that it might lead to exposing implementation details of the library because it keeps the same structure of the src code in the transpiled code and it also exports by default all types and **it doesn't matter whether you are exporting them outside of the libraries or not**, which means users of the library can import any file even when we don't want them to do it

For example, with our dummy project:


![Before](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/33734n544t7bkdx4utuf.png)



It gets compiled to:


![After](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r83f7hmb07jbku7yfvem.png)



This means client libraries can request the code from the `no-export-please` folder, which if you remember, was not supposed to be accessible outside the library.

### No tree-shaking

It doesn't remove unused code, leading to unnecessary compiling time and disk usage, for example, our `unusedFunction` in our dummy project still is present in the transpiled code even when it's not used anywhere


![Tree-shaking](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zjujibwfwezjic0vq9wh.png)



### No advanced way to tweak the transpilation

There's no way to customize the transpilation for some advanced cases like:

- Polyfills/transpiling

To be fair, this also applies to `rollup` but at least there's a standard API to support it there.

### npm dependencies

It doesn't handle well npm dependencies, it doesn't include them in the final bundle and you have to do some tricks to support them, like copying the dependencies to the dist folder, more details can be found in this StackOverflow question

<https://stackoverflow.com/questions/41109461/how-include-node-modules-in-output-directory-with-typescript>

### Only one supported module at a time

The module used in the transpiled javascript code is the one defined in the `tsconfig` config file, so if your client application is using `commonjs` and you're only generating `es6` modules, you will have problems. You can overcome this issue by executing `tsc` with different parameters multiple times though, like:

``` shell
tsc --module esnext
tsc --module commonjs
```

## Rollup

Rollup is a very fast module bundler that supports `es modules` out of the box and has an incredibly simple plugin ecosystem with a lot of options that let you extend the functionality.

At first, the first experience with rollup can be daunting, we need to install a lot of plugins as npm packages just to start!

But after a while, you will probably start to love the syntax

### Installing dependencies

Let's start by installing `rollup` and all the plugins we need

``` bash
npm i --save-dev rollup typescript rollup-plugin-peer-deps-external rollup-plugin-dts @rollup/plugin-typescript @rollup/plugin-node-resolve tslib @rollup/plugin-commonjs
```

Here's a short explanation for each package:

- rollup

This is the main binary, you can either install it globally or locally like we're doing here

- typescript

Main typescript dependency, you need this anyway if you're using typescript

- rollup-plugin-peer-deps

This plugin will delete from the bundled code the peer dependencies

- rollup-plugin-dts

This plugin will take all the typescript types in your project and bundle them in a single file while making sure of only exporting the types that you want to be accessible outside

- @rollup/plugin-typescript

This plugin is the one in charge of telling `rollup` how to handle typescript files

- @rollup/plugin-commonjs

If you want to import `commonjs` libraries, you need to add this plugin

- @rollup/plugin-node-resolve

Plugin to implement the same module resolve algorithm that node uses, here are more details: <https://nodejs.org/api/modules.html#modules_all_together>

- tslib

Library to avoid code duplication, check more details here: <https://www.typescriptlang.org/tsconfig#importHelpers>

### Rollup config

Then, we need to create a `rollup.config.js` file that will contain the rollup configuration, it can be a bit long but the syntax is very simple to understand, basically, you're exporting a js array where each entry receives a `input`, `output` and `plugins`

``` javascript
import resolve from '@rollup/plugin-node-resolve';
import typescript from '@rollup/plugin-typescript';
import commonjs from '@rollup/plugin-commonjs';
import dts from 'rollup-plugin-dts';
import peerDepsExternal from 'rollup-plugin-peer-deps-external';

import packageJson from './package.json' assert { type: 'json' };

export default [
  {
    input: './src/index.ts',
    output: [
      {
        file: packageJson.main,
        format: 'cjs',
        sourcemap: true,
      },
      {
        file: packageJson.module,
        format: 'esm',
        sourcemap: true,
      },
    ],
    plugins: [
      peerDepsExternal(),
      commonjs(),
      resolve(),
      typescript({ exclude: ['**/__tests__', '**/*.test.ts'] }),
    ],
  },
  {
    input: './dist/index.d.ts',
    output: [{ file: packageJson.types, format: 'esm' }],
    plugins: [dts()],
  },
];
```

## Updating the tsconfig.json file

Next, we need to update the `tsconfig.json` file to tell typescript that we only want the type files because all the other things will be handled by `rollup`

``` language-diff-json
     "outDir": "dist",
     "moduleResolution": "node",
     "allowSyntheticDefaultImports": true,
-    "emitDeclarationOnly": false,
+    "emitDeclarationOnly": true,
     "strict": true,
   },
   "include": [
     "src"
```

## Updating the package.json file

``` language-diff-json
   "name": "typescript-test",
   "version": "1.0.0",
   "description": "",
+  "main": "dist/lib.js",
+  "module": "dist/lib.esm.js",
+  "types": "dist/lib.d.ts",
   "scripts": {
+    "build": "rollup --c"
   },
   "keywords": [],
   "author": "",
```

- main It's the entry point for clients that are looking for `commonjs` modules
- module It's the entry point for clients that are looking for `es` modules
- types It tells typescript where the typescript types are located

## Compiling and result

To compile we just need to run `npm run build` and we will get all the files


![Final](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bekjlufiok483pb4o5o1.png)



### Problems

I'm still trying to figure out how to clean all the leftover typescript types that are left after `dts` bundle everything in one file