esbuild
skuba currently uses tsc as its default build tool. This topic provides some context around this decision and discusses how esbuild stacks up as an alternative.
Background
“Using the TypeScript compiler is still the preferred way to build TypeScript.”
The official TypeScript compiler (tsc) is the sensible default for building TypeScript projects. It’s a simple tool that just works and has type checking built-in, but doesn’t expose a plugin system. (See ttypescript for an unofficial implementation).
We have explored a few alternatives:
-
Babel was previously integrated into skuba.
However, its configuration was complex and largely divergent from a typical TypeScript project, and the cost-benefit never really worked out for backend projects.
-
esbuild is a bundler and minifier that supports TypeScript transpilation and is now experimentally included in skuba.
It has grown in prominence, particularly in frontend tooling like Vite that may see broader use at SEEK in future.
-
swc is another notable alternative that features in Next.js and Parcel.
There are a couple of gotchas when evaluating alternative build tools like esbuild:
-
esbuild strips type information rather than checking it, so it is often paired with tsc in practice.
skuba lint
already type checks via tsc, so askuba build
without type checking is acceptable. We still use tsc to emit type definitions where requested viatsconfig.json#/compilerOptions/declaration
. -
It’s another moving part in the toolchain.
esbuild is not fully compatible with all existing
tsc
configurations, may lag behind TypeScript in language features, and lacks rich interoperability with tooling like Jest (viats-jest
).These issues can be mostly contained within a centralised toolkit like skuba, but it makes it more difficult to duct tape tools together on an ad-hoc basis, and could lead to inconsistent runtime behaviour across
skuba build
,skuba node
andskuba start
, and especially when compared to tsc.
At the same time, esbuild presents potential benefits for skuba:
-
Faster and more flexible builds.
This may allow us to simplify and speed up complex scenarios like
skuba build-package
. -
Bundling and minification.
This can be useful to improve cold start performance in serverless environments like AWS Lambda.
-
A plugin architecture for transforming code during the build process, along with limited built-in support for
tsconfig.json
paths.This allows us to easily configure and resolve module aliases at compile time.
skuba’s existing
tsc
-based build supports a singlesrc
alias via skuba-dive’s register hook, which means we impose an unfortunate runtime dependency. The way that this hook must be imported is a bit magic and makes it difficult to execute arbitrary TypeScript source files, as the hook must be loaded before any aliased imports. (This is a big part of why skuba has the concept of an explicit entry point for a project.)It also enables interoperability with non-JavaScript content. For example, backend projects could
import query from './query.sql'
. Such content types are not resolvable by the Node.js runtime, but a loader can transform the imports at build time.
Try it out
-
package.json
{ "skuba": { "build": "esbuild", }, }
-
tsconfig.json
{ "compilerOptions": { "isolatedModules": true } }
(skuba v5.0.0 defaults this option to true.)
-
…and that’s it!
# uses esbuild instead of tsc pnpm build
Current limitations
This integration is still experimental and only includes the bare minimum to supplant a basic tsc-based build.
-
Some TypeScript language features are not supported.
See esbuild’s TypeScript caveats for more information.
-
esbuild is not wired up to
skuba build-package
,skuba node
norskuba start
. -
Bundling and minification are not supported.