Learning Guides
Menu

Modules and Namespaces

3 min readEffective TypeScript

Modules and Namespaces

Modern TypeScript is built on ES modules. Namespaces are mostly legacy, but you’ll still encounter them in older codebases.

Item 101: Prefer ES Modules

TYPESCRIPT
// math.ts
export function add(a: number, b: number) {
  return a + b;
}
 
// main.ts
import { add } from "./math";

Why it matters: ES modules are the standard across modern tooling. They enable tree‑shaking, faster bundling, and predictable import behavior.

This is the standard pattern for modern tooling.

Item 102: Avoid namespace in New Code

Namespaces were used before ES modules. They don’t work well with bundlers:

TYPESCRIPT
// Avoid this
namespace Utils {
  export function sum(a: number, b: number) {
    return a + b;
  }
}

Why it matters: namespaces create global pollution and don’t align with bundlers or code splitting. ES modules solve the same problem in a standard way.

Item 103: Use import type for Type‑Only Imports

TYPESCRIPT
import type { User } from "./types";

Why it matters: type‑only imports are erased at runtime, reducing bundle size and preventing side effects from unnecessary imports.

This prevents unwanted runtime imports and helps tree-shaking.

Item 104: Use export type for Clean Re‑exports

TYPESCRIPT
export type { User } from "./types";
export { fetchUser } from "./api";

Why it matters: this keeps your public API tidy without accidentally re‑exporting runtime values you don’t intend to expose.

Item 105: Be Careful With Barrel Files

Barrels (index.ts) simplify imports but can create circular dependencies. Keep them small and stable.

Why it matters: circular dependencies cause confusing runtime bugs and broken type inference. Direct imports are safer in large codebases.

Item 106: Avoid export default in Shared Libraries

Named exports are easier to refactor and search, and reduce confusion in IDEs.

Why it matters: default exports can be renamed arbitrarily at import sites, which makes refactors harder and reduces discoverability.

Item 107: Understand exports in package.json

When publishing libraries, configure your package entry points:

JSON
{
  "name": "my-lib",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "default": "./dist/index.js"
    }
  }
}

Why it matters: correct exports ensures consumers get the right JS + types and avoids “cannot find module” issues in modern bundlers.

This ensures TypeScript and bundlers resolve the right files.

Scenario: Avoiding Circular Imports

TYPESCRIPT
// BAD: barrel importing everything
import { User } from "./index";
 
// GOOD: import directly
import type { User } from "./types";

Key Takeaways

  1. ES modules are the modern standard.
  2. Avoid namespaces in new code.
  3. Use import type and export type for type-only flows.
  4. Be cautious with barrels to avoid cycles.
  5. Prefer named exports in libraries.

Next: declaration files deep dive.