Learn TypeScript - Namespaces, Modules, and Type Imports/Exports
Episode 10 of 23

Learn TypeScript - Namespaces, Modules, and Type Imports/Exports

This episode covers organizing TypeScript code: modern ES modules with import and export, type-only imports and exports, namespaces for ambient declarations, and the right module configuration. You'll understand the recommended organization patterns.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Code crammed into a single file quickly becomes unmaintainable. Episode 10 covers how to split a program into modules — units of code that import each other — and how to export types alongside values. This organization decides whether a project can grow or dies from tangled dependencies.

TypeScript supports two organizational systems: modern ES modules, which are the standard, and namespaces, now more often used for global ambient declarations. Understanding both, plus when to use each, is part of maturity as a TypeScript developer.

Episode 10 examines import and export syntax, type-only imports and exports to stay safe with modern compilers, and module configuration in tsconfig.

Modern ES Modules

import and export

Every file with an import or export statement is a module:

Ekspor dari modul
export interface Pengguna {
    nama: string;
}
 
export const versi = "1.0.0";
 
export function sapa(nama: string): string {
    return `Halo, ${nama}`;
}

The file above exports an interface, a constant, and a function. Other modules import them with the import keyword. With modules, every file has its own scope; only exported symbols can be used outside the file.

JSImpor di file lain
import { Pengguna, sapa } from "./pengguna";
 
const pengguna: Pengguna = { nama: "Budi" };
console.log(sapa(pengguna.nama));

The from "./pengguna" clause refers to the file path without an extension. The compiler and bundler resolve that path during build. Named imports make dependencies explicit and easy to trace.

Default Exports

A module can also export a single main value with export default:

Default export
export default class ApiClient {
    constructor(private baseUrl: string) {}
}

A default import doesn't use curly braces. Each module may have only one default export. This pattern is common for components, main classes, or a single function at the heart of a file.

Type-Only Imports and Exports

TypeScript separates values and types. This enables imports that only bring in types:

Import khusus tipe
import type { Pengguna } from "./pengguna";
 
const data: Pengguna = { nama: "Sari" };

The declaration import type states that only types are brought in, not runtime values. At compile time, this import is removed entirely. That matters for compilers with isolatedModules and for keeping bundle size down.

Inline syntax is also supported for clarity:

Inline type import
import { sapa, type Pengguna } from "./pengguna";

On the same line, sapa is imported as a value and Pengguna as a type. This pattern is concise and clear, widely used in modern codebases.

Namespaces

Namespaces for Ambient Declarations

A namespace groups symbols in one global scope:

Namespace
namespace Utilitas {
    export function formatAngka(n: number): string {
        return n.toLocaleString("id-ID");
    }
 
    export const satuan = "IDR";
}

A namespace places functions and constants under a single name Utilitas. This was once the main way to organize code. Today, ES modules are preferred for application code because they work better with bundlers and force explicit imports.

When Namespaces Still Matter

Namespaces remain useful for environment declaration files, especially when typing global variables:

Namespace ambient
declare namespace GlobalNya {
    interface Config {
        apiKey: string;
    }
}

A declare namespace block describes the shape of a global object without producing runtime code. This pattern is used by many classic libraries and ambient declarations. For applications you write yourself, ES modules remain the primary choice.

Module Configuration in tsconfig

Module compilation behavior is controlled from tsconfig:

Konfigurasi module
{
    "compilerOptions": {
        "module": "NodeNext",
        "moduleResolution": "NodeNext"
    }
}

The module option determines the JavaScript output format, for example NodeNext for Node.js and ESNext for modern bundlers. The moduleResolution option determines how the compiler finds import files. Both must match the runtime environment you use.

Tip

If you're unsure what to pick, let the tooling decide: scaffolding like bun create next-app or npx tsc --init already sets module and moduleResolution that are right for your project.

Closing

Episode 10 keeps your code organized: ES modules for application code, type-only imports and exports for modern compiler safety, and namespaces for ambient declarations. You also understand module settings in tsconfig.

Key takeaways:

  • A file with import or export is a module with its own scope.
  • export default provides one main value per module.
  • import type removes type imports at compile time.
  • Inline type imports mix values and types in one line.
  • Namespaces today are mainly for global ambient declarations.
  • module and moduleResolution must match the runtime.

In the next episode 11 we'll discuss type guards, narrowing, and type flow control — how to safely narrow a union type down to a concrete type inside branches.

Learn TypeScript - Namespaces, Modules, and Type Imports/Exports | Learn TypeScript