Declaration Files (dts)

TypeScript declaration file generation and isolated declarations in Packem

Declaration Files (dts)

Packem provides comprehensive TypeScript declaration file generation with support for both traditional and isolated declarations. Declaration files (.d.ts) are essential for TypeScript libraries to provide type information to consumers.

Automatic Detection

Packem automatically enables declaration file generation when it detects TypeScript usage in your project:

  • TypeScript files (.ts, .tsx) in your source code
  • types or typings field in your package.json
  • tsconfig.json file in your project
{
  "name": "my-library",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs"
    }
  }
}

Configuration

The declaration option is a boolean or one of the string modes "compatible" / "node16":

ValueBehaviour
"compatible"Generates dist/index.d.mts, dist/index.d.cts and dist/index.d.ts.
trueEquivalent to "compatible".
"node16"Generates dist/index.d.mts and dist/index.d.cts only.
falseDisables declaration generation.
undefinedAuto-detect from package.json: if a types field is present → "compatible", otherwise false. (default)

Enable/Disable Declaration Generation

import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
  // Explicitly enable declaration generation ("compatible")
  declaration: true,
})

To restrict output to the dual .d.mts / .d.cts files only, use "node16":

import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
  declaration: 'node16',
})

Per-format Declaration Control

If you need to emit only one declaration flavour without triggering the matching JavaScript build, use declarationCjs and declarationEsm:

import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
  // Generate .d.cts without producing a CJS JS bundle
  declarationCjs: true,
  // Generate .d.mts without producing an ESM JS bundle
  declarationEsm: true,
})

Isolated Declarations

Isolated declarations are a TypeScript 5.5+ feature that dramatically improves declaration generation performance by avoiding expensive type inference. Packem's DTS pipeline (powered by @visulima/rollup-plugin-dts) detects isolated declarations automatically and uses the fast oxc-based generator when available, falling back to tsc otherwise.

Isolated declarations can be 10-100x faster than traditional declaration generation for large projects.

Enable Isolated Declarations

Add "isolatedDeclarations": true to your tsconfig.json. Packem picks it up automatically — there is no Packem-level option for it.

// tsconfig.json
{
  "compilerOptions": {
    "isolatedDeclarations": true
  }
}

Your packem.config.ts stays minimal:

import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
})

Isolated Declarations Requirements

For isolated declarations to work, your TypeScript code must follow certain patterns:

// ✅ Good - Explicit return types
export function greet(name: string): string {
  return `Hello, ${name}!`
}

// ✅ Good - Explicit interface
export interface User {
  id: number
  name: string
}

// ❌ Bad - Inferred return type
export function greet(name: string) {
  return `Hello, ${name}!`
}

// ❌ Bad - Inferred type
export const config = {
  apiUrl: 'https://api.example.com'
}

// ✅ Good - Explicit type
export const config: { apiUrl: string } = {
  apiUrl: 'https://api.example.com'
}

Isolated declarations require explicit type annotations on all exported declarations. This is a TypeScript compiler requirement, not a Packem limitation.

JSDoc comments: The oxc-based generator does not preserve JSDoc comments. If your library's API documentation relies on JSDoc in .d.ts output, turn off isolatedDeclarations in tsconfig so Packem falls back to tsc (which preserves comments when removeComments is not true).

Declaration File Formats

Packem generates declaration files that match your output formats:

ESM/CJS Dual Package

{
  "exports": {
    ".": {
      "import": {
        "types": "./dist/index.d.mts",
        "default": "./dist/index.mjs"
      },
      "require": {
        "types": "./dist/index.d.cts",
        "default": "./dist/index.cjs"
      }
    }
  }
}

Packem will generate:

  • dist/index.d.mts - ESM declaration file
  • dist/index.d.cts - CJS declaration file

Legacy Node.js Compatibility

import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
  rollup: {
    node10Compatibility: {
      typeScriptVersion: '>=5.0',
      writeToPackageJson: true
    }
  }
})

This generates typesVersions in your package.json for Node.js 10 compatibility:

{
  "types": "./dist/index.d.ts",
  "typesVersions": {
    ">=5.0": {
      "*": ["./dist/index.d.ts"]
    }
  }
}

Include/Exclude Filters

Control which source files have .d.ts declarations generated using include and exclude patterns. This is useful when you want to skip declaration generation for specific files (e.g., test utilities, internal helpers).

import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
  declaration: true,

  rollup: {
    dts: {
      // Only generate declarations for files in src/
      include: ['src/**/*.ts'],
      // Skip test files and internal modules
      exclude: ['src/**/*.test.ts', 'src/internal/**']
    }
  }
})

Patterns accept minimatch glob strings, regular expressions, or arrays of either. When neither include nor exclude is specified, all source files are processed.

Inlining Types from External Packages

Packem automatically inlines types from optional peer dependencies and optional dependencies into your .d.ts output, while keeping them external for JS. This prevents TS2307 errors for consumers who don't install all your optional peers.

Automatic Behavior

Types are automatically inlined for:

  • Packages in optionalDependencies
  • Packages in peerDependencies that are marked as optional in peerDependenciesMeta
{
  "peerDependencies": {
    "webpack": "^5.0.0",
    "vite": "^5.0.0",
    "react": "^18.0.0"
  },
  "peerDependenciesMeta": {
    "webpack": { "optional": true },
    "vite": { "optional": true }
  }
}

With this config, types from webpack and vite are automatically inlined in .d.ts output (since they're optional), while react stays external (since it's a required peer).

Manual Configuration

Use rollup.dts.resolve to add extra packages or override the automatic behavior:

