cheat sheet

TypeScript Installation & Running

Install the TypeScript compiler, run .ts files without a build step using ts-node or tsx, and compile projects with tsc. Covers tsc flags, watch mode, and project references.

TypeScript Installation & Running

What it is

TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. The official tsc compiler performs type-checking and emits JavaScript output. Several faster alternatives — ts-node, tsx, and Bun — allow running TypeScript files directly without a separate compile step, which is useful during development.

Install tsc

TypeScript is installed as a dev dependency per-project. Avoid global installs so each project pins its own version.

bash
npm install -D typescript

Verify the installation:

bash
npx tsc --version

Output:

text
Version 5.4.5

Generate a starter tsconfig.json:

bash
npx tsc --init

Output:

text
Created a new tsconfig.json with:
  target: es2016
  module: commonjs
  strict: true
  esModuleInterop: true
  skipLibCheck: true
  forceConsistentCasingInFileNames: true

Run TypeScript without a compile step

ts-node

ts-node is the classic Node.js TypeScript runner. It compiles files in-memory and executes them with Node.

bash
npm install -D ts-node
bash
npx ts-node src/index.ts

For ESM projects, use the --esm flag or the ts-node/esm loader:

bash
npx ts-node --esm src/index.ts
# or
node --loader ts-node/esm src/index.ts

tsx

tsx is a fast, ESM-compatible TypeScript runner built on esbuild. It starts faster than ts-node and handles .ts, .tsx, .mts, and .cts files transparently.

bash
npm install -D tsx
bash
npx tsx src/index.ts

Watch mode (re-runs on file changes):

bash
npx tsx watch src/index.ts

Using as a Node.js loader (for programmatic use or legacy flags):

bash
node --import tsx/esm src/index.ts

Bun

Bun natively strips TypeScript types and executes the file directly — no separate package is needed.

bash
bun run src/index.ts

Output:

text
Hello from TypeScript

For new projects, tsx is the recommended dev-time runner in 2026. It is faster than ts-node, supports both CJS and ESM, and requires no extra configuration.

Compile with tsc

Basic compile

Compiles all files listed in tsconfig.json and emits JavaScript to the configured outDir:

bash
npx tsc

Watch mode

Re-compiles whenever a source file changes:

bash
npx tsc --watch

Output:

text
[12:00:00] Starting compilation in watch mode...
[12:00:01] Found 0 errors. Watching for file changes.

Type-check only (no emit)

Validates types without writing any output files — useful in CI:

bash
npx tsc --noEmit

Output (no errors):

text
(no output — exit code 0)

Output (with errors):

text
src/index.ts:5:10 - error TS2322: Type 'string' is not assignable to type 'number'.

5   const n: number = "hello";
              ~~~~~~~~~~~~~~~
Found 1 error.

Override output directory

bash
npx tsc --outDir dist

Compile a single file (bypasses tsconfig)

bash
npx tsc src/utils.ts --target ES2022 --module ESNext

Passing individual file names to tsc disables tsconfig.json. All options must be specified via CLI flags when doing this.

Compile with source maps

bash
npx tsc --sourceMap

This emits a .js.map file alongside each .js file, enabling debuggers to map back to the original TypeScript source.

Project references

Large monorepos split TypeScript code into multiple sub-projects. tsc --build (alias tsc -b) compiles them in dependency order and caches results.

bash
npx tsc --build
npx tsc --build --watch       # watch all referenced projects
npx tsc --build --clean       # delete all build outputs
npx tsc --build --force       # rebuild even if up to date

A root tsconfig.json referencing sub-projects:

json
{
  "files": [],
  "references": [
    { "path": "./packages/core" },
    { "path": "./packages/api" },
    { "path": "./packages/ui" }
  ]
}

Each referenced package's tsconfig.json must include "composite": true:

json
{
  "compilerOptions": {
    "composite": true,
    "outDir": "dist",
    "rootDir": "src"
  }
}

Useful tsc flags

