TypeScript 59 ๐ท Declaration Files โ .d.ts
A .d.ts file is a declaration file โ a TypeScript file that contains only type information, no runtime code. It describes the shape of something that exists at runtime: a JavaScript library, a global variable, a module that is written in another language, a build artifact. The compiler reads the file for types and emits nothing from it. The whole output of a build is one or more .js files and one or more .d.ts files, and the .d.ts files are what consumers of a library use for type checking. They are the boundary between the typed world and the untyped one, and knowing how to read, write, and publish them is part of working with TypeScript at any serious level.
Key point: A .d.ts file contains declarations โ interface, type, declare, export, namespace โ but no executable code. It is emitted by the compiler when declaration: true is set, and it can be written by hand for a library that has no types. The declare keyword introduces an ambient declaration: it tells the compiler “this exists at runtime, trust me.” A module’s .d.ts file mirrors the module’s exports, and a global .d.ts file adds to the global scope. The @types packages on npm are collections of .d.ts files for libraries that do not ship their own. The three /// directives โ reference, types, lib โ are the legacy mechanism for including declaration files, and they still appear in generated code.
Why declaration files exist
TypeScript’s types are erased at runtime. When a library is written in TypeScript and compiled to JavaScript, the types are gone. The library’s consumers need the types to check their own code, and the types have to be shipped separately. The .d.ts file is that separate shipment.
The compile output. A TypeScript project produces .js files for the runtime and .d.ts files for the types. The .js files are the code; the .d.ts files are the interface. The two are paired, and a consumer of the library uses the .d.ts files for checking and the .js files for execution.
The declaration flag. The declaration: true option in tsconfig.json tells the compiler to emit the .d.ts files alongside the .js files. The files go to the same output directory, with the same names and the .d.ts extension.
{
"compilerOptions": {
"declaration": true,
"outDir": "./dist"
}
}
A source file src/index.ts produces dist/index.js and dist/index.d.ts.
Why a library ships both. The .js is what runs, and the .d.ts is what the consumer’s compiler reads. Without the .d.ts, the consumer sees any for every import from the library. With it, the consumer gets the types and the checking.
Why a hand-written .d.ts is sometimes needed. A library written in plain JavaScript has no types to emit. The types are written by hand, either by the library’s authors or by the DefinitelyTyped community, and published as a .d.ts file or an @types package. The file declares the library’s shape, and the consumer’s compiler uses it.
Why the .d.ts is not always the source of truth. A hand-written .d.ts describes what the author believes the library does. If the library changes and the .d.ts does not, the types are wrong. The generated .d.ts is always in sync with the source, which is why generating is preferred when possible.
Why the .d.ts is not executed. The file contains no runtime code, and the compiler emits nothing from it. A .d.ts file that contains a function body is an error. The file is a description, not a program.
Why the
.d.tsis the library’s public interface. Theprivatemembers of a class, the internal helpers, and the implementation details are not in the.d.ts. The file contains only what the consumer needs to use the library. The interface is the contract, and the.d.tsis the contract’s text.
The declare keyword
The declare keyword introduces an ambient declaration. It tells the compiler that the name exists at runtime and that the declaration is a description, not an implementation.
declare const VERSION: string;
declare function greet(name: string): string;
declare class Logger {
log(message: string): void;
}
declare namespace MyLib {
function helper(): void;
}
Each declare introduces a name that the compiler accepts as existing. The compiler does not emit anything for the declarations, and it does not check that the runtime provides them. The declaration is a promise that the name exists.
Why declare is needed in a .d.ts. A .d.ts file has no runtime code, so every declaration is ambient. The declare keyword is required for the top-level declarations of variables, functions, classes, and namespaces. An interface or type does not need declare because it has no runtime representation.
Why declare appears inside modules. In a module, a declare statement declares a global or an ambient name that is not exported.
// module.ts
declare const DEBUG: boolean;
export function log(message: string): void {
if (DEBUG) console.log(message);
}
The DEBUG is declared as ambient โ the compiler accepts its use without an import. The runtime provides the variable through the bundler’s define or the environment.
Why declare is a claim. The compiler trusts the declaration. If the runtime does not provide the name, the code fails at runtime with a ReferenceError. The declare is a promise from the developer, and the promise can be wrong.
Why the declare keyword is used for globals. A global variable, a global function, or a global class is declared with declare in a .d.ts file. The declaration describes the global’s shape, and the compiler accepts the use without an import.
Why the declare keyword appears in declare global. The declare global block from the previous chapter uses the declare keyword to introduce the block. The block’s contents are ambient declarations in the global scope.
Why declare is not used for a type alias. A type alias has no runtime representation, so it is not ambient. It is declared without the declare keyword, and it exists only in the type system.
The structure of a .d.ts file
A .d.ts file has two forms: a module declaration file and a global declaration file. The form is determined by whether the file has a top-level import or export.
The module form. A file with a top-level export is a module declaration. Its exports are the module’s public interface.
// index.d.ts
export interface User {
id: string;
name: string;
}
export declare function getUser(id: string): Promise<User>;
export declare class UserService {
constructor(baseUrl: string);
getUser(id: string): Promise<User>;
}
The export on each declaration makes it part of the module’s interface. The consumer imports the names and gets the types. The declare on the function and class is required because the implementations are in the .js file.
The global form. A file without a top-level import or export is a script, and its declarations are in the global scope.
// globals.d.ts
declare const APP_VERSION: string;
declare function log(message: string): void;
interface Window {
myApp: {
version: string;
};
}
The declarations are global, and the file does not export anything. The Window interface is merged with the DOM’s declaration, and the myApp property is added.
Why the export {} makes a difference. A file that has any top-level export is a module, and its declarations are scoped to the file. A file without one is a script, and its declarations are global. The distinction is what determines whether the declarations augment the global scope.
Why a global file needs no declare global. A global .d.ts file is a script, so its declarations are already in the global scope. The declare global block is needed only in a module, which is why the export {} is the marker.
Why the declare keyword is present in the module form. The function and class declarations in the module form need the declare keyword because the implementation is in the .js file. The interface and type declarations do not need it because they have no runtime representation.
Why the index.d.ts is the entry point. The package.json‘s types field points to the .d.ts file that is the package’s type entry point. It is usually dist/index.d.ts, and it re-exports the types from the other files.
{
"name": "my-library",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
}
}
The types field is the classic mechanism, and the exports field’s types condition is the modern one. Both point to the .d.ts file, and the compiler uses it for the package’s types.
Writing a declaration file
A declaration file is written by hand when a library has no types. The process is to describe the library’s shape based on its documentation and its source.
The module form for a library. A library that exports functions and classes is described with the module form.
// my-library.d.ts
export interface Options {
timeout?: number;
retries?: number;
}
export declare function request(url: string, options?: Options): Promise<Response>;
export declare class Client {
constructor(options?: Options);
get(url: string): Promise<Response>;
post(url: string, body: unknown): Promise<Response>;
}
The interfaces are declared without declare because they have no runtime. The functions and classes are declared with declare because they exist at runtime.
The global form for a script. A library that attaches itself to the global is described with the global form.
// my-global-lib.d.ts
declare namespace MyLib {
interface Options {
timeout?: number;
}
function request(url: string, options?: Options): Promise<Response>;
class Client {
constructor(options?: Options);
get(url: string): Promise<Response>;
}
}
The namespace groups the declarations, and the declare makes the namespace ambient. The consumer uses MyLib.request and MyLib.Client.
The module augmentation form. A library that is extended by another library is described with the augmentation.
// my-lib-extension.d.ts
import 'my-lib';
declare module 'my-lib' {
interface Options {
newOption?: string;
}
}
The import 'my-lib' makes the file a module, and the declare module 'my-lib' augments the library’s types with the new option.
Why the hand-written file must match the runtime. The .d.ts describes what the library does. If the description is wrong โ a function takes different arguments, a property does not exist โ the consumer’s code compiles but fails at runtime. The file must be written from the library’s actual behavior, and it must be updated when the library changes.
Why the hand-written file should be minimal. A .d.ts that declares more than the library provides is a lie. A .d.ts that declares less is incomplete but not wrong. The minimal declaration is the safer choice, and the additional declarations can be added when needed.
Why the hand-written file is usually in the @types package. A library that does not ship its own types has an @types package on npm, published by DefinitelyTyped. The package contains the .d.ts files, and the compiler picks it up automatically when the library is imported.
Why the @types package is separate. The library’s authors may not want to maintain the types, and the community maintains them instead. The @types package is versioned separately, and it can be updated when the library changes. The pattern is common for older libraries that predate TypeScript.
Why the @types package is automatic. When a library has an @types package with the same name (or with a scoped name), TypeScript finds it automatically. No import or configuration is needed. The node_modules/@types directory is scanned, and the declarations are loaded.
The triple-slash directives
The triple-slash directives are a legacy mechanism for including declaration files. They are comments that start with /// and contain a directive.
/// <reference path="./other.d.ts" />
/// <reference types="node" />
/// <reference lib="es2020" />
The path directive includes another file. The types directive includes an @types package. The lib directive includes a standard library. The directives must appear at the top of the file, before any other statement.
Why the directives are legacy. The tsconfig.json‘s include and files options replaced the path directive, and the types option replaced the types directive. The modern configuration is preferred, and the directives are used only when the configuration cannot express the inclusion.
Why the types directive still appears. A .d.ts file that depends on a specific @types package uses the types directive to include it. The directive is a way to declare the dependency without relying on the automatic loading, which is useful for a library’s .d.ts that must be self-contained.
Why the lib directive still appears. A .d.ts file that uses a standard library type โ Promise, Map, Symbol โ includes the library with the lib directive. The directive is needed when the tsconfig.json‘s lib option does not include the library, which is rare but happens in some configurations.
Why the path directive is the most legacy. The path directive includes a file by relative path. The tsconfig.json‘s include is the modern replacement, and the path directive is used only in generated code that predates the modern configuration.
Why the directives must come first. The directives are processed before the rest of the file. They must appear at the top, before any import or statement. A directive in the middle of the file is ignored, which is a common mistake.
Why the directives are still emitted. The tsc compiler emits the directives in some configurations, particularly when the declaration option is on and the source uses the directives. The emitted .d.ts may contain the directives, and the consumer’s compiler reads them. The directives are a part of the ecosystem that has not been fully replaced.
Why the directives are worth knowing even if you never write them. They appear in generated
.d.tsfiles, in the@typespackages, and in the older code. Reading a.d.tsthat contains a/// <reference types="..." />requires knowing what the directive means. The modern configuration is preferred, but the directives are part of the landscape.
Publishing types with a library
A library that wants to ship its types must declare them in package.json and include the .d.ts files in the published package.
The types field. The types (or typings) field in package.json points to the .d.ts entry point.
{
"name": "my-library",
"main": "./dist/index.js",
"types": "./dist/index.d.ts"
}
The compiler reads the field and uses the file for the package’s types. Without the field, the compiler looks for index.d.ts in the package’s root, which may or may not be present.
The exports field. The modern form declares the types conditionally.
{
"name": "my-library",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./utils": {
"types": "./dist/utils.d.ts",
"import": "./dist/utils.js"
}
}
}
The types condition in each entry provides the .d.ts file for the subpath. The exports field is the modern mechanism, and it supersedes the types field.
Why the types condition must come first. The conditions are checked in order, and the first match wins. The types condition must be first so that the compiler picks it. If import comes first, the compiler resolves to the .js file and does not find the declarations.
Why the declaration files must be in the published package. The .d.ts files are part of the package’s contents. The files field in package.json must include them, or the npm publish must include them by default. A package that ships the .js but not the .d.ts gives the consumer no types.
{
"files": ["dist"]
}
The dist directory contains both the .js and the .d.ts files, and the field includes both.
Why the source maps matter for the declarations. A .d.ts.map file maps the declaration back to the source, which makes the editor’s “go to definition” work correctly. The declarationMap: true option emits the map files, and the files field should include them.
Why the declaration and declarationMap options are separate. The declaration option emits the .d.ts files, and the declarationMap option emits the .d.ts.map files. The two are separate, and a library that wants the maps enables both.
Why the stripInternal option matters. The stripInternal: true option removes the declarations marked with the @internal JSDoc tag from the emitted .d.ts. The option is used to keep the internal members out of the public interface.
/**
* @internal
*/
export function internalHelper(): void {}
The function is marked as internal, and the stripInternal option removes it from the .d.ts. The consumer does not see it, and the public interface is smaller.
Complete Example Session
// ============================================
// PART 1: THE TSCONFIG
// ============================================
// tsconfig.json
// {
// "compilerOptions": {
// "target": "es2022",
// "module": "nodenext",
// "moduleResolution": "nodenext",
// "declaration": true,
// "declarationMap": true,
// "outDir": "./dist",
// "strict": true
// }
// }
// ============================================
// PART 2: THE SOURCE
// ============================================
// src/index.ts
export interface User {
id: string;
name: string;
}
export function getUser(id: string): Promise<User> {
return fetch(`/api/users/${id}`).then((r) => r.json());
}
export class UserService {
constructor(private readonly baseUrl: string) {}
getUser(id: string): Promise<User> {
return fetch(`${this.baseUrl}/users/${id}`).then((r) => r.json());
}
}
// ============================================
// PART 3: THE EMITTED .d.ts
// ============================================
// dist/index.d.ts
// export interface User {
// id: string;
// name: string;
// }
//
// export declare function getUser(id: string): Promise<User>;
//
// export declare class UserService {
// private readonly baseUrl;
// constructor(baseUrl: string);
// getUser(id: string): Promise<User>;
// }
//
// The function and class are `declare` because the implementation is in the .js.
// The interface has no runtime, so no `declare`.
// The private field appears without a type because it is private.
// ============================================
// PART 4: THE PACKAGE.JSON
// ============================================
// package.json
// {
// "name": "my-library",
// "version": "1.0.0",
// "main": "./dist/index.js",
// "types": "./dist/index.d.ts",
// "files": ["dist"]
// }
// ============================================
// PART 5: THE MODERN EXPORTS
// ============================================
// package.json
// {
// "name": "my-library",
// "exports": {
// ".": {
// "types": "./dist/index.d.ts",
// "import": "./dist/index.js"
// },
// "./utils": {
// "types": "./dist/utils.d.ts",
// "import": "./dist/utils.js"
// }
// }
// }
// ============================================
// PART 6: A HAND-WRITTEN .d.ts
// ============================================
// my-lib.d.ts
export interface Options {
timeout?: number;
retries?: number;
}
export declare function request(url: string, options?: Options): Promise<Response>;
export declare class Client {
constructor(options?: Options);
get(url: string): Promise<Response>;
}
// ============================================
// PART 7: A GLOBAL .d.ts
// ============================================
// globals.d.ts
declare const APP_VERSION: string;
declare function log(message: string): void;
interface Window {
myApp: {
version: string;
};
}
// No top-level export โ the declarations are global.
// ============================================
// PART 8: THE @types PACKAGE
// ============================================
// The @types/express package contains the .d.ts for express.
// The compiler loads it automatically when express is imported.
import express from 'express';
// The types are from node_modules/@types/express
// ============================================
// PART 9: TRIPLE-SLASH DIRECTIVES
// ============================================
/// <reference types="node" />
/// <reference path="./other.d.ts" />
/// <reference lib="es2020" />
// Legacy. The tsconfig's types and include are the modern replacements.
// ============================================
// PART 10: WHAT NOT TO DO
// ============================================
// Don't put runtime code in a .d.ts
// declare function fn() { return 1; } // โ function body not allowed
// Don't forget to declare the function or class
// function fn(): void; // โ missing declare in a .d.ts
// Don't use the `path` directive when tsconfig's include works
// The directive is legacy.
// Don't ship the .js without the .d.ts
// The consumer gets no types.
// Don't forget the types condition in the exports field
// The compiler does not find the declarations.
// Don't declare more than the library provides
// The .d.ts is a claim about the runtime.
The ten parts cover the tsconfig, the source, the emitted .d.ts, the package.json, the modern exports, a hand-written file, a global file, the @types package, the directives, and the anti-patterns.
Quick Reference
The Declaration Options
| Option | Effect |
|---|---|
declaration | Emit .d.ts files |
declarationMap | Emit .d.ts.map files |
emitDeclarationOnly | Emit only .d.ts files |
stripInternal | Remove @internal from .d.ts |
The package.json Fields
| Field | Purpose |
|---|---|
types | The .d.ts entry point |
typings | The same, older name |
exports["."].types | The modern form |
files | What to publish |
The Declaration Forms
| Form | Marker | Scope |
|---|---|---|
| Module | Top-level export | Module |
| Global | No top-level export | Global |
| Augmentation | declare module 'name' | The named module |
| Global augmentation | declare global | Global, from a module |
The declare Keyword
| Declaration | declare required |
|---|---|
const | โ |
function | โ |
class | โ |
namespace | โ |
interface | โ |
type | โ |
Triple-Slash Directives
| Directive | Purpose |
|---|---|
/// <reference path="..." /> | Include a file |
/// <reference types="..." /> | Include an @types package |
/// <reference lib="..." /> | Include a standard library |
The @types Packages
| Package | Provides |
|---|---|
@types/node | Node.js types |
@types/express | Express types |
@types/react | React types |
@types/jest | Jest types |
Best Practices
โ Do This:
// Generate the .d.ts files with declaration: true
{ "compilerOptions": { "declaration": true } } // โ
// Point the package.json to the .d.ts entry point
{ "types": "./dist/index.d.ts" } // โ
// Use the exports field for the modern form
{ "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" } } } // โ
// Put the types condition first
{ "types": "./dist/index.d.ts", "import": "./dist/index.js" } // โ
// Write the .d.ts from the library's actual behavior
export declare function request(url: string): Promise<Response>; // โ
// Use the global form for a library that attaches to the global
declare namespace MyLib { function helper(): void; } // โ
// Mark internal members with @internal
/** @internal */ export function helper(): void {} // โ
// Include the .d.ts files in the published package
{ "files": ["dist"] } // โ
โ Don’t Do This:
// Don't put runtime code in a .d.ts
declare function fn() { return 1; } // โ function body not allowed // โ ๏ธ
// Don't forget the declare keyword
function fn(): void; // โ missing declare in a .d.ts // โ ๏ธ
// Don't ship the .js without the .d.ts
// The consumer gets no types. // โ ๏ธ
// Don't put the import condition before types
{ "import": "./dist/index.js", "types": "./dist/index.d.ts" } // โ ๏ธ
// Don't declare more than the library provides
export declare function doesNotExist(): void; // claim // โ ๏ธ
// Don't use the triple-slash path directive when tsconfig works
/// <reference path="./other.d.ts" /> // legacy // โ ๏ธ
// Don't forget the @internal tag and stripInternal
// The internal members appear in the .d.ts. // โ ๏ธ
Common Pitfalls
| Pitfall | Problem | Solution |
|---|---|---|
Runtime code in .d.ts | Compile error | Declarations only |
Missing declare | Compile error | Add it |
.d.ts not shipped | No types for consumers | Include in files |
types condition not first | Compiler picks .js | Reorder |
Wrong types path | Types not found | Point to the entry |
| Hand-written file stale | Wrong types | Regenerate or update |
@internal not stripped | Internal members visible | Set stripInternal |
| Triple-slash in the middle | Ignored | Move to the top |
Real-World Examples
1. The declaration option
{ "compilerOptions": { "declaration": true } }
2. The types field
{ "types": "./dist/index.d.ts" }
3. The exports types condition
{ "exports": { ".": { "types": "./dist/index.d.ts" } } }
4. The emitted declaration
export declare function getUser(id: string): Promise<User>;
5. A hand-written interface
export interface Options { timeout?: number; }
6. A global declaration
declare const APP_VERSION: string;
7. A global Window augmentation
interface Window { myApp: { version: string }; }
8. An @types package
import express from 'express'; // types from @types/express
9. The types reference directive
/// <reference types="node" />
10. The stripInternal option
{ "compilerOptions": { "stripInternal": true } }
Visual: The Build Output
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ src/ โ
โ index.ts โ
โ utils.ts โ
โ โ
โ โ tsc with declaration: true โ
โ โผ โ
โ โ
โ dist/ โ
โ index.js โ the runtime code โ
โ index.d.ts โ the types โ
โ index.d.ts.map โ the source map โ
โ utils.js โ
โ utils.d.ts โ
โ utils.d.ts.map โ
โ โ
โ The .js is for the runtime. โ
โ The .d.ts is for the consumer's compiler. โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Visual: The declare Keyword
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ IN A .d.ts FILE โ
โ โ
โ declare const VERSION: string; โ ambient value โ
โ declare function greet(): void; โ ambient function โ
โ declare class Logger {} โ ambient class โ
โ declare namespace MyLib {} โ ambient namespace โ
โ โ
โ interface User { id: string; } โ no declare โ
โ type Role = 'admin' | 'user'; โ no declare โ
โ โ
โ The declare keyword marks a declaration that exists โ
โ at runtime but has no implementation in the file. โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Visual: The Module and Global Forms
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ MODULE FORM โ
โ โ
โ // index.d.ts โ
โ export interface User { id: string; } โ
โ export declare function getUser(id: string): User; โ
โ โ
โ Top-level export โ the file is a module. โ
โ The declarations are scoped to the module. โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ GLOBAL FORM โ
โ โ
โ // globals.d.ts โ
โ declare const APP_VERSION: string; โ
โ interface Window { myApp: App; } โ
โ โ
โ No top-level export โ the file is a script. โ
โ The declarations are in the global scope. โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Visual: The @types Package
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ node_modules/ โ
โ @types/ โ
โ express/ โ
โ index.d.ts โ the express types โ
โ node/ โ
โ index.d.ts โ the Node.js types โ
โ jest/ โ
โ index.d.ts โ the Jest types โ
โ โ
โ import express from 'express'; โ
โ โ โ
โ โโโ the compiler finds @types/express โ
โ automatically โ
โ โ
โ No import or configuration needed. โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Visual: The package.json Fields
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ CLASSIC FORM โ
โ โ
โ { โ
โ "main": "./dist/index.js", โ
โ "types": "./dist/index.d.ts" โ
โ } โ
โ โ
โ The compiler reads the `types` field. โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ MODERN FORM โ
โ โ
โ { โ
โ "exports": { โ
โ ".": { โ
โ "types": "./dist/index.d.ts", โ must be first โ
โ "import": "./dist/index.js", โ
โ "require":"./dist/index.cjs" โ
โ } โ
โ } โ
โ } โ
โ โ
โ The `types` condition must come first, or the โ
โ compiler picks the `.js` file and finds no declarations.โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
.d.ts | Declaration file, types only |
declaration: true | Emit .d.ts files |
declarationMap: true | Emit .d.ts.map files |
stripInternal: true | Remove @internal from .d.ts |
types field | The .d.ts entry point |
exports["."].types | The modern form |
declare | Ambient declaration |
| Module form | Has a top-level export |
| Global form | No top-level export |
@types package | Community .d.ts files |
| Triple-slash directive | Legacy include mechanism |
Key takeaways:
- A
.d.tsfile contains only type declarations โ no runtime code, and the compiler emits nothing from it - The compiler generates
.d.tsfiles whendeclaration: trueโ the.jsis the runtime, and the.d.tsis the types - The
declarekeyword introduces an ambient declaration โ it tells the compiler the name exists at runtime, and it is required for top-level variables, functions, classes, and namespaces - The module form has a top-level
export; the global form does not โ the distinction determines whether the declarations are scoped to the file or in the global scope - A hand-written
.d.tsdescribes a library that has no types โ it must match the library’s actual behavior, and it is often published as an@typespackage - The
package.json‘stypesfield or theexports‘typescondition points to the entry point โ the compiler reads the field and uses the file - The
typescondition must come first in theexportsfield โ the conditions are checked in order, and the first match wins - The
.d.tsfiles must be included in the published package โ thefilesfield or the default inclusion determines whether they are shipped - The
stripInternaloption removes@internalmembers from the.d.tsโ the internal members are kept out of the public interface - The triple-slash directives are a legacy mechanism โ the
tsconfig.json‘sincludeandtypesoptions are the modern replacements, but the directives still appear in generated code
Remember: A .d.ts file is the boundary between the typed world and the untyped one. It describes what exists at runtime, and it is what the consumer’s compiler reads. The generated files are always in sync with the source, and the hand-written ones must be kept in sync by hand. Ship the .d.ts with the library, point the package.json to it, and use the types condition in the exports field. The declare keyword is the marker of an ambient declaration, and the module and global forms are the two shapes a declaration file can take.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!