ProgrammingFrontend Developer

What are Declaration Files in TypeScript, when and why to write custom d.ts files? How to structure custom type definitions for external JS modules?

Pass interviews with Hintsage AI assistant

Answer.

Background:

Many libraries in the JavaScript ecosystem provide only source js files without their own types. To describe types for third-party or custom libraries, as well as global variables, TypeScript implements a special format of files with the .d.ts extension (declaration files). They have become the standard for providing type information and type safety in projects over any js modules.

Problem:

If types for third-party JS modules are not defined, TypeScript is forced to treat such imports as any, meaning you lose all the benefits of static typing: errors in calls, non-existent fields, wrong parameters logically pass compilation and are only detected after the code is run. It is also impossible to do autocompletion and type navigation.

Solution:

With declaration files, you can manually describe types for any JS code: functions, classes, objects, namespaces, and even global constants. Thus, the project remains type-safe regardless of the origin of the external library.

Code example:

// hello.d.ts declare module 'hello' { export function sayHello(name: string): string; } // app.ts import { sayHello } from 'hello'; sayHello('TypeScript'); // Type is safe

Key features:

  • Separate the description of signatures and structure types from the implementation (source JS code);
  • Allow for strict typing even in third-party/legacy builds;
  • In .d.ts files, implementation is prohibited, only signatures/descriptions.

Trick questions.

Can function implementations be declared directly in the declaration file?

No, declaration files only allow the declaration of structures and signatures, not their implementation. Any function bodies will cause a compilation error.

// Not allowed: declare function sum(a: number, b: number) { return a + b; }

Where to find types for popular npm modules if they are not in the original package?

In the DefinitelyTyped repository (npm package @types/<lib>): almost all popular packages have up-to-date typings in the form of separate npm modules.

Can global variables (not through import) be described using a d.ts file?

Yes, through the ambient declarations mechanism, for example, declare var VERSION: string;. This is convenient for describing window.X, global constants, and variables.

Typing errors and anti-patterns

  • Writing function and class bodies in d.ts files;
  • Describing incomplete or outdated signatures, causing conflicts with the real structure;
  • Connecting different typings for one module/global variable, causing type conflicts.

Real-life example

** Negative case A JS library without typings is used in the project. Developers forgot about the d.ts file and refer to the API via any. Bugs arise when updating the library: old calls break, but the compiler does not notice this.

Pros:

  • Quick start, no additional description required.

Cons:

  • Hidden errors, implicit behavior, difficult debugging on large amounts of code.

** Positive case A custom d.ts file has been developed for the current library, signatures are kept up to date, and IDE autocompletion and navigation are used.

Pros:

  • Complete type safety, errors are instantly visible when changing the API;
  • Speeds up development, new developers can be onboarded without a deep understanding of the JS code.

Cons:

  • Separate maintenance of d.ts files, need to monitor synchronization during JS library updates.