Dunqing/typack

A native TypeScript declaration (.d.ts) bundler built on Oxc

TypeScript

55

39 commits

updated Jun 17, 2026

See the code

README

typack logo

typack

Crates.io npm docs.rs CI CodSpeed License: MIT

ty(pe) + pack — pack your TypeScript declarations.

A native TypeScript declaration (.d.ts) bundler built on Oxc.

Bundles one or more .d.ts (or .ts) entry points into a single output file using a three-stage AST pipeline — no TypeScript compiler required.

Try it in the REPL →

Features

  • Fast — native Rust implementation powered by the Oxc parser, no tsc dependency
  • Correct — semantic rename using SymbolId-level analysis to avoid name collisions across modules
  • Tree-shaking — only emits declarations that are reachable from entry points
  • Source maps — optional source map composition back to original .d.ts sources
  • External packages — configurable external specifiers preserved as imports in the output
  • CJS support — optional export = syntax for CommonJS default exports
  • .ts entry points — automatically generates declarations via Oxc's IsolatedDeclarations transform (requires isolatedDeclarations in tsconfig)
  • Node.js bindings — pre-built N-API bindings for 8 platforms via npm

How it works

The bundler runs a three-stage pipeline:

  1. Scan — parse .d.ts files (or .ts files via IsolatedDeclarations) with oxc_parser, build semantic scoping via oxc_semantic, resolve imports with oxc_resolver, and produce a topologically sorted module graph
  2. Link — analyze the module graph to build a rename plan (deconflicting names across modules) and a needed-names plan (tree-shaking unused declarations)
  3. Generate — apply renames, rewrite inline import() types, wrap namespace imports, filter unused declarations, and emit the bundled output with optional source maps

.ts entry points

In addition to .d.ts files, typack accepts .ts entry points directly. When a .ts file is encountered (as an entry or as a dependency), it is transformed to .d.ts in-memory using Oxc's IsolatedDeclarations transform before bundling.

This means your .ts source must conform to TypeScript's isolatedDeclarations constraints — primarily, all exported functions and variables need explicit return/type annotations.

typack reads tsconfig.json to pick up two relevant settings. By default it walks up from cwd to find the nearest tsconfig.json, or you can pass an explicit path via --tsconfig (CLI) or the tsconfig option (API):

tsconfig fieldEffect
isolatedDeclarationsSuppresses a warning when .ts files are used without this enabled
stripInternalExcludes declarations marked with /** @internal */ from the output

A warning is emitted if .ts files are used but isolatedDeclarations is not enabled in tsconfig. The transform still runs — any errors from non-conforming code will surface naturally.

Rust

Install

[dependencies]
typack = "0.1.0"

Usage

use typack::{TypackBundler, TypackOptions};

let result = TypackBundler::bundle(&TypackOptions {
    input: vec!["types/index.d.ts".to_string()],
    cwd: std::env::current_dir().unwrap(),
    external: vec!["react".to_string()],
    sourcemap: true,
    ..Default::default()
});

match result {
    Ok(bundle) => {
        for output in &bundle.output {
            println!("{}", output.code);
            if let Some(map) = &output.map {
                // write source map to disk
                let _ = map;
            }
        }
        for warning in &bundle.warnings {
            eprintln!("warning: {warning}");
        }
    }
    Err(diagnostics) => {
        for diagnostic in diagnostics {
            eprintln!("error: {diagnostic}");
        }
    }
}

API

TypackOptions

FieldTypeDefaultDescription
inputVec<String>[]Entry .d.ts or .ts file paths to bundle
externalVec<String>[]Module specifiers to keep as external imports
cwdPathBuf"."Working directory for relative path resolution
sourcemapboolfalseGenerate source map (.d.ts.map)
cjs_defaultboolfalseEmit export = for single default export
tsconfigOption<PathBuf>NonePath to tsconfig.json (default: auto-detect)

BundleResult

FieldTypeDescription
outputVec<BundleOutput>Per-entry bundled output
warningsVec<OxcDiagnostic>Non-fatal warnings

