A declaration file, ending .d.ts, describes exported types and values without implementing them. With declaration:true, the compiler emits these files beside the JavaScript. A consumer can check against them while Node executes the .js modules.
The download has a public index.ts exporting values normally and types with export type. package.json points its types export to dist/index.d.ts and import export to dist/index.js. The package is private, and all exercises remain local; no publication is required.
Build first, then check consumer.ts in a separate noEmit configuration. Its import uses the package’s own name, resolved through exports, so missing public exports and weakened generated declarations are visible. Importing internal source files would skip this packaging boundary.
Do not hand-edit generated declarations. Edit source, rebuild, then check the consumer and run the JavaScript tests. The browser editors combine definitions in one module; relative imports and package-resolution exercises belong in the downloaded local project.
Follow one function through compilation
These are local-file listings to compare, not three programs to paste into one editor. Source contains both the contract and implementation:
label.ts — authored source
export type Book = { readonly title: string };
export function label(book: Book): string {
return book.title.trim();
}
label.d.ts — generated contract
export type Book = { readonly title: string };
export declare function label(book: Book): string;
label.js — generated implementation
export function label(book) {
return book.title.trim();
}
The checker reads the declaration’s parameter and return types. Node executes the JavaScript body. The declaration’s declare means the function exists in the implementation; it does not implement the function. The alias and annotations disappear from JavaScript.
Read the package boundary
The gateway’s index.ts is a public entry point, often called a barrel. A re-export forwards an existing export rather than duplicating its definition:
export { createGateway } from "./gateway.js";
export type { AppEvent } from "./domain.js";
The type-only line disappears at runtime. The package.json below selects the built entry points; "." means the root import such as import { createGateway } from "typed-event-gateway", not a request for a relative file:
{
"name": "typed-event-gateway",
"private": true,
"type": "module",
"exports": {
".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" }
}
}
Here name also lets this package import itself for the consumer test. The types target is for checking, the import target is for ES module execution, and private prevents accidental npm publication. This is local package resolution, not a network download.
The declaration-files introduction gives more background. You can complete the browser exercises first and try the optional local workflow on Day 7.
| Step | Command inside the extracted folder | What it checks |
|---|---|---|
| Build | npm run build | Source, emitted JavaScript and declarations |
| Public consumer | npm run check | Build, then consumer.ts against the declarations |
| Runtime | npm test | Both previous layers, then public JavaScript behavior |
Predict, check, and run
Write the output before running. Use Check types first, then Check & run. Types disappear at runtime: explain both what the compiler checks and what the code actually does. Each editor is an independent ES module; await asynchronous work explicitly.
type Publisher<T> = { publish: (value: T) => boolean };
function usePublisher(publisher: Publisher<string>): boolean {
return publisher.publish("Types");
}
const implementation: Publisher<string> = {
publish: value => value.length > 0
};
console.log(usePublisher(implementation));Enable JavaScript to edit and type-check this example.
Check your prediction
true Read the compiler error
Predict the rejected relationship before checking. Read the real diagnostic and its source location; fix the contract or the value instead of hiding it with an assertion.
type Publisher<T> = { publish: (value: T) => boolean };
const publisher: Publisher<number> = { publish: value => String(value) };
console.log(publisher);Enable JavaScript to edit and type-check this example.
Check your diagnosis
The implementation returns string while the public declaration promises boolean. A declaration is a contract the implementation must satisfy.
Build it yourself
Declarations & Package Exports: practise
Write a public Publisher<T> type with publish:(value:T)=>boolean, then an implementation accepting numbers. Print true for publish(0). Explain which part belongs in emitted .d.ts and which part must exist in .js.
// Start from the stated contract. Add a valid call and an edge case.Enable JavaScript to edit and type-check this example.
Hint 1: the idea
The declaration describes the parameter and result; it does not implement publish.
Hint 2: a first step
Keep the public method a function-valued property and supply a concrete implementation.
Show one solution
type Publisher<T> = { publish: (value: T) => boolean };
const publisher: Publisher<number> = { publish: value => value >= 0 };
console.log(publisher.publish(0));Enable JavaScript to edit and type-check this example.
trueTrace the implementation against the task before comparing styles. A passing example supports that case; use the changes below to challenge the wider contract.
Change it and test it
In a local copy, remove createGateway from index.ts and run npm run check. The build may already fail because main.ts also imports the public entry point; that is an expected public-contract failure, not a broken test setup. Restore the export, rerun check, and inspect how dist/index.d.ts forwards the generated gateway declaration. Never repair a missing export by editing dist directly.
Completion is saved in this browser only. It does not measure mastery.