Features
Fast builds
Dtsbuild is written in Rust and uses Oxc to parse and transform .ts and .d.ts files. With isolated declarations enabled, builds can finish in less than a second.
While Rust-written libraries tend to result in large prebuilt binaries, dtsbuild also aims to keep the binary size as small as possible, sacrificing slight speed impact where practical.
Why not Go?
With TypeScript being rewritten in Go, it possibly makes sense to write the bundler in Go as well to easily interact with the compiler. But dtsbuild is written in Rust because:
- The project was already half done when the rewrite was announced.
- There's a lack of TypeScript tooling ecosystem in Go for parsing, isolated declarations support, and others.
- I wanted to learn Rust.
TypeScript first
Dtsbuild is written to bundle types directly and intends to support all of TypeScript's syntax and features. This makes it possible to bundle complex types from external libraries.
Features such as ambient modules and namespaces are also properly preserved to retain its intent.
A direct understanding of types also allows it to better support many features below that may not be implemented by JS bundlers.
Smart renames
When type names conflict, dtsbuild renames them to avoid collisions. The rename heuristic is designed to generate human-readable names deriving from existing code where possible. For example:
// other bundlers:
import { UserConfig as UserConfig$1 } from 'vite'
// dtsbuild:
import { UserConfig as ViteUserConfig } from 'vite'The rename heuristic derives from several places:
import { ServerOptions } from 'node:http'
import { UserConfig } from 'vite'
import { Info as CheckerInfo } from './checker.ts'
interface Options {
enabled?: boolean
}
export { Options as LibOptions }Dtsbuild will prioritize local and exported type names over external imported names, resulting in more meaningful API type names. You can also take advantage of these heuristics to ensure types are renamed a certain way.
In the rare case where a smart rename is not possible, the types will be appended 2, 3, and so forth.
Example renames
If any of the types conflict, the types may be renamed to something like below:
ServerOptions->HttpServerOptions,NodeHttpServerOptionsUserConfig->ViteUserConfigInfo->CheckerInfoOptions->LibOptions
Fine-grained bundling
When bundling types from multiple entry points, dtsbuild creates chunks that tracks each type declarations directly instead of per-module. This means in a shared file (e.g. a utility types.ts file), if the type is only used by one entry point, it will be directly placed in the entry point chunk instead of a shared chunk.
As a result, dtsbuild generates smaller and less chunks compared to other bundlers, and also more accurate type renames. For example:
Source code:
export { Foo } from './types.ts'export { Bar } from './types.ts'export type Foo = { foo: string }
export type Bar = { bar: string }Dtsbuild output:
type Foo = { foo: string }
export { Foo }type Bar = { bar: string }
export { Bar }Other bundlers output:
export { Foo } from './chunk-xxx.js'export { Bar } from './chunk-xxx.js'type Foo = { foo: string }
type Bar = { bar: string }
export { Foo, Bar }Isolated declarations
If compilerOptions.isolatedDeclarations is enabled in tsconfig.json (or from the tsconfig option), dtsbuild will use Oxc to generate the declarations internally within Rust instead of invoking tsc and interfacing with JS, which results in faster builds.
{
"compilerOptions": {
"isolatedDeclarations": true
}
}Strip internal types
If compilerOptions.stripInternal is enabled in tsconfig.json (or from the tsconfig option), internal types will be removed from the output including any referenced types that were only used by them.
{
"compilerOptions": {
"stripInternal": true
}
}Dtsbuild also supports additional stripping locations not handled by TypeScript:
export declare function count(num: number, /** @internal */ debug: boolean): number
export type Pixel = string | /** @internal */ numberTsconfig paths
Dtsbuild supports resolving paths specified in the tsconfig.json compilerOptions.paths option. This allows you to use path aliases in your source code.
{
"compilerOptions": {
"paths": {
"@src/*": ["./src/*"],
"local-package": ["../packages/local-package/src/index.ts"]
}
}
}TIP
If the alias path looks like a dependency import, e.g. @src/foo/bar or local-package, the external option will not bundle them by default, which may sometimes be not intended. You can configure bundling them by excluding them from externalizing, e.g. external: ["!@src/**"].