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-typescriptPeer 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
| Setting | lib | node | nextjs |
|---|---|---|---|
target | ES2024 | ES2024 | ES2024 |
module | Preserve | NodeNext | ESNext |
moduleResolution | Bundler | NodeNext | Bundler |
rewriteRelativeImportExtensions | — | true | — |
jsx | react-jsx | — | preserve |
allowJs | true | — | true |
lib | ES2024, dom | ES2024 | ES2024, dom, dom.iterable |
types | node | node | — |
strict | true | true | true |
noUncheckedIndexedAccess | true | true | true |
declaration | true | true | — |
declarationMap | true | true | — |
sourceMap | true | true | — |
inlineSources | true | true | — |
incremental | — | — | true |
include | src | src | next-env.d.ts, **/*.ts, **/*.tsx |
exclude | node_modules, __stories__, lib | node_modules, lib | node_modules |
Shared Principles
Both presets enforce:
strict: true— all strict type-checking flags enablednoUncheckedIndexedAccess: true— indexed access returnsT | undefinedmoduleDetection: force— treats all files as modulesverbatimModuleSyntax: true— preserves import/export syntax exactly as writtenforceConsistentCasingInFileNames: true— prevents cross-platform file casing issuesskipLibCheck: true— skips.d.tschecking for faster buildsresolveJsonModule: true— JSON imports supportedallowJs: true— JavaScript files allowed in TypeScript projectsnoEmit: 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 formatmoduleResolution: Bundler— bundler-friendly; extensions on relative imports are optionaljsx: react-jsx— uses the modern JSX transform (React 17+, noimport Reactneeded)declaration: true+declarationMap: true— generates.d.tsfiles with source maps for IDE "Go to Definition"inlineSources: true— embeds original source in source maps for debugginglib: [ES2024, dom]— for browser/SSR libraries- Excludes
__stories__andlibdirectories
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 timerewriteRelativeImportExtensions: true— write.ts/.tsxextensions in source;tscrewrites them to.jsat emit. The.js-in-source convention also still workslib: [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 conversionjsx: preserve— JSX left as-is for Next.js SWC/Babel to transformincremental: true— speeds up repeatedtscruns during developmentdom.iterableinlib— includes DOM iteration APIs (e.g.,NodeList.forEach)- Includes
next-env.d.tsfor 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.