import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
  rollup: {
    dts: {
      // Add extra packages on top of auto-detected ones
      resolve: ['some-extra-package', /^@farmfe\//]
    }
  }
})

Options:

  • true — inline types from all node_modules
  • false — disable auto-resolution, keep all dependencies external in .d.ts
  • (string | RegExp)[] — merged with auto-detected optional peers/deps

Multiple Entry Points

Generate declarations for multiple entry points:

import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
  entry: {
    index: 'src/index.ts',
    utils: 'src/utils.ts',
    cli: 'src/cli.ts'
  },

  declaration: true
})

This generates:

  • dist/index.d.ts
  • dist/utils.d.ts
  • dist/cli.d.ts

Declaration Maps

Declaration maps let IDEs navigate from compiled declaration files back to the original TypeScript source. They are driven by the TypeScript declarationMap compiler option — enable it in your tsconfig.json:

// tsconfig.json
{
  "compilerOptions": {
    "declaration": true,
    "declarationMap": true
  }
}
import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
  declaration: true
})

Path Mapping

Resolve TypeScript path mappings in declarations:

// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@utils/*": ["src/utils/*"]
    }
  }
}
// src/index.ts
import { helper } from '@utils/helper'
import { Component } from '@/components/Component'

Packem automatically resolves these paths in the generated declarations.

Validation

Validate your declaration files with external tools:

import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
  declaration: true,

  hooks: {
    'build:done': async () => {
      // Run attw (Are The Types Wrong?)
      await import('@arethetypeswrong/core').then(({ analyze }) => {
        return analyze('./package.json')
      })
    }
  }
})

Performance Optimization

Use Isolated Declarations

For maximum performance, enable isolated declarations in your tsconfig.json. Packem uses the oxc-based fast path automatically:

// tsconfig.json
{
  "compilerOptions": {
    "isolatedDeclarations": true
  }
}
import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
  declaration: true
})

Incremental Compilation

Enable TypeScript incremental compilation:

// tsconfig.json
{
  "compilerOptions": {
    "incremental": true,
    "tsBuildInfoFile": ".tsbuildinfo"
  }
}

Troubleshooting

Declaration Generation Fails

  1. Check TypeScript version: Ensure you're using TypeScript 4.5 or later
  2. Verify tsconfig.json: Ensure your TypeScript configuration is valid
  3. Check for type errors: Fix any TypeScript compilation errors
  4. Use explicit types: For isolated declarations, ensure all exports have explicit types

Missing Types

// Add explicit return type
export function myFunction(): string {
  return 'hello'
}

// Add explicit interface
export interface MyConfig {
  apiUrl: string
}

Path Resolution Issues

Ensure your tsconfig.json paths are correctly configured:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": ["src/**/*"]
}

Missing JSDoc Comments in Declaration Files

If JSDoc comments are missing from your generated .d.ts files:

  1. Check removeComments: Ensure removeComments is not true in your tsconfig.json. TypeScript strips all comments (including JSDoc) from declaration output when this is enabled.
  2. Isolated declarations strip comments: The oxc-based fast path used when isolatedDeclarations: true is set does not preserve JSDoc. Turn off isolatedDeclarations in tsconfig to fall back to tsc, which preserves comments.
// tsconfig.json
{
  "compilerOptions": {
    "removeComments": false
  }
}

Performance Issues

  1. Use isolated declarations for large projects
  2. Enable incremental compilation in TypeScript
  3. Exclude test files from declaration generation using rollup.dts.exclude

Examples

Basic Library

import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
  declaration: true
})

React Component Library

import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,

  declaration: true,

  externals: ['react', 'react-dom'],

  rollup: {
    esbuild: {
      jsx: 'automatic',
      jsxImportSource: 'react'
    }
  }
})

High-Performance Library

Enable isolated declarations in tsconfig.json and let Packem use the fast oxc-based generator:

// tsconfig.json
{
  "compilerOptions": {
    "isolatedDeclarations": true
  }
}
import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
  declaration: true
})

Known Limitations

Isolated Declarations (oxc fast path)

  • JSDoc comments are not preserved. The oxc-based generator strips all comments. Turn off isolatedDeclarations in tsconfig (to fall back to tsc) if your library relies on JSDoc in .d.ts files.
  • Triple-slash directives are not preserved. /// <reference types="..." /> directives in source files are stripped by oxc. They are preserved when using the dtsInput mode with pre-generated .d.ts files.

TypeScript Compiler (tsc)

  • JSDoc comments are stripped when removeComments: true in your tsconfig.json. Ensure this is set to false if you want JSDoc comments in your declaration output.
  • Triple-slash directives are not always emitted. TypeScript only includes /// <reference> directives in .d.ts output when it determines they are necessary for the output types. Source-level directives may be omitted.

DTS Bundling

  • Namespace imports for complex types. When inlining types from external packages via rollup.dts.resolve, packages with complex type dependencies may produce namespace imports (import * as pkg from 'pkg') in the output rather than fully inlining all type definitions.
  • @babel/generator is used for code generation. Any upstream Babel bugs with TypeScript AST nodes may affect the bundled .d.ts output.

Support

Contribute to our work and keep us going

Community is the heart of open source. The success of our packages wouldn't be possible without the incredible contributions of users, testers, and developers who collaborate with us every day.Want to get involved? Here are some tips on how you can make a meaningful impact on our open source projects.

Ready to help us out?

Be sure to check out the package's contribution guidelines first. They'll walk you through the process on how to properly submit an issue or pull request to our repositories.

Submit a pull request

Found something to improve? Fork the repo, make your changes, and open a PR. We review every contribution and provide feedback to help you get merged.

Good first issues

Simple issues suited for people new to open source development, and often a good place to start working on a package.
View good first issues