Module System and Declaration Files

1. ES Module Syntax (import/export)

Syntax Description Example Use Case
Named Export Export specific values by name export const value = 42; Multiple exports from module
Default Export Single main export per module export default class X {} Primary module export
Named Import Import specific named exports import { value } from './mod'; Selective imports
Default Import Import default export import Mod from './mod'; Main module import
Namespace Import Import all exports as object import * as Mod from './mod'; Bundle all exports
Re-export Export from another module export { value } from './mod'; Module aggregation
Side-effect Import Import for side effects only import './init'; Module initialization

Example: Named exports and imports

// math.ts - named exports
export const PI = 3.14159;
export const E = 2.71828;

export function add(a: number, b: number): number {
    return a + b;
}

export function multiply(a: number, b: number): number {
    return a * b;
}

export interface MathResult {
    value: number;
    operation: string;
}

// main.ts - named imports
import { PI, E, add, multiply } from './math';

console.log(PI, E);
const sum = add(5, 3);
const product = multiply(4, 2);

// Import with alias
import { add as sum, multiply as times } from './math';

// Import specific items
import { PI } from './math';

// Import type only
import type { MathResult } from './math';

Example: Default exports and mixed exports

// user.ts - default export
export default class User {
    constructor(public name: string) {}
    
    greet() {
        return `Hello, ${this.name}`;
    }
}

// Alternative: export after declaration
class User {
    constructor(public name: string) {}
}
export default User;

// app.ts - import default
import User from './user';
const user = new User('Alice');

// mixed.ts - both default and named exports
export default class Database {
    connect() { /* ... */ }
}

export const DB_VERSION = '1.0';
export function createConnection() { /* ... */ }

// Import both
import Database, { DB_VERSION, createConnection } from './mixed';

// Can rename default import
import DB from './mixed';

Example: Re-exports and barrel exports

// models/user.ts
export interface User {
    id: string;
    name: string;
}

// models/product.ts
export interface Product {
    id: string;
    price: number;
}

// models/index.ts - barrel export
export { User } from './user';
export { Product } from './product';
export * from './order'; // Re-export all

// Or rename during re-export
export { User as UserModel } from './user';

// app.ts - import from barrel
import { User, Product } from './models';

// Re-export with modifications
export { User } from './user';
export type { Product } from './product'; // Type-only re-export
export { Order as PurchaseOrder } from './order';

Example: Namespace imports and dynamic imports

// utils.ts
export const version = '1.0';
export function format(s: string) { return s.toUpperCase(); }
export function parse(s: string) { return s.toLowerCase(); }

// Import entire module as namespace
import * as Utils from './utils';

console.log(Utils.version);
const formatted = Utils.format('hello');

// Dynamic import (returns Promise)
async function loadModule() {
    const utils = await import('./utils');
    console.log(utils.version);
    utils.format('test');
}

// Conditional loading
if (condition) {
    import('./feature').then(module => {
        module.initialize();
    });
}

// Dynamic import with type
type UtilsModule = typeof import('./utils');
const module: UtilsModule = await import('./utils');

2. CommonJS Interoperability and require()

Syntax Description ES Module Equivalent Interop Behavior
module.exports CommonJS export object export default Single export object
exports.prop Add named export property export const prop Named export properties
require() Synchronous import import Requires esModuleInterop
__esModule flag Indicates ES module compiled to CJS Compiler generated Better interop detection

Example: CommonJS export patterns

// commonjs.js - CommonJS module
// Pattern 1: exports object
exports.name = 'MyModule';
exports.version = '1.0';
exports.greet = function(name) {
    return `Hello, ${name}`;
};

// Pattern 2: module.exports replacement
module.exports = {
    name: 'MyModule',
    version: '1.0',
    greet(name) {
        return `Hello, ${name}`;
    }
};

// Pattern 3: Single function export
module.exports = function greet(name) {
    return `Hello, ${name}`;
};

// Pattern 4: Class export
class Database {
    connect() { /* ... */ }
}
module.exports = Database;

// Pattern 5: Mixed (avoid - confusing)
exports.helper = () => {};
module.exports.main = () => {}; // Same as exports.main

