Learn-skills.dev shared-tooling-typescript-config
TypeScript strict mode configs, TS 5.x+ options (verbatimModuleSyntax, module preserve, moduleDetection force, configDir), path alias sync, specialized configs
git clone https://github.com/NeverSight/learn-skills.dev
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/agents-inc/skills/shared-tooling-typescript-config" ~/.claude/skills/neversight-learn-skills-dev-shared-tooling-typescript-config && rm -rf "$T"
data/skills-md/agents-inc/skills/shared-tooling-typescript-config/SKILL.mdTypeScript Configuration Patterns
Quick Guide: Shared TypeScript strict config in
. Enablepackages/typescript-config/plusstrict: true,noUncheckedIndexedAccess,exactOptionalPropertyTypes. Use modern module settings:noImplicitOverride,module: "preserve",moduleResolution: "bundler",verbatimModuleSyntax: true. UsemoduleDetection: "force"(TS 5.5+) for portable paths. Sync path aliases between tsconfig and your build tool.${configDir}
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
, named constants)import type
(You MUST enable TypeScript strict mode (
) in ALL tsconfig.json files - non-negotiable)strict: true
(You MUST use
to enforce explicit verbatimModuleSyntax: true
- replaces deprecated import type
)importsNotUsedAsValues
(You MUST use shared config pattern (
) in monorepos - never duplicate configs per package)packages/typescript-config/
(You MUST sync path aliases between tsconfig.json and your build tool - mismatches cause import resolution failures)
(You MUST use modern module settings:
, module: "preserve"
for bundler-based projects)moduleResolution: "bundler"
</critical_requirements>
Auto-detection: TypeScript config, tsconfig.json, tsconfig, strict mode, noUncheckedIndexedAccess, exactOptionalPropertyTypes, verbatimModuleSyntax, moduleDetection force, module preserve, moduleResolution bundler, configDir, path aliases, typescript-config, shared config, noImplicitOverride, isolatedDeclarations, erasableSyntaxOnly, import defer
When to use:
- Setting up TypeScript strict mode in new or existing projects
- Creating shared tsconfig patterns for monorepo consistency
- Configuring TS 5.x+ modern module settings (preserve, bundler, verbatimModuleSyntax)
- Syncing path aliases between tsconfig and your build tool
- Creating specialized configs (React, Node.js, library publishing)
- Migrating from deprecated TypeScript options
- Evaluating new TS features (
,isolatedDeclarations
,erasableSyntaxOnly
,configDir
)import defer
When NOT to use:
- Runtime TypeScript code patterns (see language/framework skills)
- Linter configuration (separate skill)
- Build tool configuration (separate skill) - but DO keep path alias sync guidance here
- Daily coding conventions like naming and imports (see CLAUDE.md)
Key patterns covered:
- Shared strict config base with monorepo extension pattern
- Modern module settings (TS 5.x+: preserve, bundler, verbatimModuleSyntax, moduleDetection)
template variable for portable shared configs (TS 5.5+)${configDir}- Path alias sync between tsconfig and build tools
- Specialized configs (react.json, node.json, library.json)
for parallel build support (TS 5.5+)isolatedDeclarations
for Node.js direct execution (TS 5.8+)erasableSyntaxOnly
for deferred module evaluation (TS 5.9+)import defer- TypeScript 6.0 new defaults and deprecations
Detailed resources:
- examples/core.md - Full config examples, specialized configs, TS 5.x+ features
- reference.md - Decision frameworks, anti-patterns, gotchas
<philosophy>
Philosophy
TypeScript configuration should be strict by default, shared across packages, and forward-compatible. Every project starts with the strictest settings. Shared configs prevent drift. Modern module settings align with bundler-based workflows.
Core principles:
- Strict by default -
plusstrict: true
,noUncheckedIndexedAccess
,exactOptionalPropertyTypesnoImplicitOverride - Share, don't duplicate - Monorepo configs extend a shared base; standalone projects use the same strict options
- Modern module mode -
+module: "preserve"
for bundler projects;moduleResolution: "bundler"
/"node18"
for Node.js"node20" - Path alias parity - Aliases must exist in both tsconfig AND the build tool
<patterns>
Core Patterns
Pattern 1: Shared Strict Config Base
All apps and packages extend a shared strict base. The base config lives in
packages/typescript-config/ (monorepo) or is inlined in a standalone project.
// packages/typescript-config/base.json (abbreviated) { "compilerOptions": { "strict": true, "exactOptionalPropertyTypes": true, "noUncheckedIndexedAccess": true, "noImplicitOverride": true, "module": "preserve", "moduleResolution": "bundler", "verbatimModuleSyntax": true } }
Consumer configs extend the shared base and only add what differs (e.g.
paths).
Why good: Single source of truth for strict settings, all packages get the same safety guarantees, consumers only add what differs
See examples/core.md for full base config, consumer configs, and specialized configs (react.json, node.json, library.json).
Pattern 2: Modern Module Settings (TS 5.x+)
verbatimModuleSyntax (TS 5.0+)
Enforces explicit
import type for type-only imports. Replaces deprecated importsNotUsedAsValues and preserveValueImports.
// With verbatimModuleSyntax: true // Good - explicit type import import type { User } from "./types"; import { createUser } from "./api"; // Bad - type imported as value (errors with verbatimModuleSyntax) import { User, createUser } from "./api";
module: "preserve" (TS 5.4+)
Preserves import/export syntax as-is. TypeScript only type-checks; the bundler handles module output.
When to use: Bundler-based projects where TypeScript does NOT emit JavaScript (
noEmit: true)
When not to use: Node.js packages that emit CJS/ESM directly -- use
module: "node18" or "node20" instead
moduleDetection: "force" (TS 5.0+)
Forces all files to be treated as modules, even without
import/export. Prevents unexpected global scope pollution.
Pattern 3: ${configDir} Template Variable (TS 5.5+)
Makes shared configs portable.
${configDir} resolves to the directory of the leaf config (the one that extends), not the base.
// packages/typescript-config/base.json { "compilerOptions": { "outDir": "${configDir}/dist", "rootDir": "${configDir}/src" }, "include": ["${configDir}/src"] }
Why good: Consumers don't need to override
outDir, rootDir, or include -- paths resolve relative to their own directory
Gotcha: Without
${configDir}, relative paths like "./dist" resolve from the base config's location (packages/typescript-config/), not the consuming package.
Pattern 4: Path Alias Sync
Path aliases must be configured in BOTH
tsconfig.json AND your build tool. A mismatch causes either TypeScript errors or build-time import resolution failures.
// tsconfig.json -- paths here for TypeScript type checking { "compilerOptions": { "paths": { "@/*": ["./src/*"] } } } // Build tool config -- SAME aliases for bundler resolution // resolve: { alias: { "@": path.resolve(__dirname, "./src") } }
Gotcha: Forgetting to sync causes "module not found" errors. TypeScript resolves fine but the bundler fails (or vice versa). When adding a new alias, always update BOTH files.
See examples/core.md for full tsconfig + build tool config examples.
Pattern 5: isolatedDeclarations (TS 5.5+)
Requires explicit type annotations on all exports. Enables parallel
.d.ts generation by external tools (oxc, swc).
// With isolatedDeclarations: true // Good - explicit return type on export export function getUser(id: string): User { return { id, name: "John" }; } // Bad - inferred return type (will error) export function getUser(id: string) { return { id, name: "John" }; }
When to use: Large monorepos publishing library packages where parallel
.d.ts generation speeds up builds
When not to use: Application code that is never consumed as a library (adds verbosity for no benefit)
Pattern 6: erasableSyntaxOnly (TS 5.8+)
Prohibits TypeScript-specific constructs that have runtime behavior (enums, namespaces, parameter properties). Ensures compatibility with Node.js
--experimental-strip-types for direct TS execution.
Blocked constructs:
enum, const enum, runtime namespace, parameter properties (constructor(public name: string))
Allowed constructs:
type, interface, type-only imports -- anything that can be erased without changing runtime behavior
When to use: Projects using Node.js direct TS execution, or targeting the types-only philosophy
When not to use: Projects that rely heavily on enums, namespaces, or parameter properties
See examples/core.md for full good/bad code examples.
Pattern 7: import defer (TS 5.9+)
Deferred module evaluation -- the module loads but doesn't execute until an export is accessed. Only works with namespace imports under
--module preserve or esnext.
import * as analytics from "./heavy-analytics.js"; // Module is loaded but NOT executed yet if (needsAnalytics) { analytics.track("event"); // Module executes here on first access }
When to use: Improving startup performance for conditionally-loaded heavy modules
Limitation: Named imports and default imports are not supported with
import defer -- only namespace syntax (import defer * as ...)
Pattern 8: TypeScript 6.0 New Defaults
TS 6.0 (February 2026) changes several defaults. If your config already follows the patterns above, most changes are transparent.
| Option | Old Default | TS 6.0 Default |
|---|---|---|
| | |
| | |
| | |
| inferred | (current dir) |
| auto-discover | (explicit only) |
| | |
| | |
Action required: Set
"types": ["node"] (or relevant packages) explicitly after upgrading to TS 6.0 -- the auto-discovery of @types/* is gone.
Deprecated in TS 6.0 (removed in TS 7.0):
,target: "es5"
(node10),moduleResolution: "node"module: "amd"|"umd"|"systemjs"|"none"
,esModuleInterop: false
as module resolution root,--baseUrl--outFile
Use
"ignoreDeprecations": "6.0" during migration to suppress warnings.
See examples/core.md for full TS 6.0 defaults table and deprecation details.
Pattern 9: NoInfer<T> Utility Type (TS 5.4+)
Prevents TypeScript from inferring a type from a specific position in generic functions.
// Good - NoInfer ensures initial must be from states array declare function createFSM<TState extends string>(config: { initial: NoInfer<TState>; states: TState[]; }): void; createFSM({ initial: "invalid", // Error: "invalid" not in states states: ["open", "closed"], });
Why good: TypeScript infers
TState from states only; initial must match without widening the union
When to use: Generic functions where one parameter should constrain another, not expand the inferred type
</patterns><decision_framework>
Decision Framework
Module Settings Selection
What module/moduleResolution to use? ├─ Bundler-based project (TypeScript does NOT emit JS)? │ └─ YES -> module: "preserve", moduleResolution: "bundler" ├─ Node.js package (direct execution)? │ ├─ Node 20+? -> module: "node20" (TS 5.9+, stable) │ └─ Node 18+? -> module: "node18" (TS 5.8+) └─ Library consumed by both bundlers and Node? └─ module: "nodenext" (most compatible)
Target Selection
What target to use? ├─ Bundler-based project? -> target: "ES2022" (stable, well-supported) ├─ Node.js 18+? -> target: "ES2022" ├─ Node.js 20+? -> target: "ES2023" └─ TS 6.0+ project? -> target: "es2025" (new default)
New Feature Adoption
isolatedDeclarations? ├─ Publishing library packages in large monorepo? -> Enable ├─ Application code only? -> Skip (adds verbosity for no benefit) └─ Using oxc or swc for builds? -> Enable (these tools benefit most) erasableSyntaxOnly? ├─ Using Node.js --experimental-strip-types? -> Enable (required) ├─ Project uses enums extensively? -> Skip (migration cost too high) └─ Standard bundler project? -> Optional (nice-to-have)
See reference.md for additional decision frameworks (shared vs local config, TS 6.0 migration).
</decision_framework>
<red_flags>
RED FLAGS
High Priority Issues:
- Disabling TypeScript strict mode (
) -- allows implicit any and null bugs across the projectstrict: false - Missing
-- type imports may or may not be elided depending on transpilerverbatimModuleSyntax: true - Not using shared configs in monorepo -- configs drift, creating inconsistent safety levels across packages
- Using deprecated
orimportsNotUsedAsValues
-- replaced bypreserveValueImports
since TS 5.0verbatimModuleSyntax
Medium Priority Issues:
- Path aliases in tsconfig but not in build tool (or vice versa) -- causes import resolution failures
- Using deprecated
(node10) instead ofmoduleResolution: "node"
or"bundler"
-- deprecated in TS 6.0"node18" - Using deprecated
ortarget: "ES3"
-- deprecated in TS 6.0, removed in TS 7.0"ES5" - Duplicated config per package instead of extending shared base
Gotchas & Edge Cases:
resolves to the directory of the leaf config (the one that uses${configDir}
), not the base configextends
requires ALL type-only imports to useverbatimModuleSyntax
-- mixed imports likeimport type
error ifimport { Type, value }
is type-only. Use inline syntax:Typeimport { type Type, value }
meansexactOptionalPropertyTypes
does NOT accept{ key?: string }
-- only omission or{ key: undefined }string
addsnoUncheckedIndexedAccess
to ALL index signatures, including arrays -- use| undefined
or guard withfor...ofif
only works withmodule: "preserve"
ornoEmit: trueemitDeclarationOnly: true- TS 6.0 changes
default totypes
--[]
,@types/node
etc. must be explicitly listed after upgrade@types/react
only supports namespace syntax (import defer
), not named or default importsimport defer * as ...
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
, named constants)import type
(You MUST enable TypeScript strict mode (
) in ALL tsconfig.json files - non-negotiable)strict: true
(You MUST use
to enforce explicit verbatimModuleSyntax: true
- replaces deprecated import type
)importsNotUsedAsValues
(You MUST use shared config pattern (
) in monorepos - never duplicate configs per package)packages/typescript-config/
(You MUST sync path aliases between tsconfig.json and your build tool - mismatches cause import resolution failures)
(You MUST use modern module settings:
, module: "preserve"
for bundler-based projects)moduleResolution: "bundler"
Failure to follow these rules will cause type-safety gaps, inconsistent configs, and import resolution failures.
</critical_reminders>