TypeScript path · In a real project · Topic 9 of 11

Declaration files and typing JavaScript

Not started

The checker can only check code against types it knows. For your own .ts files it reads the source. For everything else (an npm package written in JavaScript, the browser's DOM, Node's built-in modules, a script tag that adds a global) it reads declaration files: .d.ts files containing only types, written with declare, describing JavaScript that lives somewhere else. They produce no output and nobody checks that they are true.

Why it matters: Every library you import is typed by a declaration file someone wrote by hand or generated. When those types are wrong, missing or out of date, you need to know where they come from and how to fix them.

  1. 1A .d.ts file is types only

    It describes the shape of JavaScript that exists somewhere else, with `declare` and no implementations. It produces no output.

  2. 2Three places types come from

    The package ships its own (a `types` entry in package.json), the community writes them in DefinitelyTyped (`@types/name`), or you declare them yourself.

  3. 3Declarations are promises, not checks

    Nothing verifies that a `.d.ts` matches the JavaScript it describes. A wrong declaration gives you confident types for code that does something else.

1

How it works

The explanation, with examples you can run.

What one looks like

// slugify.d.ts: types for a JavaScript file next to it, slugify.jsexport interface SlugOptions {  separator?: string  lower?: boolean} export declare function slugify(input: string, options?: SlugOptions): string

There are no function bodies, only signatures. When your code imports ./slugify.js, the checker uses this file for the types and the runtime uses the JavaScript. If the JavaScript actually returns null for empty input, the declaration is wrong, and every caller trusts it anyway.

Where library types come from

  1. The package ships them. Its package.json points at them ("types", or "types" conditions inside "exports"), usually generated by tsc --declaration from TypeScript source. This is the best case: they are released together with the code.
  2. DefinitelyTyped. A large community repository of declarations for packages that don't ship their own, published to npm as @types/<name>. Install it as a dev dependency. Its version is not tied to the library's, so check that the major versions line up.
  3. You write them. For a package with no types anywhere, declare the parts you use:
// types/legacy-charts.d.tsdeclare module "legacy-charts" {  export function draw(el: HTMLElement, data: number[]): void}

The lib setting in tsconfig.json picks the built-in declarations (ES2023, DOM, and so on), and @types/node describes Node's APIs. A browser project that accidentally includes @types/node will happily type-check process.env in front-end code.

Globals and augmentation

Declarations can add to things that already exist:

// globals.d.tsexport {} declare global {  interface Window {    analytics?: { track(event: string): void }  }}

Interfaces with the same name merge, so this adds a property to the DOM's Window without replacing it. The same trick adds fields to a library's types, such as a user on a framework's request object.

Typing JavaScript you don't want to convert

TypeScript reads JSDoc comments in .js files:

// @ts-check /** * @param {number[]} prices * @returns {number} */export function total(prices) {  return prices.reduce((sum, p) => sum + p, 0)}

// @ts-check (or checkJs in the config) reports type errors in that file; allowJs lets TypeScript files import it; and declaration with emitDeclarationOnly generates .d.ts files from it. A library can stay in JavaScript and still give its users accurate types.

When library types are wrong

skipLibCheck: true is common because checking every .d.ts in node_modules is slow, but it also hides conflicts between them. When a declaration is wrong, fix it locally with module augmentation or a narrow cast at the call site, and send the fix upstream; casting the whole module to any throws away all its other types.

2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.You install a package and import it, and the checker says it can't find a declaration file. What do you try first?
  2. 2.What does `skipLibCheck: true` skip?
  3. 3.Your JavaScript library is written in `.js` files. How can it ship types without being rewritten?

0 of 3 right so far. Get all three to finish this topic.