FlagDescription
--noEmitType-check only, no output files
--watch / -wRecompile on change
--build / -bIncremental project-reference build
--strictEnable all strict checks
--target <ES>Set output JS version
--module <fmt>Set module format (commonjs, esnext, nodenext)
--outDir <dir>Output directory
--sourceMapEmit .js.map files
--declarationEmit .d.ts files
--diagnosticsPrint timing diagnostics
--listFilesPrint files included in the compilation

Choosing a runner

ToolSpeedESMCJSType errors shownNotes
tscSlowest✅ (full)Official, required for .d.ts emit
ts-nodeSlowPartialMature, widely supported
tsxFast❌ (type-strip only)Recommended for dev
bun runFastest❌ (type-strip only)No Node.js compatibility layer

tsx and bun do not run the TypeScript type checker — they only strip types. Always run tsc --noEmit in CI to catch type errors.

TypeScript versions and release cadence

TypeScript ships a minor version roughly every three months. Each minor (5.0, 5.1, 5.2, …) introduces new language features and may include breaking changes; patches are bug-fix only. The version installed in your project's devDependencies is the version that governs every editor, every CI run, and every contributor's compiler.

bash
npm view typescript versions --json | tail -20

Output:

text
[
  "5.3.0",
  "5.3.2",
  "5.3.3",
  "5.4.0",
  "5.4.2",
  "5.4.3",
  "5.4.4",
  "5.4.5",
  "5.5.0",
  "5.5.2",
  "5.5.3",
  "5.6.0",
  "5.6.2",
  "5.7.2"
]

Pin to a specific minor in package.json to make builds deterministic. The ~ semver range lets the patch float (5.4.55.4.x) without accidentally moving to 5.5, where syntax may change:

json
{
  "devDependencies": {
    "typescript": "~5.4.5"
  }
}

Check the current install matches the lockfile:

bash
npm ls typescript

Output:

text
my-project@1.0.0 /repo
└── typescript@5.4.5

Upgrade to the next minor in a controlled way — bump the package, run tsc --noEmit, then commit:

bash
npm install -D typescript@5.5
npx tsc --noEmit

Output:

text
src/legacy.ts:12:5 - error TS2322: Type 'undefined' is not assignable to type 'string'.

12     const v: string = maybeUndefined();
       ~~~~~~~~~~~~~~~~

Found 1 error.

Every new minor often surfaces additional errors as the type-checker gets stricter. Read the release notes for breaking-change call-outs before upgrading.

Global vs local tsc

tsc installed globally (npm install -g typescript) puts a single binary on $PATH that ignores your project's version. The risk: contributor A's global is 5.0 and emits one shape of output; contributor B's global is 5.7 and emits another. Lock everything to the local install via npx tsc (or pnpm tsc, bun tsc, yarn tsc), which resolves from node_modules/.bin first.

bash
which tsc
npx tsc --version

Output:

text
/Users/alice/.nvm/versions/node/v22.10.0/bin/tsc
Version 5.4.5

Notice which tsc finds a global binary first, but npx tsc runs the project's node_modules/.bin/tsc. Always invoke via npx/pnpm exec/bun x so the lockfile rules.

For a project where npx is annoying, add an npm script that aliases tsc to the local binary:

json
{
  "scripts": {
    "tsc": "tsc",
    "typecheck": "tsc --noEmit",
    "build": "tsc"
  }
}
bash
npm run typecheck

Output:

text
> tsc --noEmit
(no output — exit code 0)

The shell script form (npm run …) prepends node_modules/.bin to $PATH automatically — every binary listed in any installed package becomes callable from a script without npx.

Choosing a TypeScript distribution

Most projects use the official typescript package from npm, but several alternatives ship faster compilers or different feature sets. They are not drop-in replacements — each has trade-offs.

DistributionWhat it providesWhen to reach for it
typescript (official)tsc, tsserver (language service), tsc --buildDefault — required for .d.ts emit and full type-checking.
@swc/core + @swc/cliRust-based transpiler (no type-check)CI builds where speed matters more than checking.
esbuildGo-based transpiler (no type-check)Bundler + transpile in one binary; can --bundle too.
@types/typescriptType declarations only — used inside the TS source treeNot user-facing; ignore.
tsc-watchWrapper around tsc -w with hooksWhen you want to run a script after every successful compile.
ttsc (ttypescript)tsc + transformer plugin hooksLegacy — superseded by SWC plugins.

