Personal Blog

My notes of how to compile a typescript library

11/30/2022, 7: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:

  • 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

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:

  • 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

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:

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

  • tslib

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

  • 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



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

Profile Picture


I'm a Senior Software Engineer working in Berlin, primary focused on Frontend Technologies but also interested in Backend with Go, Terminal Apps and Neovim.

More posts

How to automate software installation and configuration on macOS using Homebrew and Stow

Debugging Javascript applications with Neovim

How to create a basic implementation of React Hook Form

Another way to create modals in react using the HTML dialog element

How to debug like a PRO using Neovim 🔥

© 2023 • Built with Gatsby