Setting up TypeScript

Configures TypeScript 7 in a Bun project.

Prerequisites

  • Bun project

Steps

  1. Install

    bun add -d typescript
  2. Configure — Bun / server-side project

    • tsconfig.json at the project root
    // tsconfig.json
    {
      "compilerOptions": {
        // Environment setup & latest features
        "lib": ["ESNext"],
        "target": "ESNext",
        "module": "Preserve",
        "moduleDetection": "force",
        "types": ["bun"],
    
        // Bundler mode
        "moduleResolution": "bundler",
        "allowImportingTsExtensions": true,
        "verbatimModuleSyntax": true,
        "erasableSyntaxOnly": true,
        "noEmit": true,
    
        // Best practices
        "strict": true,
        "skipLibCheck": true,
        "noFallthroughCasesInSwitch": true,
        "noUncheckedIndexedAccess": true,
        "noImplicitOverride": true,
        "exactOptionalPropertyTypes": true,
      },
      "include": ["scripts"],
    }
  3. Configure — browser app (Vite / TanStack Start)

    • Same base, plus DOM libs, vite/client types and path aliases
    • verbatimModuleSyntax is deliberately not set here — see the option reference below
    // tsconfig.json
    {
      "compilerOptions": {
        // Environment setup & latest features
        "lib": ["ESNext", "DOM", "DOM.Iterable"],
        "target": "ESNext",
        "module": "Preserve",
        "moduleDetection": "force",
        "jsx": "react-jsx",
        "types": ["vite/client", "bun"],
        "paths": { "@/*": ["./src/*"] },
    
        // Bundler mode
        "moduleResolution": "bundler",
        "allowImportingTsExtensions": true,
        // Enabling this in a TanStack Start app can result in the server bundles leaking into client bundles
        "verbatimModuleSyntax": false,
        // verbatimModuleSyntax would normally imply isolatedModules, so it has to be set directly
        "isolatedModules": true,
        "erasableSyntaxOnly": true,
        "noEmit": true,
    
        // Best practices
        "strict": true,
        "skipLibCheck": true,
        "noFallthroughCasesInSwitch": true,
        "noUncheckedIndexedAccess": true,
        "noImplicitOverride": true,
        "exactOptionalPropertyTypes": true,
      },
      "include": ["src", "vite.config.ts"],
    }
    • Pair it with oxlint's typescript/consistent-type-imports rule, which enforces import type without the bundling hazard verbatimModuleSyntax carries
    // .oxlintrc.jsonc
    {
      "rules": {
        "typescript/consistent-type-imports": "error",
      },
    }
  4. Add scripts to package.json

    • The bundler emits the JavaScript, so tsc only ever type-checks
    {
      "scripts": {
        "build": "bun --bun vite build && tsc --noEmit && oxlint ."
      }
    }

Verification

  • bunx tsc --noEmit exits 0

References