Example: Importing CommonJS in TypeScript

// Without esModuleInterop (old style)
import * as express from 'express';
const app = express(); // Works

// With esModuleInterop: true (recommended)
import express from 'express';
const app = express(); // Works like CommonJS

// require() in TypeScript (not recommended)
const fs = require('fs'); // Type: any
const fs: typeof import('fs') = require('fs'); // With types

// Import assignment (legacy)
import fs = require('fs');
fs.readFileSync('/path');

// Type-only import for CommonJS types
import type { Express } from 'express';
const app: Express = createExpressApp();

Example: TypeScript compiled to CommonJS

// source.ts - TypeScript source
export const value = 42;
export default class MyClass {}

// Compiled to CommonJS (target: "commonjs")
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.value = void 0;
exports.value = 42;

class MyClass {
}
exports.default = MyClass;

// __esModule flag helps with interop
// TypeScript/Babel use it to detect ES modules

// Import in Node.js
const mod = require('./source');
console.log(mod.value); // 42
console.log(mod.default); // MyClass (with esModuleInterop)

// With esModuleInterop, can use:
const MyClass = require('./source').default;
Note: Enable esModuleInterop and allowSyntheticDefaultImports in tsconfig.json for better CommonJS compatibility. This allows import React from 'react' instead of import * as React from 'react'.

3. Module Resolution Strategies (Node, Classic)

Strategy Algorithm Use Case File Extensions
Node (Node10) Mimics Node.js require() resolution Most common, npm packages .ts, .tsx, .d.ts, .js, .jsx
Node16/NodeNext Node.js ESM resolution with package.json Modern Node.js ESM projects Respects "type": "module"
Classic Legacy pre-1.6 resolution Rarely used, backwards compat .ts, .d.ts only
Bundler Bundler-like resolution (Webpack/Vite) SPA applications with bundlers Assumes bundler handles resolution

Example: Node resolution strategy

// For: import { x } from './module';
// TypeScript searches in order:

// 1. Relative import './module'
./module.ts
./module.tsx
./module.d.ts
./module/package.json (check "types" field)
./module/index.ts
./module/index.tsx
./module/index.d.ts

// 2. Non-relative import 'lodash'
// From /root/src/app.ts:

/root/src/node_modules/lodash.ts
/root/src/node_modules/lodash.tsx
/root/src/node_modules/lodash.d.ts
/root/src/node_modules/lodash/package.json ("types" field)
/root/src/node_modules/lodash/index.ts
/root/src/node_modules/lodash/index.d.ts

/root/node_modules/lodash/...
/node_modules/lodash/...

// 3. Check @types
/root/src/node_modules/@types/lodash.d.ts
/root/src/node_modules/@types/lodash/index.d.ts
/root/node_modules/@types/lodash/...
/node_modules/@types/lodash/...

Example: Node16/NodeNext resolution

// package.json configuration
{
    "type": "module", // Use ESM
    "exports": {
        ".": {
            "types": "./dist/index.d.ts",
            "import": "./dist/index.js",
            "require": "./dist/index.cjs"
        },
        "./utils": {
            "types": "./dist/utils.d.ts",
            "import": "./dist/utils.js"
        }
    }
}

// With Node16/NodeNext, must use extensions
// ✗ Error - missing extension
import { x } from './module';

// ✓ Correct - with extension
import { x } from './module.js'; // .js even for .ts files!

// For directories, must use index
import { y } from './utils/index.js';

// Package imports
import pkg from 'package'; // Uses package.json "exports"
import sub from 'package/submodule'; // Must be in exports map

Example: Path mapping and baseUrl

// tsconfig.json
{
    "compilerOptions": {
        "baseUrl": "./src",
        "paths": {
            "@/*": ["./*"],
            "@components/*": ["components/*"],
            "@utils/*": ["utils/*"],
            "@models": ["models/index"]
        }
    }
}

// Usage with path mapping
import { Button } from '@components/Button';
import { User } from '@models';
import { format } from '@utils/string';
import config from '@/config';

// Without mapping (relative paths)
import { Button } from '../../components/Button';
import { User } from '../../models/index';
import { format } from '../utils/string';
import config from './config';

