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
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"
]
}
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:
It gets compiled to:
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
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: 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()],
},
];
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"
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
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)