mrkeyoor.com_
Wed 05 Aug 05:02 UTC
npmCLI & Toolingupdated 05 Aug 2026

typescript

TypeScript is a static type layer on top of JavaScript plus the compiler that checks it. You write .ts files, the tsc compiler verifies the types and emits plain readable JavaScript that runs anywhere JS runs. It is a dev-time tool: types vanish at runtime, so it catches wrong arguments, nulls and typos before you ship, not after. Version 7 is a ground-up native rewrite of the compiler focused on speed; the language itself is unchanged.

Verdict

The default for any JavaScript codebase that has to be maintained, and the 260M weekly downloads say the argument is over. Adopt strict mode from day one; the only real caution right now is the 7.0 compiler transition if you rely on compiler-API tooling.

API stability4/5The language almost never breaks user code, but 7.0 replaces the compiler implementation; anything built on the old compiler API needs migration, and feature work is paused until the transition completes.
Docs5/5The handbook, playground and release notes are among the best docs in the ecosystem; nearly every error message has a searchable answer.
Maintenance5/5Microsoft-funded, pushed daily, with a public roadmap. Development now happens in the typescript-go repo while this one takes crash and security fixes only.
Ecosystem5/5Every major framework ships types, DefinitelyTyped covers the long tail, and editors bundle the language server by default.

Use it if

  • The codebase is touched by more than one person or will live longer than a month; types are the cheapest documentation you will ever write
  • You consume big third-party APIs (AWS SDK, React, Node) and want autocomplete and compile-time errors instead of runtime surprises
  • You maintain a public library; shipping .d.ts types is table stakes for adoption in 2026
  • You are refactoring a large JS codebase and want the compiler to find every call site you broke
Skip it if

Setup reality

npm install -D typescript then npx tsc --init, and the compiler itself just works. The pain is tsconfig: module vs moduleResolution (nodenext vs bundler) confuses everyone, ESM/CJS interop errors are cryptic, and untyped dependencies need @types packages or hand-written declarations. Also note the 6/7 transition: development moved to the typescript-go repo and the original repo only accepts limited fixes, so some ecosystem tools lag behind 7.0.

Patterns

Install and create a tsconfiginstall-and-init

npm install -D typescript
npx tsc --init
npx tsc --noEmit   # type-check without emitting JS

tsc --init writes a heavily commented tsconfig; keep strict true and delete options you do not understand yet.

A sane tsconfig for modern Nodestrict-tsconfig

{
  "compilerOptions": {
    "target": "es2022",
    "module": "nodenext",
    "strict": true,
    "skipLibCheck": true,
    "noUncheckedIndexedAccess": true,
    "outDir": "dist"
  },
  "include": ["src"]
}

Use module nodenext when Node runs the output, bundler when Vite/esbuild does; mixing them causes the classic ESM import errors.

Handle a caught error safelynarrow-unknown

function message(err: unknown): string {
  if (err instanceof Error) return err.message
  return String(err)
}

try {
  risky()
} catch (err) {
  console.error(message(err))
}

catch variables are unknown under strict mode; narrow before touching properties instead of casting to any.

Model success or failure with a tagged uniondiscriminated-union

type Result<T> =
  | { ok: true; value: T }
  | { ok: false; error: string }

function unwrap<T>(r: Result<T>): T {
  if (r.ok) return r.value
  throw new Error(r.error)
}

Checking the shared literal field narrows the whole object; add a never-typed default case in switches to catch missed variants at compile time.

Write a custom type guardtype-guard

function isUser(v: unknown): v is { id: string } {
  return (
    typeof v === 'object' &&
    v !== null &&
    'id' in v &&
    typeof (v as { id: unknown }).id === 'string'
  )
}

The compiler trusts your guard blindly; a wrong guard silences the checker, so use a schema validator like zod at real I/O boundaries.

Check a shape without widening the typesatisfies-operator

const config = {
  port: 3000,
  host: 'localhost',
} satisfies Record<string, string | number>

config.port.toFixed(0) // port is still number, not string | number

Use satisfies instead of a type annotation when you want validation plus the narrowest inferred type.

Derive types instead of duplicating themutility-types

type User = { id: string; name: string; email: string }

type UserPatch = Partial<Omit<User, 'id'>>
type PublicUser = Pick<User, 'id' | 'name'>
type ById = Record<string, User>

Derived types update automatically when the source type changes; hand-copied interfaces drift.

Constrain a generic to keys of a typegeneric-constraint

function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {
  return items.map((item) => item[key])
}

const names = pluck([{ id: 1, name: 'a' }], 'name') // string[]

K extends keyof T rejects invalid keys at the call site and infers the exact return type.

Add types for an untyped dependencydeclare-untyped-module

// src/types/legacy-lib.d.ts
declare module 'legacy-lib' {
  export function init(opts?: Record<string, unknown>): void
  export default init
}

Check for an @types/legacy-lib package on DefinitelyTyped first; only hand-write declarations when none exists.

Run TypeScript without a build steprun-ts-directly

npx tsx src/main.ts
# or with recent Node versions (type stripping):
node src/main.ts

Node's built-in stripping only handles erasable syntax (no enums or namespaces), and neither path checks types; keep tsc --noEmit in CI.

Alternatives

PackageRegistryPick it when
esbuildnpmYou only need TS syntax stripped and bundled fast; it never checks types, so pair it with tsc --noEmit in CI
@swc/corenpmSame idea as esbuild as a Rust-based transpiler, common inside Next.js and Jest setups
flow-binnpmAlmost never for new work; Meta's type checker lost the ecosystem war and mainly matters in legacy Meta-adjacent codebases