// Note: Bundlers need to be configured to understand paths
// Webpack: use tsconfig-paths-webpack-plugin
// Vite: use resolve.alias
Warning: Path mapping in tsconfig.json is compile-time only. For Node.js runtime, use a tool like tsconfig-paths or configure your bundler. Node16/NodeNext resolution doesn't support paths mapping.

4. Declaration Files (.d.ts) and Type Declarations

Feature Syntax Purpose Use Case
Declaration File .d.ts Type definitions without implementation Type libraries, JS libraries
declare var/let/const declare const x: T; Declare global variable Global variables from scripts
declare function declare function fn(): T; Declare global function Global functions
declare class declare class C {} Declare class structure Classes from JS
declare namespace declare namespace N {} Group related declarations Library namespaces
declare module declare module 'pkg' {} Module augmentation Add types to external modules

Example: Basic declaration file

// types.d.ts - declaration file

// Declare global variable
declare const VERSION: string;
declare let currentUser: User | null;

// Declare global function
declare function $(selector: string): Element;
declare function fetch(url: string): Promise<Response>;

// Declare class
declare class EventEmitter {
    on(event: string, handler: Function): void;
    emit(event: string, ...args: any[]): void;
}

// Declare interface (no declare needed)
interface User {
    id: string;
    name: string;
}

// Declare type alias
type ID = string | number;

// Declare enum
declare enum Color {
    Red,
    Green,
    Blue
}

// Usage in .ts files (no import needed for global declarations)
console.log(VERSION);
const el = $('div');
const user: User = { id: '1', name: 'Alice' };

Example: Module declaration files

// lodash.d.ts - types for JavaScript library

declare module 'lodash' {
    // Named exports
    export function debounce<T extends Function>(
        func: T,
        wait: number
    ): T;
    
    export function chunk<T>(
        array: T[],
        size: number
    ): T[][];
    
    export function map<T, U>(
        array: T[],
        iteratee: (value: T) => U
    ): U[];
    
    // Default export
    const _: {
        debounce: typeof debounce;
        chunk: typeof chunk;
        map: typeof map;
    };
    
    export default _;
}

// jquery.d.ts - global + module
declare module 'jquery' {
    export = $; // Export assignment for CommonJS
}

declare const $: {
    (selector: string): JQuery;
    ajax(settings: AjaxSettings): void;
};

interface JQuery {
    addClass(className: string): JQuery;
    removeClass(className: string): JQuery;
    html(content: string): JQuery;
}

interface AjaxSettings {
    url: string;
    method?: string;
    data?: any;
}

Example: Generating declaration files

// tsconfig.json
{
    "compilerOptions": {
        "declaration": true,          // Generate .d.ts files
        "declarationMap": true,       // Generate .d.ts.map for navigation
        "emitDeclarationOnly": false, // Emit both .js and .d.ts
        "declarationDir": "./types"   // Output directory for .d.ts
    }
}

// source.ts
export interface User {
    id: string;
    name: string;
}

export function createUser(name: string): User {
    return { id: Math.random().toString(), name };
}

export default class UserManager {
    private users: User[] = [];
    
    add(user: User): void {
        this.users.push(user);
    }
}

// Generated: source.d.ts
export interface User {
    id: string;
    name: string;
}
export declare function createUser(name: string): User;
export default class UserManager {
    private users;
    add(user: User): void;
}

Example: @types packages

// Install type definitions
// npm install --save-dev @types/node
// npm install --save-dev @types/express
// npm install --save-dev @types/react

// @types/node provides Node.js types
import * as fs from 'fs';
import * as path from 'path';

fs.readFileSync('./file.txt'); // Types available

// @types/express provides Express types
import express from 'express';
const app = express();

// Some packages include their own types
// package.json has "types" field
import lodash from 'lodash-es'; // Has built-in types

// Check if types exist: https://www.npmjs.com/~types
// Or search: npm search @types/package-name

// Override @types with local declarations
// Create: node_modules/@types/custom/index.d.ts
// Or configure typeRoots in tsconfig.json
{
    "compilerOptions": {
        "typeRoots": ["./types", "./node_modules/@types"]
    }
}

