TypeScript Configuration

Effect projects benefit from strict TypeScript configuration.

Key Settings Explained

Build Performance

"incremental": true,
"composite": true,
  • incremental - Fast rebuilds via .tsbuildinfo cache
  • composite - Enables project references for monorepos

Module System

"target": "ES2022",
"module": "NodeNext",
"moduleDetection": "force",
  • ES2022 - Modern JS features (top-level await, etc)
  • NodeNext - Proper ESM/CJS resolution
  • force - Treats all files as modules

Import Handling

"verbatimModuleSyntax": true,
"rewriteRelativeImportExtensions": true,
  • verbatimModuleSyntax - Preserves import type syntax exactly
  • rewriteRelativeImportExtensions - Allows .ts in imports

Type Safety

"strict": true,
"exactOptionalPropertyTypes": true,
"noUnusedLocals": true,
"noImplicitOverride": true,
  • strict - All strict checks enabled
  • exactOptionalPropertyTypes - { x?: number } can't be { x: undefined }
  • noUnusedLocals - Catch unused variables
  • noImplicitOverride - Explicit override keyword required

Development

"declarationMap": true,
"sourceMap": true,
"skipLibCheck": true,
  • declarationMap - Jump-to-definition works for .d.ts
  • sourceMap - Debugging support
  • skipLibCheck - Faster builds (skip node_modules type checking)

Effect Integration

"plugins": [
  {
    "name": "@effect/language-service"
  }
]

Enables Effect language service for editor diagnostics. For build-time diagnostics, run bunx effect-language-service patch (see Project Setup).

Why These Settings?

  1. Performance - Incremental builds, composite projects
  2. Safety - Maximum type checking without escape hatches
  3. Modern - ESM-first, works with Node.js native modules
  4. DX - Source maps, declaration maps, Effect diagnostics

Module Settings by Project Type

The key difference between project types is the module and moduleResolution settings:

Bundled Apps (Vite, Webpack, esbuild, Rollup)

{
  "compilerOptions": {
    "module": "preserve",
    "moduleResolution": "bundler",
    "noEmit": true
  }
}

Use "module": "preserve" with "moduleResolution": "bundler" when a build tool handles module transformation. TypeScript acts as a type-checker only.

  • Allows flexible import paths (with or without file extensions)
  • Assumes bundler handles package.json exports/imports
  • Used for frontend apps and any code processed by a bundler

Libraries & Node.js Apps

{
  "compilerOptions": {
    "module": "NodeNext"
  }
}

Use "module": "NodeNext" when TypeScript is your compiler. Required for:

  • npm packages (libraries)
  • Node.js apps (including those using Bun or other runtimes)
  • CLI tools

This enforces Node.js module resolution rules:

  • Requires .js file extensions in relative imports
  • Respects package.json "type" and "exports" fields
  • Works with both ESM and CommonJS

Additional library settings:

{
  "compilerOptions": {
    "declaration": true,
    "composite": true,      // monorepos only
    "declarationMap": true  // monorepos only
  }
}

Rule of thumb: Build tool compiling your code? Use preserve + bundler. TypeScript compiling your code? Use NodeNext.