TypeScript Modules and Declarations
1. ES Module Syntax (import/export) in TypeScript
| 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 (require and module.exports)
| 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, NodeNext, 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 (declare module)
| 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
importstatements instead of/// <reference path /> - Configure
typesarray in tsconfig.json instead of/// <reference types /> - Configure
libarray in tsconfig.json instead of/// <reference lib />
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