5. Ambient Module Declarations

Pattern Syntax Purpose Scope
Ambient Module declare module 'name' {} Provide types for untyped modules Project-wide
Wildcard Module declare module '*.css' {} Type non-JS imports (CSS, images, etc.) Asset imports
Module Augmentation declare module 'pkg' { export ... } Add types to existing module Extend libraries
Global Augmentation declare global {} Add to global scope from module Global extensions

Example: Ambient module declarations

// types.d.ts - ambient declarations

// Declare module with no types available
declare module 'legacy-lib' {
    export function doSomething(value: any): any;
    export const VERSION: string;
}

// Now can import
import { doSomething, VERSION } from 'legacy-lib';

// Minimal typing for quick fixes
declare module 'no-types-available' {
    const content: any;
    export default content;
}

import lib from 'no-types-available';

// Specific module path pattern
declare module '@company/*/config' {
    interface Config {
        apiKey: string;
        endpoint: string;
    }
    const config: Config;
    export default config;
}

import config from '@company/users/config';
import config2 from '@company/products/config';

Example: Wildcard module declarations for assets

// global.d.ts - type asset imports

// CSS Modules
declare module '*.module.css' {
    const classes: { [key: string]: string };
    export default classes;
}

declare module '*.module.scss' {
    const classes: { [key: string]: string };
    export default classes;
}

// Regular CSS
declare module '*.css' {
    const content: string;
    export default content;
}

// Images
declare module '*.png' {
    const value: string;
    export default value;
}

declare module '*.jpg' {
    const value: string;
    export default value;
}

declare module '*.svg' {
    import { FC, SVGProps } from 'react';
    const content: FC<SVGProps<SVGSVGElement>>;
    export default content;
}

// JSON (if not using resolveJsonModule)
declare module '*.json' {
    const value: any;
    export default value;
}

// Usage in components
import styles from './Component.module.css';
import logo from './logo.png';
import icon from './icon.svg';
import data from './data.json';

const className = styles.container; // Type: string
const imageSrc = logo; // Type: string

Example: Module augmentation

// Extend existing module with new exports
declare module 'express' {
    // Add custom properties to Request
    interface Request {
        user?: {
            id: string;
            name: string;
        };
        startTime?: number;
    }
    
    // Add custom response methods
    interface Response {
        sendSuccess(data: any): void;
        sendError(message: string): void;
    }
}

// Now can use augmented types
import { Request, Response } from 'express';

app.use((req: Request, res: Response, next) => {
    req.startTime = Date.now(); // ✓ Type-safe
    if (req.user) {
        console.log(req.user.name); // ✓ Type-safe
    }
    res.sendSuccess({ ok: true }); // ✓ Type-safe
});

// Augment module from within a module
// my-plugin.ts
import 'express';

declare module 'express' {
    interface Request {
        customField: string;
    }
}

// Available after import
import './my-plugin';
app.get('/', (req, res) => {
    console.log(req.customField); // ✓ Available
});

Example: Global augmentation from module

// extensions.d.ts - module file with global augmentation

// Must have at least one import/export to be a module
export {};

// Augment global scope
declare global {
    // Add to Window interface
    interface Window {
        myApp: {
            version: string;
            config: AppConfig;
        };
        gtag: (command: string, ...args: any[]) => void;
    }
    
    // Add global variable
    var APP_ENV: 'development' | 'production';
    
    // Add to Array prototype
    interface Array<T> {
        firstOrNull(): T | null;
        lastOrNull(): T | null;
    }
    
    // Add to String prototype
    interface String {
        toKebabCase(): string;
        toCamelCase(): string;
    }
}

// Usage anywhere in project
window.myApp.version; // ✓ Type-safe
console.log(APP_ENV); // ✓ Type-safe

const arr = [1, 2, 3];
const first = arr.firstOrNull(); // ✓ Type-safe

const str = 'hello-world';
const kebab = str.toKebabCase(); // ✓ Type-safe

6. Triple-Slash Directives and Reference Types