BundleOutput

FieldTypeDescription
codeStringThe bundled .d.ts output
mapOption<SourceMap>Source map (when sourcemap: true)

CLI

Requires the cli feature:

cargo run --features cli -- [OPTIONS] <ENTRY>...

Options

<ENTRY>...                Entry .d.ts or .ts files to bundle
--external <SPEC>         Module specifiers to keep external (repeatable)
--cwd <DIR>               Working directory (default: current directory)
--sourcemap               Generate source map (.d.ts.map)
--cjs-default             Emit `export =` for single default export
-o, --outfile <PATH>      Write output to file instead of stdout (single entry only)
--outdir <DIR>            Write outputs to directory (one per entry)
--tsconfig <PATH>         Path to tsconfig.json (default: auto-detect from cwd)

Example

# Bundle .d.ts entry
cargo run --features cli -- \
  --external react \
  --external react-dom \
  --sourcemap \
  -o dist/index.d.ts \
  types/index.d.ts

# Bundle .ts entry (requires isolatedDeclarations in tsconfig.json)
cargo run --features cli -- \
  -o dist/index.d.ts \
  src/index.ts

Node.js

Install

npm install typack

Requires Node.js >= 20. Pre-built binaries are available for:

PlatformArchitectures
macOSaarch64, x86_64
Linux (glibc)aarch64, x86_64
Linux (musl)aarch64, x86_64
Windowsaarch64, x86_64

Usage

import { bundle } from "typack";

const result = bundle({
  input: ["types/index.d.ts"], // or ["src/index.ts"] for .ts entry
  cwd: process.cwd(),
  external: ["react"],
  sourcemap: true,
});

for (const output of result.output) {
  console.log(output.code);
  if (output.map) {
    // output.map is a JSON string
    fs.writeFileSync("dist/index.d.ts.map", output.map);
  }
}

for (const warning of result.warnings) {
  console.warn(`warning: ${warning.message}`);
}

TypeScript types

interface BundleDtsOptions {
  /** Entry `.d.ts` or `.ts` file paths to bundle */
  input: Array<string>;
  external?: Array<string>;
  cwd?: string;
  sourcemap?: boolean;
  cjsDefault?: boolean;
  tsconfig?: string;
}

interface BundleDtsOutput {
  code: string;
  map?: string;
}

interface BundleDtsResult {
  output: Array<BundleDtsOutput>;
  warnings: Array<BundleDtsDiagnostic>;
}

interface BundleDtsDiagnostic {
  message: string;
  file?: string;
  span?: Array<number>;
  severity: string;
}

Resolution

Module resolution uses oxc_resolver with the "types" export condition enabled, respecting package.json exports and conditional exports.

  • External specifiers — specifiers in the external list are preserved in the output (exact match)
  • Unresolved relative specifiers — fatal errors
  • Unresolved bare specifiers — automatically externalized with a warning
  • Side-effect imports — import "pkg" statements are preserved when the target contains global or module augmentations
  • Non-TS assets — imports of CSS, images, etc. are silently skipped
  • Reference directives — /// <reference path="..." /> directives are collected and deduplicated

Development

Prerequisites

  • Rust 1.91.0+
  • just (task runner)

Commands

just ready    # fmt + check + test + lint
just fmt      # cargo fmt
just check    # cargo check
just test     # cargo test
just lint     # cargo clippy

Tests

  • Fixture tests — small focused cases in tests/fixtures/, each with an index.d.ts input and snapshot.d.ts expected output
  • Real-world tests — regression tests against declarations from Rolldown, Vue, and Vitest
  • Semantic tests — targeted tests for rename deconfliction and tree-shaking logic

Credits

The three-stage architecture (scan, link, generate) is inspired by Rolldown. Test fixtures were adapted from rolldown-plugin-dts and rollup-plugin-dts.

License

MIT

Dunqing/typack

A native TypeScript declaration (.d.ts) bundler built on Oxc

TypeScript

55

39 commits

updated Jun 17, 2026

See the code

README

