TypeScript Modules
A quick guide to TypeScript modules, named exports, type-only imports, and path resolution.
TL;DR
- Share application code across files using
exportandimportstatements. - Prefer named exports over
defaultto simplify refactoring and autocompletion. - Use
import typeto ensure zero runtime code overhead.
Named Exports and Imports
named exportPrefixes declarations with the export keyword to share them.
// math.ts
export const PI = 3.14159;
export function double(n: number): number {
return n * 2;
}named importPulls specific declarations into scope using curly braces.
import { PI, double } from './math';
console.log(double(10)); // 20renaming with asAliases imported or exported identifiers to prevent collisions.
import { double as multiplyByTwo } from './math';
const val = multiplyByTwo(5);Default Exports
export defaultDesignates the primary entry point of an individual module.
// logger.ts
export default function log(msg: string): void {
console.log(`[APP]: ${msg}`);
}default importImports the default entity without using enclosing curly braces.
import log from './logger';
log('System initialized');mixed exportsCombines a default export alongside auxiliary named exports.
// client.ts
export const timeout = 5000;
export default class ApiClient {}Type-Only Imports and Exports
import typeImports types strictly without emitting any JavaScript runtime code.
import type { User } from './types';
let currentUser: User;inline type modifierDifferentiates values and types within a single import statement.
import { type User, fetchUser } from './users';
const user: User = await fetchUser('101');export typeRe-exports type signatures from internal contracts safely.
export type { UserConfig, AuthToken } from './auth';Re-Exporting and Barrels
barrel exportAggregates multiple submodules into a unified entry point.
// components/index.ts
export { Button } from './Button';
export { Modal } from './Modal';export wildcardRe-exports every named export from a specified target file.
export * from './constants';
export * from './helpers';renamed re-exportExposes an internal default export as a clean named export.
export { default as Header } from './Header';Module Resolution and Paths
relative pathsReferences adjacent files using explicit directory indicators.
import { utils } from './subfolder/utils';
import { parentConfig } from '../config';bare specifiersLoads packages installed inside the node_modules folder.
import React from 'react';
import { z } from 'zod';path aliasesShortens deeply nested imports using configured prefix aliases.
// Configured via tsconfig paths
import { formatPrice } from '@/lib/utils';Tips
- Always use
import typewhen bringing in interfaces to prevent bundlers from including unnecessary JavaScript overhead. - Keep barrel
index.tsfiles focused on cohesive subdirectories to avoid triggering circular dependencies during bundle compilation.
Warnings
- Remember that
tsconfig.jsonpath aliases only satisfy the type checker and still require bundler resolution plugins. - Avoid using wildcard
export *barrels in large libraries because they frequently defeat bundler tree-shaking optimizations.
In Practice
Demonstrates type-only imports, named exports, and barrel patterns in an API client module.
- Import contract types using explicit type-only import syntax.
- Declare a configuration constant using an immutable as const assertion.
- Export an asynchronous fetch helper enforcing request and response types.
- Export both the service function and its configuration for external consumers.
import type {
ApiResponse, UserRecord
} from './contracts';
export const API_CONFIG = {
baseUrl: 'https://api.example.com/v1',
timeoutMs: 3000
} as const;
export async function fetchUserRecord(
userId: string
): Promise<ApiResponse<UserRecord>> {
const url = `${API_CONFIG.baseUrl}/users/${userId}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to load user: ${userId}`);
}
const data = await response.json();
return data as ApiResponse<UserRecord>;
}FAQ
Named exports require consumers to import symbols using exact names wrapped in braces (or explicitly aliased with as). A default export provides a single fallback value per file that consumers can rename arbitrarily, making named exports much easier to refactor across large codebases.
Use import type whenever you import symbols used purely as type annotations or generics. TypeScript guarantees that type-only imports are completely removed from the emitted JavaScript, reducing bundle size and preventing circular dependency side effects.
Path aliases in tsconfig.json guide TypeScript type checking and autocompletion, but the compiler does not rewrite import paths during output emission. Your runtime bundler—such as Vite, webpack, or esbuild—must be configured with corresponding path resolution plugins.
You can export a const object and a type alias sharing the same identifier from the same module. TypeScript tracks values and types in separate namespaces, allowing callers to import both without naming conflicts.
export const Role = { Admin: 'admin' } as const;
export type Role = (typeof Role)[keyof typeof Role];