Declaration Files

10 questions found

What is a .d.ts file and what is it used for?

Beginner
A declaration file contains only type information — interfaces, type aliases, and function/variable signatures — with no runtime implementation code. It lets TypeScript type-check code that uses a JavaScript library, or lets consumers of your own package get type checking and autocomplete.
// mathUtils.d.ts
export function add(a: number, b: number): number;
export const PI: number;
Real-world example Providing type information for a plain JavaScript library so TypeScript users get autocomplete and type errors when using it.

Common follow-ups: Do .d.ts files get compiled into any JavaScript output?

Modules

How do you get type definitions for a popular JavaScript library that doesn't ship its own types?

Beginner
Install the corresponding community-maintained package from DefinitelyTyped, prefixed with @types/ — TypeScript automatically discovers and uses these type packages from node_modules without any extra configuration.
npm install lodash
npm install --save-dev @types/lodash
Real-world example Getting full type safety and autocomplete for a library like lodash or express that's written in plain JavaScript.

Common follow-ups: How does TypeScript know to look in @types packages automatically?

tsconfig & Compiler Options

How does the 'declare' keyword work, and what does it promise the compiler?

Intermediate
declare tells TypeScript 'trust me, this exists at runtime somewhere else' — it describes a type shape without providing (or requiring) any implementation, and is erased entirely from compiled output. It's the core keyword used throughout .d.ts files and for describing global variables injected by other scripts.
declare const APP_VERSION: string; // exists globally at runtime, injected by a build tool
console.log(APP_VERSION); // type-checks fine, no import needed
Real-world example Describing a global variable injected by a bundler's DefinePlugin, or a third-party script tag that attaches to 'window'.

Common follow-ups: What happens if you try to 'declare' something and then also try to assign it a value?

Modules

How do you write a declaration file for a library that attaches itself to the global 'window' object?

Intermediate
Use a 'declare global' block (inside a module file) or a plain ambient declaration (in a non-module .d.ts) to extend the global scope's type, describing the shape the library adds to window at runtime.
// jquery-global.d.ts
declare global {
  interface Window {
    $: (selector: string) => any;
  }
}
export {};  // marks this file as a module

// usage.ts
window.$('#app'); // now type-checks
Real-world example Adding types for a legacy jQuery or analytics script loaded via a <script> tag rather than an npm import.

Common follow-ups: Why does adding 'export {}' at the end matter for how this file is treated?

Modules

How do you type a CommonJS module using 'export =' and 'import ... = require(...)'?

Intermediate
'export =' in a .d.ts describes a module whose entire export is a single value (common in older CommonJS libraries), rather than a set of named exports — consumers then use 'import x = require("module")' syntax (or esModuleInterop-based default import) to bring it in correctly.
// legacy-lib.d.ts
declare function legacyLib(config: object): void;
export = legacyLib;

// usage.ts
import legacyLib = require('legacy-lib');
Real-world example Typing an older CommonJS npm package whose module.exports is a single function rather than an object of named exports.

Common follow-ups: How does the esModuleInterop compiler option change how you can import an 'export =' module?

Modules

What is module augmentation and how do you use it to add a method to an existing library's types?

Advanced
Module augmentation lets you re-open and extend an already-declared module's types from a separate file, by writing `declare module 'existing-module' { ... }` and adding new members — commonly used to add custom properties that a plugin injects onto a library's base types.
// express-augment.d.ts
import 'express';
declare module 'express' {
  interface Request {
    userId?: string; // added by an auth middleware
  }
}

// usage.ts
app.get('/', (req) => {
  console.log(req.userId); // now recognized
});
Real-world example Adding a custom 'userId' property that an authentication middleware attaches to Express's Request object.

Common follow-ups: Why must you 'import' the original module in the augmentation file for this to work correctly?

Namespaces & Declaration Merging

How does TypeScript resolve which .d.ts file to use for a given package, and what does the 'types'/'typings' field in package.json control?

Advanced
TypeScript checks a package's package.json for a 'types' (or legacy 'typings') field pointing to its bundled declaration file; if absent, it falls back to looking for an @types/<package-name> package in node_modules, and finally to an implicit 'any' type if neither exists (unless noImplicitAny blocks that).
// package.json of a library
{
  "name": "my-lib",
  "main": "dist/index.js",
  "types": "dist/index.d.ts"
}
Real-world example Publishing your own npm package with bundled types so consumers get type checking without a separate @types install.

Common follow-ups: What happens if a package has NEITHER a 'types' field NOR a matching @types package available?

tsconfig & Compiler Options

How do you write a declaration file for a library with multiple overloaded call signatures, like jQuery's $()?

Advanced
List multiple function signatures back-to-back with the same name (no implementation) inside the declaration file — TypeScript treats them as overloads and picks the most specific matching signature based on the caller's argument types.
declare function $(selector: string): HTMLElement[];
declare function $(callback: () => void): void;

$('#app');           // resolves to the string overload
$(() => console.log('ready')); // resolves to the callback overload
Real-world example Accurately describing a flexible, multi-shape JavaScript API in a hand-written declaration file for a legacy library.

Common follow-ups: How does overload resolution differ when writing overloads in a declaration file versus in a regular implementation function?

Function Types Overloads & Optional/Default Parameters

What is 'triple-slash directive' syntax and when is it still necessary in modern TypeScript?

Advanced
Triple-slash directives like `/// <reference types="node" />` at the top of a file are an older mechanism for explicitly pulling in ambient type declarations, mostly superseded by automatic node_modules/@types resolution — still occasionally needed in standalone .d.ts files or when writing global scripts outside a normal module graph.
/// <reference types="node" />
declare const process: NodeJS.Process; // relies on @types/node's ambient types
Real-world example Explicitly including Node.js global types in a script-mode .d.ts file that isn't part of the normal module resolution graph.

Common follow-ups: Why is this directive rarely needed for regular application code that imports @types packages normally?

tsconfig & Compiler Options

How do 'declaration' and 'declarationMap' compiler options in tsconfig.json affect a published npm package?

Advanced
Setting "declaration": true generates a corresponding .d.ts file for every compiled .ts file automatically, giving consumers of your published package full type information without hand-writing declarations. "declarationMap": true additionally generates source maps for those .d.ts files, letting editors 'Go to Definition' jump straight into your original TypeScript source instead of the generated declaration file.
// tsconfig.json
{
  "compilerOptions": {
    "declaration": true,
    "declarationMap": true,
    "outDir": "dist"
  }
}
Real-world example Publishing an npm library where consumers get full type support and can jump directly to your original source for definitions.

Common follow-ups: Why would you want declarationMap disabled in a final production package release?

tsconfig & Compiler Options