typack logo

typack

Crates.io npm docs.rs CI CodSpeed License: MIT

ty(pe) + pack — pack your TypeScript declarations.

A native TypeScript declaration (.d.ts) bundler built on Oxc.

Bundles one or more .d.ts (or .ts) entry points into a single output file using a three-stage AST pipeline — no TypeScript compiler required.

Try it in the REPL →

Features

  • Fast — native Rust implementation powered by the Oxc parser, no tsc dependency
  • Correct — semantic rename using SymbolId-level analysis to avoid name collisions across modules
  • Tree-shaking — only emits declarations that are reachable from entry points
  • Source maps — optional source map composition back to original .d.ts sources
  • External packages — configurable external specifiers preserved as imports in the output
  • CJS support — optional export = syntax for CommonJS default exports
  • .ts entry points — automatically generates declarations via Oxc's IsolatedDeclarations transform (requires isolatedDeclarations in tsconfig)
  • Node.js bindings — pre-built N-API bindings for 8 platforms via npm

How it works

The bundler runs a three-stage pipeline:

  1. Scan — parse .d.ts files (or .ts files via IsolatedDeclarations) with oxc_parser, build semantic scoping via oxc_semantic, resolve imports with oxc_resolver, and produce a topologically sorted module graph
  2. Link — analyze the module graph to build a rename plan (deconflicting names across modules) and a needed-names plan (tree-shaking unused declarations)
  3. Generate — apply renames, rewrite inline import() types, wrap namespace imports, filter unused declarations, and emit the bundled output with optional source maps

.ts entry points

In addition to .d.ts files, typack accepts .ts entry points directly. When a .ts file is encountered (as an entry or as a dependency), it is transformed to .d.ts in-memory using Oxc's IsolatedDeclarations transform before bundling.

This means your .ts source must conform to TypeScript's isolatedDeclarations constraints — primarily, all exported functions and variables need explicit return/type annotations.

typack reads tsconfig.json to pick up two relevant settings. By default it walks up from cwd to find the nearest tsconfig.json, or you can pass an explicit path via --tsconfig (CLI) or the tsconfig option (API):

tsconfig fieldEffect
isolatedDeclarationsSuppresses a warning when .ts files are used without this enabled
stripInternalExcludes declarations marked with /** @internal */ from the output

A warning is emitted if .ts files are used but isolatedDeclarations is not enabled in tsconfig. The transform still runs — any errors from non-conforming code will surface naturally.

Rust

Install

[dependencies]
typack = "0.1.0"

Usage

use typack::{TypackBundler, TypackOptions};

let result = TypackBundler::bundle(&TypackOptions {
    input: vec!["types/index.d.ts".to_string()],
    cwd: std::env::current_dir().unwrap(),
    external: vec!["react".to_string()],
    sourcemap: true,
    ..Default::default()
});

match result {
    Ok(bundle) => {
        for output in &bundle.output {
            println!("{}", output.code);
            if let Some(map) = &output.map {
                // write source map to disk
                let _ = map;
            }
        }
        for warning in &bundle.warnings {
            eprintln!("warning: {warning}");
        }
    }
    Err(diagnostics) => {
        for diagnostic in diagnostics {
            eprintln!("error: {diagnostic}");
        }
    }
}

API

TypackOptions

FieldTypeDefaultDescription
inputVec<String>[]Entry .d.ts or .ts file paths to bundle
externalVec<String>[]Module specifiers to keep as external imports
cwdPathBuf"."Working directory for relative path resolution
sourcemapboolfalseGenerate source map (.d.ts.map)
cjs_defaultboolfalseEmit export = for single default export
tsconfigOption<PathBuf>NonePath to tsconfig.json (default: auto-detect)

BundleResult

FieldTypeDescription
outputVec<BundleOutput>Per-entry bundled output
warningsVec<OxcDiagnostic>Non-fatal warnings

BundleOutput

FieldTypeDescription
codeStringThe bundled .d.ts output
mapOption<SourceMap>Source map (when sourcemap: true)

