DEV Community

Cover image for My notes of how to compile a typescript library
Miguel Crespo
Miguel Crespo

Posted on • Originally published at miguelcrespo.co

My notes of how to compile a typescript library

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://stackblitz.com/edit/typescript-vys5ns?file=src/index.ts

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": [
    "src"
  ],
  "exclude": [
    "node_modules"
  ]
}
Enter fullscreen mode Exit fullscreen mode

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

It gets compiled to:

After

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

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:

tsc --module esnext
tsc --module commonjs
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

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

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()],
  },
];
Enter fullscreen mode Exit fullscreen mode

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": [
     "src"
Enter fullscreen mode Exit fullscreen mode

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": "",
Enter fullscreen mode Exit fullscreen mode
  • 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

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

Top comments (0)