Technology · TypeScript

TypeScript Modules

Learn how to organize TypeScript code using ES modules, named exports, and default exports.

TL;DR
  1. 01Use export and import to share code between files.
  2. 02Choose named exports for multiple values per module.
  3. 03Use type-only imports to keep runtime bundles small.

Named Exports

  • Add the export keyword in front of any declaration to share it.
    // utils.ts
    export const PI = 3.14159;
    export function double(n: number) {
      return n * 2;
    }
  • Import named exports using curly braces and the exact names.
    import { PI, double } from "./utils";
  • Rename imports inline with the as keyword to avoid conflicts.
    import { double as multiplyByTwo } from "./utils";
  • Export multiple values from one file for related helpers and constants.
  • This is the most common style in modern TypeScript codebases.

Default Exports

  • Use export default to mark one main export per file.
    // logger.ts
    export default function log(msg: string) {
      console.log(msg);
    }
  • Import the default with any name you choose, without curly braces.
    import log from "./logger";
  • A file can have one default export and any number of named exports.
  • Default exports lose the original name during import, which hurts refactoring.
  • Many style guides recommend named exports over defaults for that reason.

Re-exports

  • Re-export from another file to build a barrel that groups related modules.
    // index.ts
    export { double, PI } from "./utils";
    export { default as log } from "./logger";
  • Use export * from to re-export every named export from a module.
    export * from "./utils";
  • Barrels make imports cleaner across the rest of your codebase.
  • They can also hurt tree-shaking, so use them carefully in large projects.
  • Place barrel files at the root of folders for predictable import paths.

Type-only Imports

  • Use the type keyword to import only types, not runtime values.
    import type { User } from "./types";
  • Type-only imports are erased during compilation for smaller bundles.
  • Mix value and type imports in one statement using the inline type keyword.
    import { type User, getUser } from "./api";
  • Use export type to re-export types without runtime code.
    export type { User } from "./types";
  • This is especially helpful when working with bundlers and isolated modules.

Module Resolution

  • Use relative paths like ./utils for files inside your project.
  • Use bare specifiers like react for packages installed in node_modules.
  • Configure path aliases in tsconfig.json to shorten deep import paths.
    import { Button } from "@/components/Button";
  • The moduleResolution setting controls how the compiler finds modules.
  • Use "bundler" mode in modern projects that rely on tools like Vite or Webpack.

Tip: Prefer named exports over default exports, since they keep import names consistent and make refactoring much safer.

Warning: Path aliases require both tsconfig.json and your bundler to agree, or imports will work in the IDE but fail at runtime.

TypeScript InterfacesTypeScript Tsconfig Options