CLI

Requires the cli feature:

cargo run --features cli -- [OPTIONS] <ENTRY>...

Options

<ENTRY>...                Entry .d.ts or .ts files to bundle
--external <SPEC>         Module specifiers to keep external (repeatable)
--cwd <DIR>               Working directory (default: current directory)
--sourcemap               Generate source map (.d.ts.map)
--cjs-default             Emit `export =` for single default export
-o, --outfile <PATH>      Write output to file instead of stdout (single entry only)
--outdir <DIR>            Write outputs to directory (one per entry)
--tsconfig <PATH>         Path to tsconfig.json (default: auto-detect from cwd)

Example

# Bundle .d.ts entry
cargo run --features cli -- \
  --external react \
  --external react-dom \
  --sourcemap \
  -o dist/index.d.ts \
  types/index.d.ts

# Bundle .ts entry (requires isolatedDeclarations in tsconfig.json)
cargo run --features cli -- \
  -o dist/index.d.ts \
  src/index.ts

Node.js

Install

npm install typack

Requires Node.js >= 20. Pre-built binaries are available for:

PlatformArchitectures
macOSaarch64, x86_64
Linux (glibc)aarch64, x86_64
Linux (musl)aarch64, x86_64
Windowsaarch64, x86_64

Usage

import { bundle } from "typack";

const result = bundle({
  input: ["types/index.d.ts"], // or ["src/index.ts"] for .ts entry
  cwd: process.cwd(),
  external: ["react"],
  sourcemap: true,
});

for (const output of result.output) {
  console.log(output.code);
  if (output.map) {
    // output.map is a JSON string
    fs.writeFileSync("dist/index.d.ts.map", output.map);
  }
}

for (const warning of result.warnings) {
  console.warn(`warning: ${warning.message}`);
}

TypeScript types

interface BundleDtsOptions {
  /** Entry `.d.ts` or `.ts` file paths to bundle */
  input: Array<string>;
  external?: Array<string>;
  cwd?: string;
  sourcemap?: boolean;
  cjsDefault?: boolean;
  tsconfig?: string;
}

interface BundleDtsOutput {
  code: string;
  map?: string;
}

interface BundleDtsResult {
  output: Array<BundleDtsOutput>;
  warnings: Array<BundleDtsDiagnostic>;
}

interface BundleDtsDiagnostic {
  message: string;
  file?: string;
  span?: Array<number>;
  severity: string;
}

Resolution

Module resolution uses oxc_resolver with the "types" export condition enabled, respecting package.json exports and conditional exports.

  • External specifiers — specifiers in the external list are preserved in the output (exact match)
  • Unresolved relative specifiers — fatal errors
  • Unresolved bare specifiers — automatically externalized with a warning
  • Side-effect imports — import "pkg" statements are preserved when the target contains global or module augmentations
  • Non-TS assets — imports of CSS, images, etc. are silently skipped
  • Reference directives — /// <reference path="..." /> directives are collected and deduplicated

Development

Prerequisites

  • Rust 1.91.0+
  • just (task runner)

Commands

just ready    # fmt + check + test + lint
just fmt      # cargo fmt
just check    # cargo check
just test     # cargo test
just lint     # cargo clippy

Tests

  • Fixture tests — small focused cases in tests/fixtures/, each with an index.d.ts input and snapshot.d.ts expected output
  • Real-world tests — regression tests against declarations from Rolldown, Vue, and Vitest
  • Semantic tests — targeted tests for rename deconfliction and tree-shaking logic

Credits

The three-stage architecture (scan, link, generate) is inspired by Rolldown. Test fixtures were adapted from rolldown-plugin-dts and rollup-plugin-dts.

License

MIT

Project health

Latest release

0.3.0 · Mar 13, 2026

Releases this year

5

Significant stargazers

Kevin Deng

4,839 followers · starred Mar 2026

MK (fengmk2)

8,408 followers · starred Mar 2026

raf

912 followers · starred Mar 2026

Jiahan Chen

1,860 followers · starred Mar 2026