Learn / TypeScript and React Product Systems
TypeScript Modules, Packages, and Type Tests
A consolidated foundations lesson preserving 4 focused PTesting lessons without duplicating an unrelated authored PTLearn topic.
Course: TypeScript and React Product Systems. Level: Intermediate. Topic: Frontend systems.
Stage: basic - Foundation - Language and runtime foundations. Connect typescript modules, packages, and type tests to the professional workflow for TypeScript React.
Outcomes
- Explain what TypeScript checks and what JavaScript still does at runtime.
- Choose strict compiler options for application and library code.
- Separate type checking, emitting, linting, formatting, and test execution.
- Explain how TypeScript module settings relate to Node.js module behavior.
- Use package exports to define public entry points.
- Avoid accidental deep imports and unstable internal APIs.
- Structure tests around behavior instead of implementation details.
- Use typed fixtures and builders to reduce invalid test setup.
- Understand where type-level tests fit for libraries and helpers.
- Understand declaration files and source maps.
- Choose between tsc, bundlers, and runtime transpilers.
- Prepare package metadata for internal and public consumers.
Concepts
- Orientation and Tooling
- TypeScript is a design-time contract
- Compiler settings that raise the floor
- TypeScript Advanced foundations
- Guided practice
- Module Systems and Boundaries
- Module settings are runtime decisions
- Type-only imports
- Testing TypeScript
- Behavior tests still matter
- Type tests for reusable helpers
- Package Tooling
- Application builds vs library builds
- Runtime transpilers
Concept flow
Show how language and runtime foundations moves from trigger to implementation outcome in TypeScript React.
- Language model
- Runtime behavior
- Engineering decision
- Verification evidence
Session flow
- Model Orientation and Tooling (concept, 61 min) — Name the decisions behind Orientation and Tooling before writing code.
- Explain what TypeScript checks and what JavaScript still does at runtime.
- Explain where Orientation and Tooling belongs in learning analytics dashboard.
- Build the vertical slice (walkthrough, 110 min) — Implement the smallest useful slice in legacy/typescript-react/typescript-modules-packages-and-type-tests.txt.
- Choose strict compiler options for application and library code.
- Connect TypeScript is a design-time contract to a working example.
- Verify and harden (exercise, 73 min) — Accepted answers: exports
- Separate type checking, emitting, linting, formatting, and test execution.
- Record one risk or follow-up before moving on.
- Strict TypeScript in a Real Workflow: TypeScript is a design-time contract (concept, 28 min) — TypeScript analyzes source files before code runs. It can prove many program facts, but it cannot validate network data, database rows, environment variables, or JSON by itself. Treat the compiler as a fast reviewer for code you own, then add runtime validation at trust boundaries.
- Use unknown, not any, when data enters from outside the program.
- A type assertion is a claim. A parser is evidence.
- Retained source example: Static confidence, runtime input
type User = { id: string; email: string };
async function loadUser(): Promise<User> {
const response = await fetch('/api/user');
const body: unknown = await response.json();
// The next step must validate body before returning User.
return body as User;
}
The cast silences the compiler but does not inspect the JSON. A production version should parse and validate body first.
- Strict TypeScript in a Real Workflow: Compiler settings that raise the floor (walkthrough, 28 min) — The best learning path is to enable strictness early. The most useful flags prevent accidental null access, unchecked indexed reads, implicit any types, and sloppy optional property handling.
- Retained source example: Application tsconfig baseline
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"isolatedModules": true,
"skipLibCheck": true
}
}
- Strict TypeScript in a Real Workflow: practices and mistakes (review, 5 min) — Retained guidance from the legacy PTesting lesson.
- Practice: Start new projects with strict enabled instead of migrating later.
- Practice: Run typecheck in CI even when a bundler transpiles TypeScript.
- Practice: Document every intentional type assertion near the boundary where it is made.
- Avoid: Assuming TypeScript validates API responses at runtime.
- Avoid: Using any to make early design uncertainty disappear.
- Avoid: Mixing build, lint, and test failures into one opaque script.
- Strict TypeScript in a Real Workflow: references (review, 2 min) — Original references retained from the legacy library.
- TypeScript tsconfig reference: https://www.typescriptlang.org/tsconfig/
- TypeScript handbook: The basics: https://www.typescriptlang.org/docs/handbook/2/basic-types.html
- ESM, CommonJS, and Package Design: Module settings are runtime decisions (concept, 30 min) — TypeScript does not invent a module runtime. It emits JavaScript that Node.js, a bundler, or another runtime must load. For modern Node.js, NodeNext settings align TypeScript resolution with package.json type and exports behavior.
- Retained source example: Node ESM package
{
"type": "module",
"exports": {
".": "./dist/index.js",
"./testing": "./dist/testing.js"
},
"types": "./dist/index.d.ts"
}
- ESM, CommonJS, and Package Design: Type-only imports (walkthrough, 30 min) — Types disappear after compilation. Use type-only imports when an import is only needed by the checker, especially in libraries and isolated module transforms.
- Retained source example: Avoid unnecessary runtime imports
import type { RequestContext } from './context.js';
import { createLogger } from './logger.js';
export function handler(context: RequestContext) {
return createLogger(context.requestId);
}
- ESM, CommonJS, and Package Design: practices and mistakes (review, 5 min) — Retained guidance from the legacy PTesting lesson.
- Practice: Use explicit file extensions in ESM imports for Node-targeted output.
- Practice: Export only stable public entry points from package.json.
- Practice: Keep internal modules reachable within the package but undocumented for consumers.
- Avoid: Changing tsconfig module output without checking runtime behavior.
- Avoid: Publishing internals that consumers start depending on.
- Avoid: Using value imports for types under isolated module transforms.
- ESM, CommonJS, and Package Design: references (review, 2 min) — Original references retained from the legacy library.
- TypeScript handbook: Modules: https://www.typescriptlang.org/docs/handbook/modules.html
- Node.js packages documentation: https://nodejs.org/api/packages.html
- Unit, Integration, and Type Tests: Behavior tests still matter (concept, 35 min) — The compiler cannot prove that business rules, SQL queries, HTTP calls, or validation messages are correct. Test the observable behavior at the lowest level that gives confidence.
- Retained source example: Typed test fixture
import { describe, expect, it } from 'vitest';
const user = {
id: 'u_1',
email: 'ada@example.com',
} satisfies User;
describe('displayEmail', () => {
it('normalizes casing', () => {
expect(displayEmail(user)).toBe('ada@example.com');
});
});
- Unit, Integration, and Type Tests: Type tests for reusable helpers (walkthrough, 35 min) — Libraries and advanced type helpers can use type tests to make sure inference stays stable. These tests check compiler behavior, not runtime behavior.
- Retained source example: Expected type failure
// @ts-expect-error age must be a number
const badUser: User = { id: 'u_2', email: 'x@example.com', age: 'old' };
- Unit, Integration, and Type Tests: practices and mistakes (review, 5 min) — Retained guidance from the legacy PTesting lesson.
- Practice: Use satisfies for fixture literals that should be checked but remain precise.
- Practice: Keep builders small and override-friendly.
- Practice: Test validators with both valid and invalid runtime data.
- Avoid: Assuming passing typecheck means business behavior is tested.
- Avoid: Using as User in fixtures, which can hide invalid setup.
- Avoid: Mocking so deeply that tests no longer cover integration boundaries.
- Unit, Integration, and Type Tests: references (review, 2 min) — Original references retained from the legacy library.
- Vitest guide: https://vitest.dev/guide/
- TypeScript comments directives: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-9.html
- Builds, Declarations, and Scripts: Application builds vs library builds (concept, 30 min) — Applications often transpile for one runtime and deploy the result. Libraries must publish JavaScript plus declaration files that accurately describe the public API.
- Retained source example: Declaration output
{
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "dist",
"rootDir": "src"
}
}
- Builds, Declarations, and Scripts: Runtime transpilers (walkthrough, 30 min) — Tools that run TypeScript directly can speed up development, but many transpile without full type checking. Keep a separate typecheck command so local speed does not remove correctness checks.
- Retained source example: CI-friendly scripts
{
"scripts": {
"dev": "tsx watch src/server.ts",
"typecheck": "tsc --noEmit",
"build": "tsc -p tsconfig.json",
"prepublishOnly": "npm run typecheck && npm run build"
}
}
- Builds, Declarations, and Scripts: practices and mistakes (review, 5 min) — Retained guidance from the legacy PTesting lesson.
- Practice: Publish only intended build artifacts with the files field.
- Practice: Generate declaration maps for libraries when source debugging matters.
- Practice: Run typecheck before publish or deploy.
- Avoid: Shipping source TypeScript without a clear runtime plan.
- Avoid: Publishing stale declaration files.
- Avoid: Relying on dev transpilation as the only correctness check.
- Builds, Declarations, and Scripts: references (review, 2 min) — Original references retained from the legacy library.
- npm package.json docs: https://docs.npmjs.com/cli/v10/configuring-npm/package-json
- TypeScript declaration files: https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html
Code example
ts in legacy/typescript-react/typescript-modules-packages-and-type-tests.txt.
const track: string = "PTLearn foundation";
console.log(track);
Walkthrough examples
- TypeScript Modules, Packages, and Type Tests in a learning analytics dashboard — A team is extending a resilient React product surface and needs this lesson's pattern to be clear enough for review, testing, and future maintenance.
- File: legacy/typescript-react/typescript-modules-packages-and-type-tests.txt
- File: tests/typescript-modules-packages-and-type-tests.spec
- File: docs/typescript-react/typescript-modules-packages-and-type-tests.md
- Start from the provided ts snippet and make the intent visible in names and boundaries.
- Apply the checklist item "Explain the language or runtime rule in your own words" before adding extra behavior.
- Write down how the implementation changes when TypeScript is a design-time contract fails or becomes slow.
- Static confidence, runtime input — The cast silences the compiler but does not inspect the JSON. A production version should parse and validate body first.
- Retained source code:
type User = { id: string; email: string };
async function loadUser(): Promise<User> {
const response = await fetch('/api/user');
const body: unknown = await response.json();
// The next step must validate body before returning User.
return body as User;
}
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
- Application tsconfig baseline — Additional example retained from the legacy lesson.
- Retained source code:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"isolatedModules": true,
"skipLibCheck": true
}
}
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
- Separate scripts by responsibility — Type checking and emitting are different tasks. Keeping scripts separate makes CI failures easier to diagnose.
- Retained source code:
{
"scripts": {
"typecheck": "tsc --noEmit",
"build": "tsc -p tsconfig.json",
"test": "vitest run",
"lint": "eslint ."
}
}
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
- Node ESM package — Additional example retained from the legacy lesson.
- Retained source code:
{
"type": "module",
"exports": {
".": "./dist/index.js",
"./testing": "./dist/testing.js"
},
"types": "./dist/index.d.ts"
}
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
- Avoid unnecessary runtime imports — Additional example retained from the legacy lesson.
- Retained source code:
import type { RequestContext } from './context.js';
import { createLogger } from './logger.js';
export function handler(context: RequestContext) {
return createLogger(context.requestId);
}
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
- Public barrel with intentional exports — Additional example retained from the legacy lesson.
- Retained source code:
export { createClient } from './client.js';
export type { ClientOptions, ClientResult } from './types.js';
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
- Typed test fixture — Additional example retained from the legacy lesson.
- Retained source code:
import { describe, expect, it } from 'vitest';
const user = {
id: 'u_1',
email: 'ada@example.com',
} satisfies User;
describe('displayEmail', () => {
it('normalizes casing', () => {
expect(displayEmail(user)).toBe('ada@example.com');
});
});
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
- Expected type failure — Additional example retained from the legacy lesson.
- Retained source code:
// @ts-expect-error age must be a number
const badUser: User = { id: 'u_2', email: 'x@example.com', age: 'old' };
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
- Builder keeps tests readable — Additional example retained from the legacy lesson.
- Retained source code:
function buildUser(overrides: Partial<User> = {}): User {
return { id: 'u_test', email: 'test@example.com', ...overrides };
}
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
- Declaration output — Additional example retained from the legacy lesson.
- Retained source code:
{
"compilerOptions": {
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "dist",
"rootDir": "src"
}
}
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
- CI-friendly scripts — Additional example retained from the legacy lesson.
- Retained source code:
{
"scripts": {
"dev": "tsx watch src/server.ts",
"typecheck": "tsc --noEmit",
"build": "tsc -p tsconfig.json",
"prepublishOnly": "npm run typecheck && npm run build"
}
}
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
- Files field for publishing — Additional example retained from the legacy lesson.
- Retained source code:
{
"files": ["dist", "README.md", "LICENSE"],
"main": "./dist/index.js",
"types": "./dist/index.d.ts"
}
- Explain the expected behavior.
- Compare the example with the canonical PTLearn implementation.
Practice
- Choose strict flags: List three compiler options from this lesson and state the risk each option reduces. The guided runner checks for option names only.
- Hint: Start with strict.
- Hint: Think about arrays, optional properties, and module transforms.
- Accepted answers: strict | noUncheckedIndexedAccess | exactOptionalPropertyTypes | isolatedModules
- Name the boundary: Which package.json field defines public package entry points for consumers? The guided runner checks the field name.
- Accepted answers: exports
- Apply: Explain how TypeScript module settings relate to Node.js module behavior.
- Classify a test: A test that uses @ts-expect-error to verify a bad assignment fails is what kind of test? The guided runner checks the phrase.
- Accepted answers: type test | type-level
- Apply: Structure tests around behavior instead of implementation details.
- Identify declaration metadata: Which package.json field points consumers to generated TypeScript declarations? The guided runner checks the field name.
- Accepted answers: types
- Apply: Understand declaration files and source maps.
Checklist
- Explain the language or runtime rule in your own words
- Run one focused example and record its output
- Name one failure mode or tradeoff
- Keep the verification evidence with the lesson
Quiz prompts
- A teammate wants to hide Orientation and Tooling inside a convenient helper. What should you check first? — Place Orientation and Tooling at the boundary that keeps learning analytics dashboard behavior explicit, testable, and reviewable.
- Which artifact best proves this TypeScript React lesson is ready for review? — Production-ready learning needs evidence: a test, trace, command, screenshot, or log that catches the risk again.
- Why is unknown preferred over any for external JSON? — unknown preserves safety by requiring proof before property access or assignment.
- Why use import type? — Type-only imports keep emitted JavaScript clean and explicit.
- What does @ts-expect-error do? — If the compiler no longer reports an error there, @ts-expect-error itself fails.
- Why keep tsc --noEmit when using a fast runtime transpiler? — Fast transpilation and type checking are often separate concerns.
Flashcards
- In TypeScript React, what should you remember about Orientation and Tooling? Orientation and Tooling matters here because it supports "Explain what TypeScript checks and what JavaScript still does at runtime.".
- In TypeScript React, what should you remember about TypeScript is a design-time contract? TypeScript is a design-time contract matters here because it supports "Choose strict compiler options for application and library code.".
- In TypeScript React, what should you remember about Compiler settings that raise the floor? Compiler settings that raise the floor matters here because it supports "Separate type checking, emitting, linting, formatting, and test execution.".
- In TypeScript React, what should you remember about TypeScript Advanced foundations? TypeScript Advanced foundations matters here because it supports "Explain how TypeScript module settings relate to Node.js module behavior.".
Labs
- Ship a typescript modules, packages, and type tests slice — Extend a resilient React product surface with a small but reviewable feature that proves the lesson's architecture in code.
- Choose strict flags: List three compiler options from this lesson and state the risk each option reduces. The guided runner checks for option names only.
- Hint: Start with strict.
- Hint: Think about arrays, optional properties, and module transforms.
- Accepted answers: strict | noUncheckedIndexedAccess | exactOptionalPropertyTypes | isolatedModules
- Name the boundary: Which package.json field defines public package entry points for consumers? The guided runner checks the field name.
- Accepted answers: exports
- The implementation demonstrates Orientation and Tooling without hidden global state.
- At least one test or verification step covers the main success path.
- The review notes explain the tradeoff behind TypeScript is a design-time contract.
Challenge
- Review-ready typescript modules, packages, and type tests (Core) — Turn the lesson work into a pull-request-sized change for learning analytics dashboard. Include the code, verification notes, one explicit tradeoff, and an updated concept diagram that names the riskiest handoff.
- All checklist items are either implemented or documented with a reason.
- The change can be understood from legacy/typescript-react/typescript-modules-packages-and-type-tests.txt plus one short note.
- The concept diagram names ownership, failure handling, and verification points.
- A teammate could run the verification steps without asking for hidden context.
Canonical lesson URL