Production-Grade TypeScript: Architecture, Tooling, and Ecosystem
The final frontier of TypeScript engineering. This guide moves from code to infrastructure: manipulating the AST with the Compiler API, optimizing compilation speeds, setting up enterprise-grade monorepos, and bridging the gap between compile-time checks and runtime reality with Zod and tRPC.
TypeScript Senior/Expert Level Topics
Advanced Generic Patterns
Advanced generics involve conditional types, mapped types, and recursive type definitions to create flexible, reusable type abstractions that adapt based on input types.
// Conditional + Mapped + Recursive type DeepReadonly<T> = { readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K]; }; type UnwrapPromise<T> = T extends Promise<infer U> ? UnwrapPromise<U> : T; type Result = UnwrapPromise<Promise<Promise<string>>>; // string
Higher-Kinded Types Simulation
TypeScript lacks native HKTs, but we simulate them using interface merging and "type lambdas" through a registry pattern to abstract over type constructors.
// HKT simulation via URI registry interface URItoKind<A> { Array: Array<A>; Option: Option<A>; } type URIS = keyof URItoKind<any>; type Kind<F extends URIS, A> = URItoKind<A>[F]; interface Functor<F extends URIS> { map: <A, B>(fa: Kind<F, A>, f: (a: A) => B) => Kind<F, B>; }
Type-Level Programming
Using TypeScript's type system as a compile-time programming language to perform computations, validations, and transformations entirely at the type level.
// Type-level arithmetic type BuildTuple<N, T extends any[] = []> = T['length'] extends N ? T : BuildTuple<N, [...T, unknown]>; type Add<A extends number, B extends number> = [...BuildTuple<A>, ...BuildTuple<B>]['length']; type Sum = Add<3, 4>; // 7 // Type-level string parsing type ParseInt<S> = S extends `${infer N extends number}` ? N : never; type Five = ParseInt<"5">; // 5
Phantom Types
Phantom types are type parameters that don't appear in runtime values but enforce compile-time constraints, useful for state machines and validation.
type Validated = { readonly _brand: 'validated' }; type Unvalidated = { readonly _brand: 'unvalidated' }; type Email<State> = string & { readonly _state: State }; function createEmail(input: string): Email<Unvalidated> { return input as Email<Unvalidated>; } function validate(email: Email<Unvalidated>): Email<Validated> | null { return email.includes('@') ? email as Email<Validated> : null; } function sendEmail(email: Email<Validated>) { /* only validated! */ }
Builder Pattern with Types
Combining the builder pattern with TypeScript's type system to track which properties have been set, ensuring compile-time completeness.
type BuilderState = { name: boolean; age: boolean }; class UserBuilder<S extends BuilderState = { name: false; age: false }> { private data: Partial<User> = {}; setName(name: string): UserBuilder<S & { name: true }> { this.data.name = name; return this as any; } setAge(age: number): UserBuilder<S & { age: true }> { this.data.age = age; return this as any; } build(this: UserBuilder<{ name: true; age: true }>): User { return this.data as User; } } new UserBuilder().setName("John").setAge(30).build(); // ✓ new UserBuilder().setName("John").build(); // ✗ Error!
Advanced Utility Type Creation
Creating sophisticated utility types that compose primitives like infer, conditional types, and template literals for complex transformations.
// Deep partial with array handling type DeepPartial<T> = T extends (infer U)[] ? DeepPartial<U>[] : T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T; // Get all nested paths as union type Paths<T, P extends string = ''> = T extends object ? { [K in keyof T & string]: P extends '' ? K | Paths<T[K], K> : `${P}.${K}` | Paths<T[K], `${P}.${K}`> }[keyof T & string] : never; type UserPaths = Paths<{ a: { b: { c: number } } }>; // "a" | "a.b" | "a.b.c"
Type-Safe Event Emitters
Creating event emitters where event names and their payload types are strictly enforced at compile time.
type EventMap = { login: { userId: string; timestamp: Date }; logout: { userId: string }; error: Error; }; class TypedEmitter<T extends Record<string, any>> { private listeners = new Map<keyof T, Set<Function>>(); on<K extends keyof T>(event: K, handler: (payload: T[K]) => void): void { if (!this.listeners.has(event)) this.listeners.set(event, new Set()); this.listeners.get(event)!.add(handler); } emit<K extends keyof T>(event: K, payload: T[K]): void { this.listeners.get(event)?.forEach(fn => fn(payload)); } } const emitter = new TypedEmitter<EventMap>(); emitter.on('login', (e) => console.log(e.userId)); // e is typed! emitter.emit('login', { userId: '123', timestamp: new Date() });
Type-Safe Dependency Injection
Implementing DI containers where dependencies are registered and resolved with full type inference and compile-time validation.
type TokenMap = {}; class Container { private deps = new Map<symbol, any>(); register<K extends string, T>( token: K, factory: () => T ): asserts this is this & { [P in K]: T } { const sym = Symbol.for(token); this.deps.set(sym, factory()); } resolve<K extends keyof this>(token: K): this[K] { return this.deps.get(Symbol.for(token as string)); } } // Usage with inference const container = new Container(); container.register('logger', () => new Logger()); container.register('db', () => new Database()); const logger = container.resolve('logger'); // Logger type inferred
Advanced Decorator Patterns
Using decorators with metadata reflection for AOP patterns like validation, caching, and method interception with full type preservation.
import 'reflect-metadata'; function Validate<T>(schema: (val: T) => boolean) { return function (target: any, key: string, desc: PropertyDescriptor) { const original = desc.value; desc.value = function (...args: T[]) { args.forEach((arg, i) => { if (!schema(arg)) throw new Error(`Validation failed for arg ${i}`); }); return original.apply(this, args); }; }; } // Metadata-based DI function Injectable(): ClassDecorator { return (target) => { const params = Reflect.getMetadata('design:paramtypes', target) || []; Reflect.defineMetadata('di:dependencies', params, target); }; }
Compiler API
TypeScript's Compiler API allows programmatic access to parsing, type-checking, and code generation for building custom tools.
import * as ts from 'typescript'; const code = `const x: number = 42;`; const sourceFile = ts.createSourceFile( 'temp.ts', code, ts.ScriptTarget.Latest, true ); // Create program for type checking const program = ts.createProgram(['file.ts'], {}); const checker = program.getTypeChecker(); // Get type information const symbol = checker.getSymbolAtLocation(node); const type = checker.getTypeOfSymbolAtLocation(symbol, node); console.log(checker.typeToString(type));
Custom Transformers
Transformers modify the AST during compilation, enabling compile-time code generation, macro-like behavior, and optimization.
import * as ts from 'typescript'; const transformer: ts.TransformerFactory<ts.SourceFile> = (context) => { return (sourceFile) => { const visitor = (node: ts.Node): ts.Node => { // Replace all string literals with uppercase if (ts.isStringLiteral(node)) { return ts.factory.createStringLiteral(node.text.toUpperCase()); } return ts.visitEachChild(node, visitor, context); }; return ts.visitNode(sourceFile, visitor) as ts.SourceFile; }; }; // Usage in tsconfig or programmatically // "customTransformers": { "before": [transformer] }
AST Manipulation
Understanding and manipulating TypeScript's Abstract Syntax Tree for code analysis, refactoring, and generation tools.
SourceFile
│
┌─────────────┼─────────────┐
│ │ │
ImportDecl FunctionDecl ExportDecl
│
┌─────────┼─────────┐
│ │ │
Identifier Params Block
│ │
"greet" ReturnStatement
│
BinaryExpr
│
┌──────────┼──────────┐
│ │ │
String "+" Identifier
function analyzeAST(sourceFile: ts.SourceFile) { ts.forEachChild(sourceFile, function visit(node) { console.log(`${ts.SyntaxKind[node.kind]} at ${node.pos}-${node.end}`); ts.forEachChild(node, visit); }); }
Writing Custom Lint Rules
Creating ESLint rules with TypeScript's type information via @typescript-eslint/utils for semantic linting beyond syntax.
import { ESLintUtils } from '@typescript-eslint/utils'; const createRule = ESLintUtils.RuleCreator(name => `https://example.com/${name}`); export const noFloatingPromises = createRule({ name: 'no-floating-promises', meta: { type: 'problem', messages: { floating: 'Promises must be awaited or returned' }, schema: [], }, defaultOptions: [], create(context) { const services = ESLintUtils.getParserServices(context); const checker = services.program.getTypeChecker(); return { ExpressionStatement(node) { const type = checker.getTypeAtLocation( services.esTreeNodeToTSNodeMap.get(node.expression) ); if (type.symbol?.name === 'Promise') { context.report({ node, messageId: 'floating' }); } }, }; }, });
Performance Optimization
Key strategies for reducing TypeScript compilation times and type-checking overhead in large codebases.
┌─────────────────────────────────────────────────────────────┐
│ PERFORMANCE CHECKLIST │
├─────────────────────────────────────────────────────────────┤
│ ✓ Use `interface` over `type` for object shapes (faster) │
│ ✓ Avoid deep conditional types (max 3-4 levels) │
│ ✓ Use `skipLibCheck: true` in tsconfig │
│ ✓ Enable `incremental: true` │
│ ✓ Prefer `unknown` over complex union exhaustion │
│ ✓ Avoid `export * from` (slows resolution) │
│ ✓ Use project references for large codebases │
│ ✓ Profile with `--generateTrace` and analyze │
└─────────────────────────────────────────────────────────────┘
tsc --generateTrace ./trace npx @typescript/analyze-trace ./trace
Project References
Project references enable modular compilation, allowing independent builds of sub-projects with proper dependency ordering.
monorepo/
├── tsconfig.json # Root config with references
├── packages/
│ ├── core/
│ │ ├── tsconfig.json # "composite": true
│ │ └── src/
│ ├── api/
│ │ ├── tsconfig.json # references: [{ path: "../core" }]
│ │ └── src/
// packages/api/tsconfig.json { "compilerOptions": { "composite": true, "declaration": true, "declarationMap": true, "outDir": "./dist" }, "references": [ { "path": "../core" } ] }
tsc --build # Builds in dependency order tsc --build --watch # Incremental watch across projects
Incremental Compilation
Incremental mode caches compilation info to .tsbuildinfo files, dramatically speeding up subsequent builds.
// tsconfig.json { "compilerOptions": { "incremental": true, "tsBuildInfoFile": "./dist/.tsbuildinfo" } }
First build: ████████████████████ 15.2s
Second build: ███ 2.1s (86% faster)
.tsbuildinfo contains:
├── File versions (hashes)
├── Dependency graph
├── Emitted file signatures
└── Semantic diagnostics cache
Watch Mode Optimization
Optimizing --watch for fast feedback loops using OS file watchers and reduced scope checking.
{ "compilerOptions": { "incremental": true }, "watchOptions": { "watchFile": "useFsEvents", // macOS/Linux native "watchDirectory": "useFsEvents", "fallbackPolling": "dynamicPriority", "synchronousWatchDirectory": true, "excludeDirectories": ["node_modules", "dist"] } }
# Ultra-fast watch with SWC npx ts-node --swc --watch src/index.ts # Or with transpileOnly (skips type-checking) tsc --watch --noEmit & nodemon dist/index.js
Monorepo TypeScript Setup
Configuring TypeScript for monorepos using workspaces, project references, and shared configurations.
monorepo/
├── package.json # workspaces: ["packages/*"]
├── tsconfig.base.json # Shared compiler options
├── tsconfig.json # Root with all references
└── packages/
├── shared/
│ ├── package.json # "name": "@mono/shared"
│ └── tsconfig.json
├── web/
│ └── tsconfig.json # extends + references
└── api/
└── tsconfig.json
// tsconfig.base.json { "compilerOptions": { "strict": true, "composite": true, "declaration": true, "declarationMap": true, "moduleResolution": "bundler" } } // packages/api/tsconfig.json { "extends": "../../tsconfig.base.json", "references": [{ "path": "../shared" }], "compilerOptions": { "rootDir": "./src", "outDir": "./dist" } }
Path Mapping and Module Resolution
Configuring path aliases and understanding TypeScript's module resolution for clean imports and proper bundler integration.
// tsconfig.json { "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["src/*"], "@components/*": ["src/components/*"], "@utils": ["src/utils/index.ts"], "#server/*": ["src/server/*"] // Node subpath pattern }, "moduleResolution": "bundler", // Modern bundlers // or "nodenext" for pure Node.js "moduleSuffixes": [".ios", ""] // React Native platform ext } }
Resolution order for "import { foo } from '@utils'"
──────────────────────────────────────────────────────
1. tsconfig paths → src/utils/index.ts
2. node_modules/@utils
3. Error: Cannot find module
⚠️ Runtime also needs configuration!
- Vite: vite-tsconfig-paths
- Node: tsconfig-paths or tsx
- Jest: moduleNameMapper
Composite Projects
Composite projects enable large TypeScript codebases to be split into smaller, independently compilable projects using references in tsconfig.json, enabling incremental builds and better IDE performance through project boundaries.
┌─────────────────────────────────────────────────┐
│ monorepo/ │
│ ├── packages/ │
│ │ ├── core/ ←──────┐ │
│ │ │ └── tsconfig.json │ references │
│ │ ├── api/ ───────┤ │
│ │ │ └── tsconfig.json │ │
│ │ └── web/ ───────┘ │
│ │ └── tsconfig.json │
│ └── tsconfig.base.json (shared settings) │
└─────────────────────────────────────────────────┘
// packages/api/tsconfig.json { "compilerOptions": { "composite": true, "outDir": "./dist" }, "references": [{ "path": "../core" }] }
tsc --build --watch # Incremental builds across all projects
Type-safe API Design Patterns
Design APIs that leverage TypeScript's type system to catch errors at compile time, using branded types, discriminated unions, and builder patterns to make invalid states unrepresentable.
// Branded types for type-safe IDs type UserId = string & { readonly __brand: unique symbol }; type OrderId = string & { readonly __brand: unique symbol }; const createUserId = (id: string): UserId => id as UserId; // Discriminated unions for state machines type RequestState<T> = | { status: 'idle' } | { status: 'loading' } | { status: 'success'; data: T } | { status: 'error'; error: Error }; // Builder pattern with type accumulation class QueryBuilder<T extends Record<string, unknown> = {}> { select<K extends string>(field: K): QueryBuilder<T & Record<K, unknown>> { return this as any; } }
Type-safe ORMs and Query Builders
Modern TypeScript ORMs like Prisma, Drizzle, and Kysely provide end-to-end type safety by inferring types from your schema, ensuring queries are validated at compile time and results are fully typed.
// Prisma: Schema-first, generated types const user = await prisma.user.findUnique({ where: { id: 1 }, include: { posts: true } // Auto-typed relation }); // user.posts is Post[] - fully typed! // Drizzle: TypeScript-first schema import { pgTable, serial, text } from 'drizzle-orm/pg-core'; const users = pgTable('users', { id: serial('id').primaryKey(), name: text('name').notNull(), }); const result = await db.select().from(users).where(eq(users.id, 1)); // result: { id: number; name: string }[] // Kysely: Query builder with inference const result = await db .selectFrom('users') .select(['id', 'name']) .execute(); // Typed from DB schema
Integration with Build Tools
Each bundler has different TypeScript handling strategies: webpack uses ts-loader or babel-loader, esbuild/swc strip types without type-checking for speed, and rollup uses plugins—all requiring separate tsc for type validation.
┌──────────────────────────────────────────────────────────┐
│ Build Pipeline │
├──────────────┬──────────────┬──────────────┬─────────────┤
│ webpack │ esbuild │ swc │ rollup │
├──────────────┼──────────────┼──────────────┼─────────────┤
│ ts-loader │ Native TS │ @swc/core │ @rollup/ │
│ fork-ts- │ (strips │ (strips │ plugin- │
│ checker │ types only) │ types only) │ typescript │
├──────────────┴──────────────┴──────────────┴─────────────┤
│ ⚠️ esbuild/swc: Run `tsc --noEmit` separately! │
└──────────────────────────────────────────────────────────┘
// vite.config.ts (uses esbuild) export default defineConfig({ plugins: [react()], esbuild: { target: 'esnext' } }); // package.json - parallel type checking { "scripts": { "build": "tsc --noEmit && vite build" } }
TypeScript with Node.js (ESM vs CJS)
Node.js TypeScript projects must choose between CommonJS ("module": "commonjs") and ES Modules ("module": "nodenext"), with ESM requiring explicit .js extensions in imports and "type": "module" in package.json.
┌─────────────────────────────────────────────────────────┐
│ CommonJS vs ES Modules in Node.js │
├────────────────────────┬────────────────────────────────┤
│ CommonJS │ ES Modules │
├────────────────────────┼────────────────────────────────┤
│ require() / exports │ import / export │
│ Synchronous loading │ Async, static analysis │
│ __dirname available │ import.meta.url │
│ .js or .cjs extension │ .js or .mjs extension │
│ No file ext in imports │ MUST include .js extension │
└────────────────────────┴────────────────────────────────┘
// tsconfig.json for ESM { "compilerOptions": { "module": "nodenext", "moduleResolution": "nodenext", "outDir": "./dist" } }
// ESM requires .js extension (even for .ts files!) import { helper } from './utils.js'; // ✅ Correct import { helper } from './utils'; // ❌ Fails at runtime
TypeScript with React/Vue/Angular Advanced Patterns
Each framework leverages TypeScript differently: React uses generics for component props and hooks, Vue 3 uses defineComponent with Composition API, and Angular uses decorators with strict template type-checking.
// React: Generic components & typed hooks interface ListProps<T> { items: T[]; renderItem: (item: T) => React.ReactNode; } function List<T>({ items, renderItem }: ListProps<T>) { return <>{items.map(renderItem)}</>; } // Vue 3: Typed props with defineComponent defineComponent({ props: { userId: { type: Number, required: true } }, setup(props) { const user = ref<User | null>(null); // props.userId is number } }); // Angular: Strict templates @Component({ template: `<div>{{ user.name }}</div>` // Error if user is possibly null }) export class UserComponent { @Input() user!: User; }
Testing TypeScript Applications
Testing TypeScript requires configuring your test runner (Jest/Vitest) to handle TS files, with Vitest offering native support, while Jest needs ts-jest or SWC; use type-safe mocking libraries like jest-mock-extended.
// vitest.config.ts - native TS support import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { globals: true, environment: 'node' } }); // Type-safe mocking with jest-mock-extended import { mock, MockProxy } from 'jest-mock-extended'; interface UserService { getUser(id: number): Promise<User>; } describe('UserController', () => { let mockService: MockProxy<UserService>; beforeEach(() => { mockService = mock<UserService>(); mockService.getUser.calledWith(1).mockResolvedValue({ id: 1, name: 'Test' }); }); });
Migration Strategies from JavaScript
Migrate incrementally by enabling allowJs and checkJs, renaming files from .js to .ts one at a time, starting with leaf modules, using // @ts-ignore temporarily, and progressively tightening strict mode options.
┌─────────────────────────────────────────────────────────┐
│ Migration Strategy Phases │
├─────────────────────────────────────────────────────────┤
│ Phase 1: Setup │
│ ├── Add tsconfig.json with allowJs: true │
│ ├── Set strict: false initially │
│ └── Install @types/* packages │
├─────────────────────────────────────────────────────────┤
│ Phase 2: Gradual conversion (leaf → root) │
│ ├── utils.js → utils.ts │
│ ├── services/*.js → services/*.ts │
│ └── app.js → app.ts │
├─────────────────────────────────────────────────────────┤
│ Phase 3: Enable strict incrementally │
│ ├── noImplicitAny: true │
│ ├── strictNullChecks: true │
│ └── strict: true (final goal) │
└─────────────────────────────────────────────────────────┘
// Initial permissive config { "compilerOptions": { "allowJs": true, "checkJs": true, "strict": false } }
Contribution to DefinitelyTyped
Contributing to DefinitelyTyped involves forking the repo, creating types in types/<package>/index.d.ts, adding tests, and submitting a PR—following strict naming conventions and ensuring compatibility with the package's API.
# Clone and setup git clone https://github.com/DefinitelyTyped/DefinitelyTyped.git cd DefinitelyTyped npm install # Create new type definition mkdir types/my-library
// types/my-library/index.d.ts declare module 'my-library' { export function doSomething(input: string): number; export interface Config { timeout?: number; } } // types/my-library/my-library-tests.ts import { doSomething } from 'my-library'; doSomething('test'); // $ExpectType number
// types/my-library/package.json (required metadata) { "name": "@types/my-library", "version": "1.0.0", "types": "index.d.ts" }
Writing Publishable Type Definitions
Publishable type definitions should include proper exports, JSDoc comments, support for multiple module systems, and be tested with dtslint or tsd—either bundled with your package or published separately to @types.
// src/index.ts - Source with embedded types /** * Formats a date to ISO string * @param date - The date to format * @returns ISO formatted string */ export function formatDate(date: Date): string { return date.toISOString(); } // package.json { "name": "my-package", "main": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.mjs", "require": "./dist/index.js" } } } // tsconfig.json { "compilerOptions": { "declaration": true, "declarationMap": true } }
TypeScript ESLint Advanced Configuration
TypeScript ESLint requires @typescript-eslint/parser and rules that leverage type information via parserOptions.project, enabling powerful rules like no-floating-promises and strict-boolean-expressions.
// eslint.config.js (flat config - ESLint 9+) import tseslint from 'typescript-eslint'; export default tseslint.config( ...tseslint.configs.strictTypeChecked, { languageOptions: { parserOptions: { project: './tsconfig.json', tsconfigRootDir: import.meta.dirname, }, }, rules: { '@typescript-eslint/no-floating-promises': 'error', '@typescript-eslint/strict-boolean-expressions': 'error', '@typescript-eslint/no-misused-promises': 'error', '@typescript-eslint/await-thenable': 'error', '@typescript-eslint/no-unnecessary-type-assertion': 'error', }, } );
Runtime Type Validation (zod, io-ts, typia)
Runtime validators bridge the compile-time/runtime gap: Zod provides fluent schema building, io-ts offers functional programming style with Either types, and Typia generates validators at compile-time for zero runtime overhead.
// Zod: Most popular, fluent API import { z } from 'zod'; const UserSchema = z.object({ id: z.number(), email: z.string().email(), role: z.enum(['admin', 'user']) }); type User = z.infer<typeof UserSchema>; // Infer TS type const result = UserSchema.safeParse(input); // Runtime validation // io-ts: Functional style import * as t from 'io-ts'; const User = t.type({ id: t.number, name: t.string }); const decoded = User.decode(input); // Either<Errors, User> // Typia: Compile-time generation (fastest) import typia from 'typia'; interface User { id: number; email: string; } const validate = typia.createValidate<User>(); // Zero overhead
End-to-end Type Safety (tRPC, GraphQL Codegen)
End-to-end type safety ensures types flow from backend to frontend automatically: tRPC shares types via TypeScript inference, while GraphQL codegen generates types from your schema and operations.
┌─────────────────────────────────────────────────────────┐
│ End-to-End Type Flow │
│ │
│ Backend Frontend │
│ ┌─────────┐ Type Inference ┌─────────┐ │
│ │ tRPC │ ←──────────────────→ │ Client │ │
│ │ Router │ (no codegen!) │ │ │
│ └─────────┘ └─────────┘ │
│ │
│ ┌─────────┐ Code Generation ┌─────────┐ │
│ │ GraphQL │ ────────────────────→│ Types + │ │
│ │ Schema │ (graphql-codegen) │ Hooks │ │
│ └─────────┘ └─────────┘ │
└─────────────────────────────────────────────────────────┘
// tRPC: Shared types via inference // server.ts const appRouter = router({ user: { get: publicProcedure.input(z.number()).query(({ input }) => db.user.find(input)) } }); export type AppRouter = typeof appRouter; // client.ts const user = await trpc.user.get.query(1); // Fully typed!
WebAssembly with TypeScript
TypeScript integrates with WebAssembly through AssemblyScript (a TypeScript subset compiling to Wasm) or by consuming Wasm modules with typed bindings, enabling high-performance computation in browsers and Node.js.
// AssemblyScript: TypeScript-like syntax → Wasm // assembly/index.ts export function fibonacci(n: i32): i32 { if (n <= 1) return n; return fibonacci(n - 1) + fibonacci(n - 2); } // Consuming Wasm in TypeScript // types/wasm.d.ts declare module '*.wasm' { const module: { fibonacci: (n: number) => number; }; export default module; } // app.ts import wasmModule from './optimized.wasm'; const result = wasmModule.fibonacci(40); // Type-safe!
# Compile AssemblyScript npx asc assembly/index.ts -o build/optimized.wasm --optimize
TypeScript in Serverless Environments
Serverless TypeScript requires fast cold starts (favor esbuild/swc bundling), minimal bundle sizes, and typed handler interfaces—with platforms like AWS Lambda, Vercel, and Cloudflare Workers each having specific type packages.
// AWS Lambda with typed handlers import { APIGatewayProxyHandler } from 'aws-lambda'; export const handler: APIGatewayProxyHandler = async (event) => { const body = JSON.parse(event.body || '{}'); return { statusCode: 200, body: JSON.stringify({ message: 'Success' }) }; }; // Vercel Serverless Function import type { VercelRequest, VercelResponse } from '@vercel/node'; export default function handler(req: VercelRequest, res: VercelResponse) { res.json({ timestamp: Date.now() }); }
# serverless.yml with esbuild plugin (fast builds) plugins: - serverless-esbuild custom: esbuild: bundle: true minify: true
Edge Runtime Considerations
Edge runtimes (Cloudflare Workers, Vercel Edge, Deno Deploy) have limited APIs compared to Node.js—no fs, restricted crypto, and different global objects—requiring careful type configuration and edge-compatible libraries.
┌─────────────────────────────────────────────────────────┐ │ Edge vs Node.js Runtime APIs │ ├─────────────────────┬───────────────────────────────────┤ │ Available │ Not Available │ ├─────────────────────┼───────────────────────────────────┤ │ fetch, Request │ fs, path, child_process │ │ Response, Headers │ Node.js native modules │ │ crypto.subtle │ Full crypto module │ │ TextEncoder/Decoder │ Buffer (use Uint8Array) │ │ WebSocket │ net, http modules │ └─────────────────────┴───────────────────────────────────┘
// Cloudflare Workers typed handler export interface Env { MY_KV: KVNamespace; DB: D1Database; } export default { async fetch(request: Request, env: Env): Promise<Response> { const data = await env.MY_KV.get('key'); return new Response(data); } }; // tsconfig.json for edge { "compilerOptions": { "types": ["@cloudflare/workers-types"], "lib": ["ES2022"] } }