ES Module Syntax
ES Module Syntax
Named Exports
// math.ts
export function add(a: number, b: number): number {
return a + b;
}
export function subtract(a: number, b: number): number {
return a - b;
}
export const PI = 3.14159;
export interface Point {
x: number;
y: number;
}
export type Result<T> = { success: true; data: T } | { success: false; error: string };
Default Export
// logger.ts
export default class Logger {
log(message: string): void {
console.log(`[LOG] ${message}`);
}
}
// Import default export
import Logger from './logger.js';
const logger = new Logger();
Re-exports
// Re-export from another module
export { add, subtract } from './math.js';
export type { Point } from './types.js';
// Re-export with rename
export { add as sum } from './math.js';
Module Resolution
Module Resolution
TypeScript Configuration
// tsconfig.json
{
"compilerOptions": {
"module": "NodeNext",
"moduleResolution": "NodeNext"
}
}
Import Styles
// Named import
import { readFile, writeFile } from 'fs/promises';
// Default import
import express from 'express';
// Namespace import
import * as path from 'path';
// Side-effect import
import './polyfills.js';
// Type-only import
import type { Request, Response } from 'express';
import { type User, createUser } from './user.js';
File Extensions
// With NodeNext module resolution, include file extensions
import { helper } from './utils.js'; // not './utils'
// In tsconfig, use .js extension even for .ts files
// The compiler will resolve to the correct .ts file
Barrel Files
Barrel Files
Organizing Exports
// types/index.ts (barrel file)
export type { User } from './user.js';
export type { Post } from './post.js';
export type { Comment } from './comment.js';
// Import from barrel
import type { User, Post, Comment } from './types/index.js';
Selective Barrel Exports
// Avoid exporting everything - be explicit
export { UserService } from './user-service.js';
export type { UserServiceInterface } from './user-service.js';
// Don't do this - leaks internal implementation
// export * from './internal.js';
Index Files
// api/index.ts
export { UserController } from './user-controller.js';
export { PostController } from './post-controller.js';
export { setupRoutes } from './routes.js';
// Clean imports in main file
import { UserController, PostController, setupRoutes } from './api/index.js';
CommonJS Interop
CommonJS Interop
CJS vs ESM
// CommonJS style (Node.js legacy)
const express = require('express');
module.exports = { helper };
// ES Module style (modern)
import express from 'express';
export function helper() {}
Enabling Interop
// tsconfig.json
{
"compilerOptions": {
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
}
}
Importing CJS from ESM
// Works with esModuleInterop enabled
import express from 'express'; // CJS module imported as default
// Without esModuleInterop, you would need:
import * as express from 'express';
Package.json Configuration
// For ES modules in Node.js
{
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
}
}
Best Practices
- Always use ES module syntax in TypeScript
- Enable
esModuleInteropin tsconfig - Include
.jsextensions in imports - Use
typekeyword for type-only imports - Keep barrel files explicit, avoid
export *