TS

Modules & Declaration Files

TypeScript · 6 entries

Declaration Files (.d.ts)

syntax
// filename.d.ts
declare function fn(param: Type): ReturnType;
declare const value: Type;
example
// global.d.ts
declare function trackEvent(name: string, data?: Record<string, unknown>): void;

declare const APP_VERSION: string;

declare interface ImportMeta {
  env: {
    VITE_API_URL: string;
    VITE_DEBUG: string;
  };
}
output
// .d.ts files provide type info without runtime code

Note Declaration files describe the shape of existing JavaScript code. They contain only type information — no executable code. TypeScript automatically picks up .d.ts files in your project. Most npm packages include their own or have a @types/package companion.

declare Keyword

syntax
declare const name: Type;
declare function fn(): Type;
declare class Name { ... }
declare module "name" { ... }
example
// Tell TypeScript about a global that exists at runtime
declare const gtag: (
  command: "config" | "event",
  targetId: string,
  params?: Record<string, unknown>
) => void;

// Tell TypeScript about a global class
declare class Stripe {
  constructor(apiKey: string);
  charges: {
    create(params: { amount: number; currency: string }): Promise<unknown>;
  };
}

gtag("event", "purchase", { value: 99.99 });
output
// declare tells TypeScript 'trust me, this exists at runtime'

Note declare is for things that already exist in the runtime environment but TypeScript cannot see (global scripts, CDN libraries, injected variables). It emits zero JavaScript. Using declare when the value does not actually exist leads to runtime crashes with no compile-time warning.

Ambient Module Declarations

syntax
declare module "module-name" {
  export function fn(): Type;
  export default value;
}
example
// types/untyped-libs.d.ts

// Type an entire untyped npm package
declare module "color-hash" {
  export default class ColorHash {
    constructor(options?: { lightness: number });
    hex(input: string): string;
    rgb(input: string): [number, number, number];
  }
}

// Wildcard: type all .svg imports
declare module "*.svg" {
  const content: string;
  export default content;
}

// Wildcard: type all .css modules
declare module "*.module.css" {
  const classes: Record<string, string>;
  export default classes;
}
output
// Now: import ColorHash from 'color-hash'; // fully typed

Note Ambient module declarations let you type third-party packages that lack types, or describe non-JS imports (CSS, SVG, images). Wildcard modules (*.ext) match any import path ending with that extension. Place these in a .d.ts file included in your tsconfig.

Module Augmentation

syntax
// In a .ts file (not .d.ts)
import "original-module";

declare module "original-module" {
  interface ExistingInterface {
    newProperty: Type;
  }
}
example
// Augmenting Express Request
import "express";

declare module "express" {
  interface Request {
    userId?: string;
    sessionToken?: string;
    permissions?: string[];
  }
}

// Now in your middleware:
import { Request, Response, NextFunction } from "express";

function authMiddleware(req: Request, res: Response, next: NextFunction) {
  req.userId = "usr_123";        // No error — augmented
  req.permissions = ["read"];    // No error — augmented
  next();
}
output
// The augmented properties are available everywhere Request is used

Note Module augmentation extends existing module types without modifying the original package. The file performing augmentation must be a module itself (have at least one import/export). This only works for interfaces and namespaces — you cannot add new top-level exports through augmentation.

Triple-Slash Directives

syntax
/// <reference path="..." />
/// <reference types="..." />
/// <reference lib="..." />
example
/// <reference types="vite/client" />
// Enables Vite-specific types like import.meta.env

/// <reference lib="es2022" />
// Includes ES2022 library types

/// <reference path="./custom-globals.d.ts" />
// Explicitly includes another declaration file
output
// Triple-slash directives are special comments that instruct the compiler

Note Triple-slash directives must appear at the very top of the file (before any code). They are mostly obsolete for modern projects — tsconfig's 'types', 'lib', and 'include' options do the same job. The main surviving use case is /// <reference types="..." /> in .d.ts files and Vite/framework setups.

Namespaces vs Modules

syntax
// Namespace (legacy)
namespace Name { export ... }
// Module (modern)
export ...
import { ... } from "...";
example
// LEGACY: Namespace (avoid in new code)
namespace Validation {
  export interface Rule {
    validate(input: string): boolean;
  }

  export class EmailRule implements Rule {
    validate(input: string): boolean {
      return input.includes("@");
    }
  }
}

// MODERN: Use ES modules instead
// validation.ts
export interface Rule {
  validate(input: string): boolean;
}

export class EmailRule implements Rule {
  validate(input: string): boolean {
    return input.includes("@");
  }
}
output
// ES modules are the standard; namespaces are legacy

Note Namespaces (formerly 'internal modules') are largely obsolete. Modern TypeScript projects should use ES modules (import/export) exclusively. Namespaces still appear in declaration files for global libraries and in legacy codebases. Do not use namespace and module imports in the same file.