Lesson 20 of 25
Modules and Declaration Files
Exporting and importing types alongside values, the import type syntax, and how a .d.ts file describes the shape of code without providing any implementation.
Modules in TypeScript are the same ES modules you already know from
JavaScript — import and export — with the type system layered
seamlessly on top, plus one addition specific to types themselves: a way to
describe a module's shape without any implementation at all.
Exporting and importing types
Types export and import exactly like values, using the same syntax:
// user.ts
export interface User {
name: string;
age: number;
}
export function createUser(name: string, age: number): User {
return { name, age };
}// main.ts
import { User, createUser } from "./user";
const user: User = createUser("Amara", 29);Nothing new so far — a type and a value were exported from the same file and imported together, the same statement handling both.
import type: making the type-only nature explicit
When an import is used purely as a type — never as a runtime value — import type says so explicitly:
import type { User } from "./user";
function printUser(user: User) {
console.log(user.name);
}Because User never appears as a runtime value anywhere in this file, the
entire import type statement is erased when compiling to JavaScript — there
is nothing left to import, so nothing is emitted. A regular import { User } from "./user" (without type) would, in most configurations, also be
erased automatically once the compiler notices User is only ever used as a
type — but writing import type makes that intent explicit rather than
relying on the compiler to figure it out, which matters more once a file
mixes type-only and value imports:
import { createUser } from "./user"; // a real runtime import — createUser is a function
import type { User } from "./user"; // erased entirely — User is type-onlyThis split is required in stricter configurations (notably isolatedModules,
common with fast transpilers that process one file at a time and can't
always tell on their own whether an import is type-only) and is good
practice regardless, since it documents the distinction for a reader too.
Declaration files: describing a shape with no implementation
A .d.ts file contains only type declarations — no runnable code at all.
It exists to describe the shape of something the compiler needs to check
against, without needing (or being able) to see how it's actually
implemented:
// math-utils.d.ts
export function add(a: number, b: number): number;
export function multiply(a: number, b: number): number;This is exactly what happens when you compile a regular .ts file with
declaration emission turned on (declaration: true in tsconfig.json) — the
compiler produces a matching .d.ts file alongside the .js output,
stripping every implementation and keeping only the shapes. This is how a
published TypeScript library ships type information to its consumers: the
shipped code is plain JavaScript, and a .d.ts file next to it tells
TypeScript what that JavaScript's types actually are.
Typing an untyped JavaScript module
Sometimes you depend on a plain JavaScript module with no types of its own —
older code, or a library that never adopted TypeScript. A hand-written
.d.ts file lets you describe its shape yourself:
// vendor-analytics.d.ts
declare module "vendor-analytics" {
export function track(event: string, properties?: Record<string, unknown>): void;
}// somewhere in your app
import { track } from "vendor-analytics";
track("lesson_completed", { lessonId: "modules-and-declaration-files" });declare module tells TypeScript "trust me about this module's shape" —
there's no checking against a real implementation, because there isn't one
available to check against. This is exactly the mechanism behind the next
lesson, on typing third-party packages properly.
Try it yourself
What to remember
- Types export and import through the same
import/exportsyntax as values;import typemakes a type-only import explicit and guarantees it's erased. - A
.d.tsfile describes shapes only — no runtime implementation — and is what a compiled TypeScript library ships alongside its plain JavaScript output. declare module "name" { ... }lets you hand-write types for an untyped JavaScript module you depend on.- A
.d.tsfile is only as accurate as whoever wrote it — the compiler cannot verify it against a real implementation it never sees.
Check yourself
4 questions · pass 3/4 to unlock Working with Third-Party Types
1.What does
import type { User } from "./types";do differently from a regular import?2.What does a .d.ts file (a declaration file) contain?
3.What is the purpose of a package's
@types/package-namecounterpart on npm?4.In an ES module, what makes a file a module rather than a script, as far as TypeScript and JavaScript are concerned?
4 left to answer