I care about details and perfection in user interfaces. I love writing low-level code and have a passion for mathematics.

Speeding up TypeScript Library Development - Isolated Declarations

Building TypeScript libraries in 2024 has been unnecessarily painful. You write your code, run the build, and wait. Sometimes seconds, sometimes minutes. For large projects, generating declaration files can take an eternity. Your development flow stops while tsc analyzes your entire codebase, inferring types across every file and dependency.

This is about to change.

The Declaration Generation Problem

Traditional TypeScript declaration generation is architecturally expensive. When you build a library, TypeScript must traverse your entire dependency graph, analyzing and inferring types across all files. Every function's return type needs to be computed, every exported value needs its type determined, and all of this happens sequentially.

export function processData(input: string) {
  const parsed = JSON.parse(input);
  const transformed = transformInternal(parsed);
  return mapToOutput(transformed);
}

To generate the declaration file, TypeScript must analyze transformInternal and mapToOutput, inferring the return type by following the whole call chain. This makes declaration generation slow, 30+ seconds for medium libraries, and minutes for monorepos.

Enter Isolated Declarations

TypeScript 5.5 introduced a game-changing feature: isolatedDeclarations. This single compiler option fundamentally changes how declaration files are generated.

With isolated declarations, each file can be processed independently and in parallel. No more analyzing the entire codebase. No more complex type inference. Declaration generation becomes a simple syntax-stripping operation that takes milliseconds instead of minutes.

// With isolated declarations - explicit return type
export function processData(input: string): ProcessedData {
  const parsed = JSON.parse(input);
  const transformed = transformInternal(parsed);
  return mapToOutput(transformed);
}

The key difference? You add explicit type annotations to your public exports. Internal code remains unchanged. Tools can now generate declaration files by simply reading the type annotations, without analyzing any implementation details.

The Speed Difference

The performance gains are not incremental. They're transformative.

Without isolated declarations:

bash
$ tsc --declaration
 Type checking completed in 2.3s
 Declaration files generated in 28.4s

With isolated declarations enabled:

bash
$ bunup src/index.ts
 Build completed in 37ms

We're talking about going from 30 seconds to under 50 milliseconds. That's not a 10x improvement. It's a 600x improvement.

This isn't just about saving time; instant builds fundamentally change how you work, enabling faster iteration and a more enjoyable development experience.

Enabling Isolated Declarations

Adding isolated declarations to your project is straightforward. Update your tsconfig.json:

{
  "compilerOptions": {
    "declaration": true,
    "isolatedDeclarations": true
  }
}

When you enable isolated declarations, TypeScript will immediately show you where explicit types are needed on public exports only, not on internal code.

For example:

// TypeScript error: Function must have an explicit return type annotation
export function createUser(name: string) {
  return insertUser(name)
}

Add the explicit return type:

export function createUser(name: string): User {
  return insertUser(name)
}

It's like having a guardian watching over your library's public surface.

In most codebases, this means adding return types to a handful of exported functions. Internal code doesn't need changes.

TypeScript also provides quick fixes to add return types automatically, or you can add them manually. Either way, it's effortless.

Clear and Predictable Public APIs

When you add explicit types to your exports, you're doing more than just enabling TypeScript's isolated declarations or improving build speed. These explicit types become living documentation for your library's consumers. Instead of relying on TypeScript's inference, which can produce complex or unexpected types, you explicitly define your public API. The public surface of your library and the type definitions that users see are exactly what you intended and defined, always reliable and predictable, not what TypeScript infers at build time. The result is clear, predictable, and clean.

Why This Matters Now

The JavaScript ecosystem is at an inflection point. Build tools written in Rust, Zig and Go are replacing traditional JavaScript-based tooling. These tools are orders of magnitude faster, but they can't run TypeScript's type checker. Isolated declarations bridge this gap, allowing next-generation tools to generate perfect TypeScript declarations without invoking tsc.

Modern tools like Bun are adding native TypeScript declaration generation to their bundlers, something previously impossible without isolated declarations.

Enabling isolated declarations today ensures your library is prepared for the future. You'll get faster builds, clearer types for consumers, and seamless compatibility with modern tooling, all with benefits that only grow over time.

The Path Forward

Isolated declarations represent more than performance optimization. They're a philosophical shift toward explicit, predictable public APIs. By clearly defining what you export and what types you expose, you create better boundaries between your library's internals and its public interface.

Start with isolated declarations enabled from day one on new projects. For existing libraries, the migration is often just a 5-minute change.

The result is a library that builds in milliseconds and is ready for the next generation of JavaScript tooling.


Ready to speed up your TypeScript builds? Tools like Bunup already support isolated declarations out of the box, making it easier than ever to build fast, modern TypeScript libraries.