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.

Tips

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

Warnings

  1. 01Path aliases require both <code>tsconfig.json</code> and your bundler to agree, or imports will work in the IDE but fail at runtime.

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.

FAQ