Declarations & Package Exports

The Course
•
5 min read

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.

StepCommand inside the extracted folderWhat it checks
Buildnpm run buildSource, emitted JavaScript and declarations
Public consumernpm run checkBuild, then consumer.ts against the declarations
Runtimenpm testBoth 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.

Predict, then runTypeScript
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.

Find and fix the type errorTypeScript
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

Exercise

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.

Your workspaceTypeScript
// 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
One possible solutionTypeScript
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.

true

Trace 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.

Was this article helpful?

Your response is saved on this device.