Bundling TypeScript with Esbuild for NPM
3 min readCompared to other build tools, Esbuild is a radically more performant bundler. This post will look at setting up a build script to compile TypeScript projects for NPM.
Follow along with the example repo here.
Getting Started
First, we’ll install the library and set up our build script:
1npm install esbuild typescript --save-dev
1"scripts": {2 "build": "node build.js"3}
1const { build } = require('esbuild')23build({4 entryPoints: ['src/index.ts'],5 outdir: 'dist',6 bundle: true,7})
Esbuild will automatically detect that we’re using TypeScript and attempt to load a tsconfig.json
file if available. Note that any compiler options set in tsconfig.json
will take precedence over build
options.
An important option we need to set is the external
property. Since we are publishing this library to NPM we’ll want to exclude any of our dependencies from the final bundle. Convinently enough, we can use our package.json
file as a source of truth:
1const { build } = require('esbuild')2const { dependencies, peerDependencies } = require('./package.json')34build({5 entryPoints: ['src/index.ts'],6 outdir: 'dist',7 bundle: true,8 external: Object.keys(dependencies).concat(Object.keys(peerDependencies)),9})
Now, if we run our script, we should see our built files in a dist
directory:
1npm run build
Additional Formats
When bundling libraries for NPM, it’s good practice to build multiple files for different browser and bundler formats. We can do this easily by pulling out shared options and passing them into any number of different builds we want to provide. By default, format
is set to iife, which bundles for a browser environment. Let’s add a format for ESM users:
1const { build } = require('esbuild')2const { dependencies, peerDependencies } = require('./package.json')34const shared = {5 entryPoints: ['src/index.ts'],6 bundle: true,7 external: Object.keys(dependencies).concat(Object.keys(peerDependencies)),8}910build({11 ...shared,12 outfile: 'dist/index.js',13})1415build({16 ...shared,17 outfile: 'dist/index.esm.js',18 format: 'esm',19})
Type Definitions
Since Esbuild can only bundle our code, we still need to generate definition files. This will allow library consumers to utilize the types we’ve written. While it’s relatively simple to emit multiple declaration files using TypeScript directly, the npm-dts library helps us bundle our types into one succinct file:
1npm install npm-dts --save-dev
1const { Generator } = require('npm-dts')23new Generator({4 entry: 'src/index.ts',5 output: 'dist/index.d.ts',6}).generate()
Publishing to NPM
Make sure the fields in your package.json
file are filled out correctly and point to the proper files we’ve created:
1{2 "module": "dist/index.esm.js",3 "main": "dist/index.js",4 "typings": "dist/index.d.ts"5}
Now we’re ready to publish our library! 🎉
1npm publish
Conclusion
We looked at how Esbuild provides a fast build system that we can use to compile and bundle TypeScript when developing libraries for NPM. In the future, I imagine we’ll see a type checker written in a performant language like Go or Rust that will drastically speed up our workflows.
- development
- esbuild
- react