Directive Syntax Purpose Modern Alternative
/// <reference path="" /> Reference file by path Include declaration file Use import statements
/// <reference types="" /> Reference @types package Include type package Use types array in tsconfig
/// <reference lib="" /> Reference built-in lib Include specific lib types Use lib array in tsconfig
/// <reference no-default-lib="true" /> Exclude default lib Custom lib definitions Rarely needed

Example: Triple-slash path references (legacy)

// types.d.ts
interface User {
    id: string;
    name: string;
}

// main.ts - reference other file
/// <reference path="./types.d.ts" />

const user: User = { id: '1', name: 'Alice' };

// Better modern approach: use import
import type { User } from './types';

// Multiple references
/// <reference path="./globals.d.ts" />
/// <reference path="./utils.d.ts" />
/// <reference path="./models.d.ts" />

// Note: Triple-slash directives must be at top of file
// Before any code or imports

Example: Reference types directive

// Include types from @types package
/// <reference types="node" />

// Now have Node.js types without explicit import
const fs: typeof import('fs') = require('fs');
console.log(__dirname); // Global Node.js variable

// Include jQuery types
/// <reference types="jquery" />

$('div').addClass('active'); // jQuery available globally

// In declaration files (.d.ts)
/// <reference types="react" />

declare module 'my-react-library' {
    import { ComponentType } from 'react';
    export const MyComponent: ComponentType<{}>;
}

// Modern approach in tsconfig.json
{
    "compilerOptions": {
        "types": ["node", "jest", "jquery"]
    }
}

Example: Reference lib directive

// Reference specific library versions
/// <reference lib="es2015" />
/// <reference lib="dom" />

// Enables specific APIs
const map = new Map(); // ES2015
const el = document.querySelector('div'); // DOM

// Reference newer libs
/// <reference lib="es2020.promise" />
/// <reference lib="es2021.string" />

// Promise.allSettled (ES2020)
const results = await Promise.allSettled([
    Promise.resolve(1),
    Promise.reject('error')
]);

// String.replaceAll (ES2021)
const replaced = 'hello'.replaceAll('l', 'L');

// Available lib values:
// es5, es2015, es2016, es2017, es2018, es2019, es2020, es2021
// dom, dom.iterable, webworker, scripthost
// es2015.core, es2015.collection, es2015.promise, etc.

// Modern approach in tsconfig.json
{
    "compilerOptions": {
        "lib": ["es2020", "dom", "dom.iterable"]
    }
}

Example: Advanced triple-slash usage

// custom-lib.d.ts - custom library definitions
/// <reference no-default-lib="true" />

// Provide minimal environment (rare use case)
interface Array<T> {
    length: number;
    push(...items: T[]): number;
}

interface String {
    length: number;
    substring(start: number, end?: number): string;
}

// Combined directives
/// <reference lib="es2020" />
/// <reference types="node" />
/// <reference path="./custom.d.ts" />

// Order matters: must be at very top
/// <reference path="./types.d.ts" />
/// <reference types="node" />
import { readFile } from 'fs'; // After directives

// Directive ignored if not at top
import something from './module';
/// <reference types="node" /> // ✗ Ignored - comes after import
Note: Triple-slash directives are legacy features. Modern TypeScript projects should:
  • Use import statements instead of /// <reference path />
  • Configure types array in tsconfig.json instead of /// <reference types />
  • Configure lib array in tsconfig.json instead of /// <reference lib />
Only use triple-slash directives in declaration files (.d.ts) or for global script files.
Warning: Triple-slash directives:
  • Must appear at the very top of the file (before any code)
  • Are only processed in .d.ts files and at project entry points
  • Don't work in module files (files with import/export)
  • Can cause confusion - prefer explicit imports and tsconfig

Module System Summary

  • ES Modules - Use import/export for modern module syntax with named, default, and namespace patterns
  • CommonJS - Enable esModuleInterop for better CJS compatibility with require()/module.exports
  • Module Resolution - Node/Node16 strategies determine how imports are resolved; use path mapping for aliases
  • Declaration Files - .d.ts files provide types without implementation; generate with declaration: true
  • Ambient Modules - Use declare module for typing untyped libraries and wildcard patterns for assets
  • Triple-Slash Directives - Legacy feature for references; prefer imports and tsconfig configuration