Vitus Labs
Tools TypeScript

Tools TypeScript

Shared TypeScript configuration presets for libraries, Node packages, and Next.js applications.

@vitus-labs/tools-typescript provides three opinionated TypeScript configuration presets — one for bundled libraries, one for pure Node packages, one for Next.js applications. All target ES2024 with strict type checking.

Installation

npm install @vitus-labs/tools-typescript

Peer dependency: TypeScript >= 6

Presets

lib — Bundled Library Development

For packages bundled by Rolldown/Rollup/Vite/etc. before consumers see them. Uses moduleResolution: Bundler because the bundler resolves imports — extensions on relative imports are optional. Generates declaration files and source maps.

// tsconfig.json
{
  "extends": "@vitus-labs/tools-typescript/lib"
}

node — Pure Node Packages (built with plain tsc)

For server/CLI packages that emit raw .js directly via tsc and are consumed by Node's strict ESM resolver. Uses moduleResolution: NodeNext + rewriteRelativeImportExtensions — extensions on relative imports are required at compile time, and .ts/.tsx extensions in source are rewritten to .js at emit. No DOM, no JSX, no JS interop — pure server TypeScript.

// tsconfig.json
{
  "extends": "@vitus-labs/tools-typescript/node"
}
// Source can use .ts extensions; tsc rewrites to .js at emit
import { foo } from './foo.ts'

nextjs — Next.js Applications

For Next.js apps with SSR/SSG. Uses jsx: preserve (Next.js handles JSX transform) and enables incremental builds for fast development.

// tsconfig.json
{
  "extends": "@vitus-labs/tools-typescript/nextjs"
}

Comparison

Settinglibnodenextjs
targetES2024ES2024ES2024
modulePreserveNodeNextESNext
moduleResolutionBundlerNodeNextBundler
rewriteRelativeImportExtensionstrue
jsxreact-jsxpreserve
allowJstruetrue
libES2024, domES2024ES2024, dom, dom.iterable
typesnodenode
stricttruetruetrue
noUncheckedIndexedAccesstruetruetrue
declarationtruetrue
declarationMaptruetrue
sourceMaptruetrue
inlineSourcestruetrue
incrementaltrue
includesrcsrcnext-env.d.ts, **/*.ts, **/*.tsx
excludenode_modules, __stories__, libnode_modules, libnode_modules

Shared Principles

Both presets enforce:

  • strict: true — all strict type-checking flags enabled
  • noUncheckedIndexedAccess: true — indexed access returns T | undefined
  • moduleDetection: force — treats all files as modules
  • verbatimModuleSyntax: true — preserves import/export syntax exactly as written
  • forceConsistentCasingInFileNames: true — prevents cross-platform file casing issues
  • skipLibCheck: true — skips .d.ts checking for faster builds
  • resolveJsonModule: true — JSON imports supported
  • allowJs: true — JavaScript files allowed in TypeScript projects
  • noEmit: true — no JS output (bundler or Next.js handles compilation)
  • esModuleInterop: true — CommonJS/ESM interop compatibility

lib Preset Details

{
  "compilerOptions": {
    "target": "ES2024",
    "module": "Preserve",
    "moduleResolution": "Bundler",
    "moduleDetection": "force",
    "verbatimModuleSyntax": true,
    "jsx": "react-jsx",
    "lib": ["ES2024", "dom"],
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,
    "resolveJsonModule": true,
    "allowJs": true,
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "inlineSources": true,
    "noEmit": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "__stories__", "lib"]
}

When to use: Building reusable libraries that get bundled (Rolldown/Rollup/Vite/webpack/esbuild) before consumers see them. The bundler resolves imports, so Bundler resolution is permissive about extensions.

Key behaviors:

  • module: Preserve — lets the downstream bundler decide ESM vs CJS output format
  • moduleResolution: Bundler — bundler-friendly; extensions on relative imports are optional
  • jsx: react-jsx — uses the modern JSX transform (React 17+, no import React needed)
  • declaration: true + declarationMap: true — generates .d.ts files with source maps for IDE "Go to Definition"
  • inlineSources: true — embeds original source in source maps for debugging
  • lib: [ES2024, dom] — for browser/SSR libraries
  • Excludes __stories__ and lib directories

node Preset Details

{
  "compilerOptions": {
    "target": "ES2024",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "rewriteRelativeImportExtensions": true,
    "moduleDetection": "force",
    "verbatimModuleSyntax": true,
    "lib": ["ES2024"],
    "types": ["node"],
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,
    "resolveJsonModule": true,
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "inlineSources": true,
    "noEmit": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "lib"]
}

When to use: Server, CLI, or library packages that build with plain tsc and ship raw .js for Node's strict ESM resolver to consume directly. No bundler in the chain.

Key behaviors:

  • module: NodeNext + moduleResolution: NodeNext — Node's actual ESM resolution algorithm; extensions on relative imports are required at compile time
  • rewriteRelativeImportExtensions: true — write .ts/.tsx extensions in source; tsc rewrites them to .js at emit. The .js-in-source convention also still works
  • lib: [ES2024] — no DOM (server-only)
  • No jsx (server code shouldn't render React)
  • No allowJs (TS-only — server code shouldn't need to interop with JS sources)
  • types: ["node"] — Node typings included by default

The strictness catches missing-extension bugs at build time instead of consumer runtime — you can't ship a package whose imports won't resolve under Node ESM.

nextjs Preset Details

{
  "compilerOptions": {
    "target": "ES2024",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "moduleDetection": "force",
    "verbatimModuleSyntax": true,
    "jsx": "preserve",
    "lib": ["ES2024", "dom", "dom.iterable"],
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,
    "resolveJsonModule": true,
    "allowJs": true,
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noEmit": true,
    "incremental": true
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}

When to use: Next.js applications where Next.js handles transpilation via SWC.

Key behaviors:

  • module: ESNext — native ES modules; Next.js transpiler handles format conversion
  • jsx: preserve — JSX left as-is for Next.js SWC/Babel to transform
  • incremental: true — speeds up repeated tsc runs during development
  • dom.iterable in lib — includes DOM iteration APIs (e.g., NodeList.forEach)
  • Includes next-env.d.ts for Next.js generated types

Extending Presets

Override any setting in your project's tsconfig.json:

{
  "extends": "@vitus-labs/tools-typescript/lib",
  "compilerOptions": {
    "types": ["node", "vitest/globals"],
    "outDir": "lib",
    "rootDir": "src",
    "declarationDir": "./lib/types"
  },
  "include": ["src"],
  "exclude": ["node_modules", "__stories__", "lib"]
}

In a monorepo, each package selects the appropriate preset based on what consumers see:

monorepo/
├── packages/
│   ├── my-bundled-lib/         (bundled by rolldown before publish)
│   │   └── tsconfig.json → extends "@vitus-labs/tools-typescript/lib"
│   ├── my-cli-tool/            (built with plain tsc, ships raw .js)
│   │   └── tsconfig.json → extends "@vitus-labs/tools-typescript/node"
│   └── my-server/              (Node server, ships raw .js)
│       └── tsconfig.json → extends "@vitus-labs/tools-typescript/node"
└── apps/
    └── my-app/                 (Next.js app)
        └── tsconfig.json → extends "@vitus-labs/tools-typescript/nextjs"

Choosing between lib and node: if the package gets bundled before consumers see it, use lib. If consumers receive raw .js that Node loads directly (no bundler in the chain), use node — the strictness catches missing-extension bugs at compile time.

On this page