The recommendation in 2026: use the official typescript package for type-checking and .d.ts emit, and use a bundler (Vite, esbuild, Bun) for the actual JS output. Do not try to make swc or esbuild replace tsc — they do not type-check.

bash
npm install -D typescript @swc/core

Output: (none — exits 0 on success)

devDependencies layout for a TypeScript project

A modern TypeScript project's package.json typically lists these dev dependencies. Each entry is here for a reason; deleting one silently breaks a workflow.

json
{
  "name": "my-app",
  "type": "module",
  "scripts": {
    "build": "tsc",
    "dev": "tsx watch src/index.ts",
    "typecheck": "tsc --noEmit",
    "test": "vitest run",
    "lint": "eslint src"
  },
  "devDependencies": {
    "typescript": "~5.4.5",
    "tsx": "^4.19.0",
    "@types/node": "^22.5.0",
    "vitest": "^2.0.0",
    "eslint": "^9.0.0",
    "@typescript-eslint/parser": "^8.0.0",
    "@typescript-eslint/eslint-plugin": "^8.0.0"
  }
}

The standard four:

  • typescript — the compiler itself.
  • tsx — fast dev-time runner (replaces ts-node).
  • @types/node — type declarations for Node's stdlib (fs, path, process, etc.). Required for any Node script.
  • @typescript-eslint/* — TypeScript-aware ESLint rules.
bash
npm install -D typescript tsx @types/node

Output:

text
added 3 packages in 1.2s

@types/* packages

Most npm packages either ship their own .d.ts files (bundled types) or have a community-maintained @types/<name> package on the @types scope. The TypeScript compiler auto-discovers any @types/* package in node_modules — you don't need to register them anywhere.

bash
npm install -D @types/node @types/express @types/lodash

Output:

text
added 3 packages in 0.8s

For a library that doesn't have @types/<name> and doesn't bundle types, TypeScript errors with TS7016. The fix is either to author a one-line ambient declaration (see .d.ts files) or to install the community types if they exist:

bash
npm install -D @types/some-library

Output: (none — exits 0 on success)

A handful of types packages are pinned to specific runtime versions — @types/node@22.x for Node 22, @types/node@20.x for Node 20. Match the runtime you're targeting, not the latest:

bash
node --version
npm install -D @types/node@22

Output:

text
v22.10.0
added 1 package in 0.4s

Editor integration

Every IDE that supports TypeScript reads from the same tsserver binary inside node_modules/typescript/lib/tsserver.js. The editor and tsc see the same world if (and only if) they use the same version.

VS Code workspace TypeScript version

VS Code ships its own bundled TypeScript and uses it by default. For a per-project version, click the version indicator in the status bar (bottom-right when a .ts file is open) and pick "Use Workspace Version". The selection is saved to .vscode/settings.json:

json
{
  "typescript.tsdk": "node_modules/typescript/lib"
}

This ensures every developer who opens the project sees the same compiler. Commit this setting.

Neovim / LSP

typescript-language-server (or vtsls, a newer fork) wraps tsserver and exposes it over LSP. Pick the project's TypeScript by setting the server's tsdk option:

lua
require('lspconfig').vtsls.setup({
  settings = {
    typescript = {
      tsdk = vim.fn.getcwd() .. '/node_modules/typescript/lib',
    },
  },
})

Refresh the editor after upgrading

After npm install -D typescript@<new-version>, the running editor still has the old tsserver in memory. VS Code: Command Palette → TypeScript: Restart TS Server. Neovim: :LspRestart.

bash
# To force every editor's restart on macOS, touch the project's tsconfig:
touch tsconfig.json

Output: (none — exits 0 on success)

Cross-runtime: TypeScript outside Node.js

Node is the most common host for TypeScript, but several other runtimes treat TypeScript as a first-class input. Each handles the type-strip / type-check split differently — none of these replaces tsc for type-checking. See ts-node, tsx & friends for the deep comparison.

RuntimeTS supportType-checks?Notes
Node 22.6+--experimental-strip-types flagNoBundled stripper based on amaro. Default-on in Node 23.6+.
BunNative parserNoSingle binary, fastest cold start.
DenoNative via swcYes (by default)Sandboxed; type-check toggleable.
Cloudflare WorkersWrangler/esbuildNoBuild step happens in the deploy pipeline.
VercelesbuildNoSame — TS handled by the platform's bundler.

Use the right tool for the job, but always run tsc --noEmit somewhere in the chain. Stripping is not checking.

bash
node --experimental-strip-types src/index.ts
bun src/index.ts
deno run --allow-net src/index.ts

Output:

text
Hello from TypeScript

What tsc actually does, end-to-end

Knowing what happens between npx tsc and the bytes hitting your dist/ directory makes diagnosing build problems much easier. The compiler runs five passes:

  1. Locate inputs — read tsconfig.json, expand include/exclude/files, plus every transitive import.
  2. Parse — produce AST nodes for every .ts / .tsx / .d.ts file.
  3. Bind — assign each declaration a symbol and link references back to declarations.
  4. Check — run the type-checker pass-by-pass, surfacing errors.
  5. Emit — write .js, .d.ts, and .map files according to compilerOptions.

You can interrupt the pipeline at each stage with a flag. --noEmit stops after pass 4. --declaration --emitDeclarationOnly writes only .d.ts and skips .js. --listFiles prints every file scanned during pass 1.

bash
npx tsc --listFiles --noEmit | head -10

Output:

text
/repo/node_modules/typescript/lib/lib.es5.d.ts
/repo/node_modules/typescript/lib/lib.es2015.d.ts
/repo/node_modules/typescript/lib/lib.es2016.d.ts
/repo/node_modules/typescript/lib/lib.dom.d.ts
/repo/node_modules/@types/node/index.d.ts
/repo/src/index.ts
/repo/src/util.ts

--diagnostics prints timing breakdown for each pass:

bash
npx tsc --diagnostics --noEmit

Output:

text
Files:                          47
Lines:                       18421
Identifiers:                 36842
Symbols:                     12387
Types:                        4192
Parse time:                   0.42s
Bind time:                    0.12s
Check time:                   1.87s
Emit time:                    0.00s
Total time:                   2.41s

When a build feels slow, this is where to look. Long bind time points to too many imports; long check time points to gnarly generic types. The compiler API exposes the same numbers programmatically via ts.performance.

Comparison with Python and other ecosystems

TypeScript installation differs from typed languages in adjacent ecosystems. The mental model "TypeScript = Python's mypy" is useful but incomplete.

ConcernTypeScriptPython (mypy / pyright)Rust
Compiler ships in stdlib?No — install typescript from npmNo — install mypy from PyPIYes — rustc is the language
Per-project version pin?devDependenciespyproject.toml / requirements.txtrust-toolchain.toml
Build artifact required?Optional — .ts.js for prodType-check only; runs .py directlyAlways — cargo build → binary
Editor uses local install?Yes via tsserverMostly — pyright uses its own bundled copyrust-analyzer reads rust-toolchain.toml
Strict by default?Opt-in via strict: trueOpt-in via strict = true in pyproject.tomlAlways

The most surprising-for-newcomers part of TypeScript: there is no runtime — the type system disappears. Compare with Rust where the compiler enforces types end-to-end, or Python where mypy is purely advisory but the runtime exists.

Common pitfalls

  1. Calling a global tsc by mistakewhich tsc resolves to the global, but the project's version is in node_modules. Always use npx tsc or an npm script.
  2. @types/node version doesn't match runtimeprocess.env.X typed as string | undefined even after augmenting ProcessEnv if @types/node is from a different major. Pin it to the Node major you run.
  3. Mixing dev runners — using ts-node in dev, tsc in build, and an editor that picks a different bundled TypeScript. The three should agree on tsconfig.json; pick one TypeScript version everywhere.
  4. npm install -g typescript only — works, until a contributor's global is a different minor. Always install locally and reference via npx.
  5. VS Code says "Use Workspace Version" but you ignore the prompt — VS Code's bundled TypeScript is older than the project's, so the editor flags errors the build doesn't. Always commit .vscode/settings.json with typescript.tsdk.
  6. tsx not installed in CI — your dev runs fine because tsx is in node_modules, but CI uses npm ci --omit=dev and skips it. Move tsx from devDependencies to wherever the CI script needs it, or run the build before --omit=dev.
  7. bun run reading the wrong package script — Bun runs package.json scripts but ignores pre*/post* hooks. If your prebuild step is critical, call it explicitly.
  8. Mixing tsx and node shebangs — the file's first line decides who runs it. #!/usr/bin/env -S npx tsx works for the author but fails for users without tsx installed. Build to JS for distribution.
  9. emitDeclarationOnly without declaration: truetsc silently emits nothing. Always pair them.
  10. Stale .tsbuildinfo after an upgradetsc may skip recompilation believing nothing changed. Run tsc -b --force once after upgrading the compiler.

