Runtime#
There is no TypeScript runtime. The operator’s source is
type-checked, then compiled (by tsc, esbuild, swc,
vite, or built-in to tsx / deno / bun) to
JavaScript, then executed on the JavaScript runtime (see
Runtime). Types are erased
during compilation; nothing TypeScript-specific exists at
runtime.
Type erasure#
The compiler strips annotations, type declarations,
interface declarations, as assertions, access modifiers,
and generic type parameters. The output is plain JavaScript that
runs on V8, JavaScriptCore, or SpiderMonkey.
interface User { id: string; name: string; }
const u: User = {id: "1", name: "rk"};
// ^^^^ erased
// emits: const u = {id: "1", name: "rk"};
A few constructs do emit runtime code: enum (numeric and
string), class with parameter properties (assignment to
this.x), namespace (IIFE), and decorators (with
experimentalDecorators or the standard decorator proposal).
tsc and tsconfig#
tsc is the reference compiler. tsconfig.json configures
type-checking and code generation.
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"noUncheckedIndexedAccess": true,
"esModuleInterop": true,
"isolatedModules": true,
"skipLibCheck": true,
"outDir": "dist",
"rootDir": "src",
"declaration": true,
"sourceMap": true
},
"include": ["src/**/*"]
}
The fields that matter most.
Option |
Effect |
|---|---|
|
Enables every strict check ( |
|
|
|
JS syntax level the output uses. |
|
Module syntax emitted. |
|
How |
|
Forces each file to be compilable independently. Required
by single-file compilers ( |
|
Emit |
|
Skip type-checking |
Run tsc --noEmit in CI for type-checking, and
uses a faster compiler (esbuild / swc) for the JavaScript
output.
Module systems#
TypeScript follows JavaScript: ESM is the modern default, CJS is the older alternative.
// ESM
export function greet(name: string): string { return `hello, ${name}`; }
import {greet} from "./greet.js"; // .js, even in source
For projects targeting Node ESM, write .js in
import specifiers (TypeScript looks up ./greet.ts and emits
./greet.js). For bundler projects (Vite, esbuild), the
extension is optional.
import type and export type are erased at compile time.
import type {Config} from "./config.js"; // erased
import {load} from "./loader.js"; // runtime import
For projects with isolatedModules: true, type-only imports
must be marked explicitly; tools (@typescript-eslint)
auto-fix.
Declaration files (.d.ts)#
A .d.ts file declares types without code. Used to describe
external JavaScript libraries (@types/* packages) and to
ship type info for the operator’s own compiled libraries.
// env.d.ts
declare module "*.svg" {
const content: string;
export default content;
}
declare global {
namespace NodeJS {
interface ProcessEnv {
readonly DATABASE_URL: string;
}
}
}
export {};
tsc --declaration emits .d.ts files for the project’s
own modules; downstream consumers import them automatically when
the package’s types field in package.json points at
them.
package.json for libraries#
A typed library declares both runtime entry points and type entry points.
{
"name": "myscan",
"version": "0.1.0",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"scripts": {
"build": "tsc -p tsconfig.build.json",
"check": "tsc --noEmit"
}
}
Running TypeScript directly#
tsx, ts-node, deno, and bun each strip types
on-the-fly. Use tsx for Node-host scripts and
deno / bun when they own the project.
$ npx tsx src/main.ts
$ deno run --allow-net src/main.ts
$ bun run src/main.ts
tsx does not type-check; the operator pairs it with tsc
--noEmit in CI.
Runtimes that type-check on load#
Deno and Bun support running .ts directly and surface type
errors as part of execution (configurable). For Node, the
type-check is always a separate tsc pass.
JIT and codegen#
The same V8 / JavaScriptCore JIT runs the emitted code; the operator’s optimisation knobs are the JavaScript ones (see Runtime). TypeScript adds no runtime overhead.