Modules & Type Imports

The Course
•
5 min read

The beginner local project introduced exports and imports. A type alias exists only during checking, so import it with import type { Book } from "./domain.js". Import runtime functions with an ordinary import such as import { decodeBook } from "./domain.js".

Type-only imports are erased from emitted JavaScript. They communicate dependency intent and avoid asking the runtime for a value that does not exist. A runtime class is different: a class can be both a constructor value and an instance type. You do not need classes for this catalogue.

The browser editors are independent single-file modules. Combine definitions in one box; do not paste relative project imports into them. Day 7’s download keeps domain.ts, service.ts and main.ts separate, with .js import extensions resolved to .ts source by the NodeNext compiler settings.

For example, domain.ts can export both type Book = { title: string } and a function label accepting Book. A caller imports label as a value and Book as a type. The self-contained equivalent below runs the same function without the file boundary.

The actual file boundary

These two listings belong in separate local files, not the single-file browser editor. The .js spelling names the emitted file Node will execute; the compiler finds domain.ts while checking.

domain.ts

export type Book = { title: string };

export function label(book: Book): string {
  return book.title;
}

main.ts

import type { Book } from "./domain.js";
import { label } from "./domain.js";
const book: Book = { title: "Modules" };

console.log(label(book));

After compilation, the Book import and annotations disappear. The label import, record and console call remain. Day 7 supplies a complete configured project so you can check this locally.

Predict, run, and explain

Write the output and trace the important state changes before checking it. Then run the program. Use Check types first; execution is allowed only when the real compiler accepts the source.

Predict, then runTypeScript
type Book = { title: string };

function label(book: Book): string {
  return book.title;
}

const book: Book = { title: "Modules" };

console.log(label(book));

Enable JavaScript to edit and type-check this example.

Check your prediction
Modules

Read the compiler error

The spelling names a type, not a runtime constructor or record.

Find and fix the type errorTypeScript
type Book = { title: string };

console.log(Book);

Enable JavaScript to edit and type-check this example.

Check your diagnosis

Print a Book value instead. Type aliases are erased; they cannot be used as values.

Types disappear while runtime collaborators remain

A type alias helps check source but does not become a JavaScript constructor. A runtime function can use that alias in its contract, yet still needs a real implementation. Keeping the two roles separate prevents attempts to print or import a type as if it were a value.

The local project splits declarations into modules; the browser version combines the same definitions into one file. Do not delete necessary runtime imports merely because type imports disappear. A decoder function must still be present when emitted code calls it.

Types disappear while runtime collaborators remain: traceTypeScript
type Book = { title: string };

function label(book: Book): string {
  return book.title.trim();
}

const book: Book = { title: " Types " };

console.log(label(book));
console.log(typeof label);

Enable JavaScript to edit and type-check this example.

Check your prediction
Types
function

Book and the annotations are erased. Label, the object and trim still exist and run. The type did not implement the normalization.

Types disappear while runtime collaborators remain: contrastTypeScript
type Book = { title: string };

function label(book: Book): string {
  return book.title;
}

const sameFunction = label;

console.log(sameFunction === label);
console.log(sameFunction({ title: "Modules" }));

Enable JavaScript to edit and type-check this example.

Check your prediction
true
Modules

An imported runtime function is still a function value, not a copied implementation generated by its parameter type. Module boundaries determine where it is supplied, while the contract checks how it is called.

Try the rule on new inputs

In the download, mark each import as type-only or runtime. Inspect emitted service.js and explain why the decoder import remains. Keep relative imports out of the single-file browser exercise.

Your turn

Exercise

Modules & Type Imports: practise

Write a Book alias with title and pages, then a pageLabel function returning Title: N pages. Print Types: 120 pages. After running, describe which declarations survive into JavaScript.

Your workspaceTypeScript
// Combine the declarations here; real import/export files are in the download.

Enable JavaScript to edit and type-check this example.

Hint 1: the idea

The alias disappears, but the function and record exist at runtime.

Hint 2: a first step

Use Book on the parameter and string on the return contract.

Show one solution
One possible solutionTypeScript
type Book = {
  title: string;

  pages: number;
};

function pageLabel(book: Book): string {
  return `${book.title}: ${book.pages} pages`;
}

console.log(pageLabel({ title: "Types", pages: 120 }));

Enable JavaScript to edit and type-check this example.

Types: 120 pages

Explain why the implementation meets the stated contract. Which part is enforced by the language or checker, and which part needs behavioral evidence?

Change it and test it

In a local copy, export Book and pageLabel from one module and import them with the appropriate type/value forms. Run checking before execution. Explain why typeof Book cannot inspect a type alias at runtime.

Was this article helpful?

Your response is saved on this device.