Real-world recipes

Pin TypeScript across a monorepo

Every workspace inherits the root typescript install via npm/pnpm/Bun hoisting. Pin in one place; never re-install at a child workspace.

json
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": ["packages/*"],
  "devDependencies": {
    "typescript": "~5.4.5",
    "tsx": "^4.19.0"
  }
}
bash
pnpm install
pnpm -r exec tsc --version

Output:

text
packages/shared  | Version 5.4.5
packages/ui      | Version 5.4.5
packages/app     | Version 5.4.5

Migrate from ts-node to tsx

The standard 2026 dev runner migration — same package.json shape, two-line diff.

bash
npm uninstall ts-node
npm install -D tsx

Output:

text
removed 14 packages in 0.5s
added 1 package in 0.3s

Update package.json:

json
{
  "scripts": {
    "dev": "tsx watch src/server.ts",
    "start": "node dist/server.js"
  }
}
bash
npm run dev

Output:

text
[tsx] watching src/server.ts
Server ready on http://localhost:3000

If you used ts-node's register hook for tests (mocha -r ts-node/register), swap to tsx (mocha --import tsx).

Type-check in CI without emitting

The single most common CI script: type-check the project, fail the build on errors, write nothing to disk.

bash
npx tsc --noEmit

Output:

text
(no output — exit code 0)

For monorepos with project references, use tsc -b --noEmit instead — it walks the reference graph and skips up-to-date sub-projects.

bash
npx tsc -b --noEmit

Output:

text
(no output — exit code 0)

Install TypeScript without Node (Bun-first project)

If your project never touches Node — say, a Cloudflare Worker built with Bun — TypeScript still installs via Bun's package manager. Bun reads package.json and writes a bun.lock:

bash
bun add -d typescript @types/bun

Output:

text
bun add v1.2.18

 installed typescript@5.4.5
 installed @types/bun@1.1.5

 2 packages installed [123.00ms]

The @types/bun package provides TypeScript declarations for the Bun.* namespace, bun:test, and Bun's bundler API. With it in devDependencies, your editor sees Bun.serve(), Bun.file(), and import { describe } from "bun:test" without errors.

Add TypeScript to an existing JavaScript project

Three commands turn a plain JS repository into a TypeScript-aware one — no rewrite required. allowJs: true lets tsc type-check existing .js files; you migrate file-by-file.

bash
npm install -D typescript @types/node
npx tsc --init

Output:

text
Created a new tsconfig.json with:
  target: es2016
  module: commonjs
  strict: true
  esModuleInterop: true
  skipLibCheck: true
  forceConsistentCasingInFileNames: true

Edit tsconfig.json to enable JavaScript checking:

json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "allowJs": true,
    "checkJs": false,
    "noEmit": true,
    "skipLibCheck": true
  },
  "include": ["src"]
}
bash
npx tsc --noEmit

Output:

text
(no output — exit code 0)

Now rename one file at a time from .js to .ts and watch the type errors appear. See project references for surgical per-directory migration.

Reproduce the project compiler version exactly

When tracking down "works on my machine" type errors, compare the TypeScript version in use across machines.

bash
npx tsc --version
npm ls typescript --json | jq -r '.dependencies.typescript.version'

Output:

text
Version 5.4.5
5.4.5

If those two disagree, an editor or a script is using a different version. Force the local version via npx, restart the editor, and confirm again.