# Compiler, modules and DOM boundaries
# A browser-oriented configuration
This tsconfig.json is for TypeScript 5.x with a bundler, not a Node CommonJS program. Install TypeScript locally in the example application; the TypeScript and Webpack configs describe different parts of the same build.
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["ES2020", "DOM"],
"strict": true,
"noEmitOnError": true,
"sourceMap": true,
"outDir": "dist"
},
"include": ["src/**/*"]
}
| Option | Why it matters |
|---|---|
target | Chooses the JavaScript syntax level to emit |
module / moduleResolution | Preserve imports for the bundler and resolve them accordingly |
lib | Supplies type descriptions for language and browser APIs |
strict | Enables stronger checks, including nullability and implicit any checks |
noEmitOnError | Do not emit through tsc when there are type errors |
sourceMap / outDir | Debug original source; keep emitted files outside the source tree |
lib describes APIs; it does not install them. A lower target can transform syntax but does not polyfill APIs such as fetch.
With TypeScript installed, these npm scripts expose its local CLI:
{
"scripts": {
"typecheck": "tsc --noEmit",
"compile": "tsc"
}
}
npm run typecheck checks without writing output; npm run compile emits separate JS files. Webpack will bundle them via its loader pipeline instead. Do not set permanent noEmit: true when using a loader that expects TypeScript to emit.
# Explicit modules and a safe DOM lookup
Use the Student class from Types and contracts in src/index.ts:
import { Student } from './Student';
import type { Greeter } from './Student';
const students: Greeter[] = [new Student('Ada', 1)];
const root = document.getElementById('root');
if (root === null) {
throw new Error('Missing #root element');
}
root.textContent = students.map(student => student.greeting()).join('\n');
The HTML must contain an element with id="root", and the script must run after it exists. The null check both protects runtime execution and narrows the type. Writing document.getElementById('root')! only suppresses the compiler warning; it cannot make a missing node appear.
For a specific control, check element instanceof HTMLInputElement before reading value. Prefer textContent for data that should be displayed as text. .d.ts files describe types for code elsewhere; they do not supply runtime objects. Explicit exports/imports avoid accidentally creating global interfaces.
# JSON needs runtime validation
An API response does not become trustworthy because we write as StudentData[]. This standalone example checks the shape before returning typed values; its URL matches the Koa endpoint:
type StudentData = { id: number; name: string };
function isStudent(value: unknown): value is StudentData {
if (typeof value !== 'object' || value === null) return false;
const item = value as Record<string, unknown>;
return typeof item.id === 'number' && Number.isFinite(item.id)
&& typeof item.name === 'string';
}
async function loadStudents(): Promise<StudentData[]> {
const response = await fetch('/api/students');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data: unknown = await response.json();
if (!Array.isArray(data) || !data.every(isStudent)) {
throw new Error('Invalid student response');
}
return data;
}
The Record assertion lets us inspect properties; the actual field checks establish the contract. Callers still handle loading and errors as covered in Async HTTP. A shape check is not a substitute for application-specific validation or authorization.
Try it: remove the HTML target or change an API ID to a string. Which error can TypeScript detect, and which must be detected at runtime?
TSConfig reference (opens new window) · Strict null checks (opens new window)