Skip to content

My notes of how to compile a typescript library

Posted on:November 30, 2022 at 07:00 AM


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:

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

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

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

Source code

The tsconfig file

  "compilerOptions": {
    "target": "esnext",
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "declaration": true,
    "outDir": "dist",
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
    "emitDeclarationOnly": false,
    "strict": true,
    "esModuleInterop": true
  "include": [
  "exclude": [

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:

Source code It gets compiled to:

Dist folder 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


No advanced way to tweak the transpilation

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

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

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:

tsc --module esnext
tsc --module commonjs


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

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:

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

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

This plugin will delete from the bundled code the peer dependencies

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

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

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

Plugin to implement the same module resolve algorithm that node uses, here are more details:

Library to avoid code duplication, check more details here:

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

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: [
      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

     "outDir": "dist",
     "moduleResolution": "node",
     "allowSyntheticDefaultImports": true,
-    "emitDeclarationOnly": false,
+    "emitDeclarationOnly": true,
     "strict": true,
   "include": [

Updating the package.json file

   "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": "",

It’s the entry point for clients that are looking for commonjs modules

It’s the entry point for clients that are looking for es modules

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



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