@aleph-alpha/tsconfig-frontend
v0.7.0
Published
Shared strict TypeScript presets (base, vue-app, lib, node) for Aleph Alpha frontend projects
Maintainers
Readme
@aleph-alpha/tsconfig-frontend
Shared, strict-by-default TypeScript configuration for Aleph Alpha frontend projects.
It ships a small set of composable presets. The idea: everything that is genuinely
shareable (language target, module resolution, the strict family, common quality flags)
lives here in one reviewed place; everything project-specific (paths, include/exclude,
emit layout, environment types) stays in the consuming project. Every compilerOption a
preset sets can be overridden locally — presets are a baseline, not a straitjacket.
pnpm add -D @aleph-alpha/tsconfig-frontendWhy four presets instead of one?
base is the single extensible core. The other three exist because TypeScript compilation
has distinct, mutually-exclusive contexts that one config cannot serve at once — and which
every frontend project already separates (a tsconfig.app.json for browser code + a
tsconfig.node.json for tooling is the de-facto standard):
- Browser/Vue code needs
DOMlibs + JSX; Node tooling must not have them. - Node tooling needs
types: ["node"]; browser code must not pollute its global scope with them. - Packages that emit
.d.tsneeddeclaration/composite; apps that don't emit would error on them.
Collapsing to one config wouldn't remove that configuration — it would just copy-paste it into
every project (and invite mistakes like a too-low lib). The presets move it into one place.
Presets at a glance
| Preset | Extends | Use it for | Emits? |
| --- | --- | --- | --- |
| /base | — | Framework-agnostic TS: shared libs, utils, pure logic | no |
| /vue-app | @vue/tsconfig | Vue SFCs & browser code (apps and Vue component libs) | no (Vite/dts plugin emits) |
| /lib | ./base | Buildable libraries that emit .d.ts via tsc | yes |
| /node | @tsconfig/node24 | Node tooling: vite.config, vitest.config, eslint.config, scripts | no |
The bare
@aleph-alpha/tsconfig-frontendentry still resolves (it maps to the legacy flat config) for backwards compatibility. New code should extend one of the four presets above.
/base
What — the framework-agnostic strict baseline. No DOM, no Node, no JSX assumptions.
Sets (the shareable core all other presets build on conceptually):
{
"target": "es2022",
"module": "esnext",
"moduleResolution": "bundler",
"lib": ["es2022"],
// strict family
"strict": true, // implies noImplicitAny, strictNullChecks, ...
"noUncheckedIndexedAccess": true, // arr[i] / obj[key] is T | undefined
"verbatimModuleSyntax": true, // type-only imports must use `import type`
"useDefineForClassFields": true,
// safe bug-catchers (no emit impact, catch real bugs)
"noImplicitOverride": true, // overrides must be marked `override`
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
// ergonomics
"isolatedModules": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"importHelpers": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true
}Why — this is the strictness contract for the whole org. noUncheckedIndexedAccess and the
bug-catchers catch real defects; verbatimModuleSyntax keeps imports unambiguous for bundlers.
When to use — pure TypeScript with no browser or Node assumptions (e.g. a utils package).
How to use
{
"extends": "@aleph-alpha/tsconfig-frontend/base",
"compilerOptions": {
"baseUrl": ".",
"paths": { "@/*": ["src/*"] }
},
"include": ["src/**/*.ts"]
}/vue-app
What — config for Vue Single-File Components and browser code. Extends the Vue team's official
@vue/tsconfig/tsconfig.dom.json, so it tracks upstream Vue/TS recommendations.
Sets (on top of @vue/tsconfig, which already provides strict, verbatimModuleSyntax,
useDefineForClassFields, jsx: "preserve", jsxImportSource: "vue", noEmit):
{
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"types": ["vite/client"],
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true
}Why — .vue files need JSX + DOM libs + Vue's jsxImportSource. Building on @vue/tsconfig
(rather than re-deriving it) avoids drift and gives us the Vue team's battle-tested defaults.
When to use — any app or component library containing .vue files. The actual JS/.d.ts output is
produced by Vite / vite-plugin-dts, not tsc — so noEmit: true (inherited) is correct here.
How to use
{
"extends": "@aleph-alpha/tsconfig-frontend/vue-app",
"compilerOptions": {
"baseUrl": ".",
"paths": { "@/*": ["src/*"] }
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}/lib
What — for buildable library packages that emit type declarations through tsc.
Sets (extends ./base):
{
"declaration": true,
"composite": true,
"lib": ["es2022", "dom", "dom.iterable"]
}Why — emitting libraries need declaration (to ship .d.ts) and composite (project
references / incremental builds). These would be wrong for apps, so they live in their own preset.
When — a non-Vue package whose .d.ts is produced by tsc (not by Vite). Typically paired with
a local outDir/rootDir.
How
{
"extends": "@aleph-alpha/tsconfig-frontend/lib",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"paths": { "@/*": ["src/*"] }
},
"include": ["src/**/*.ts"]
}Note:
compositeis incompatible withoutFile. If a package bundles its declarations into a single file viaoutFile, extend/baseinstead and adddeclaration+emitDeclarationOnlylocally (see theoutFilegotcha below).
/node
What — config for Node-executed tooling files, not shipped browser code.
Sets (extends @tsconfig/node24):
{
"noEmit": true,
"module": "esnext",
"moduleResolution": "bundler",
"types": ["node"],
"noImplicitOverride": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true
}Why — vite.config.ts, vitest.config.ts, eslint.config.ts, build scripts, etc. run in
Node and need Node globals/types, not the DOM. Keeping them in a separate config means browser
code never accidentally pulls in Node types and vice-versa.
When to use — the tsconfig.node.json that type-checks your config/tooling files.
How to use
{
"extends": "@aleph-alpha/tsconfig-frontend/node",
"include": ["vite.config.ts", "vitest.config.ts", "eslint.config.ts"]
}What the package owns vs. what the project owns
In the presets (shareable): target, module/moduleResolution, baseline lib, the strict
family, the bug-catchers, and the emit shape for /lib. Change these only by changing the package
(so every consumer moves together).
In the project (always local & overridable): paths + baseUrl, include/exclude,
rootDir/outDir/tsBuildInfoFile, environment types, references,
experimentalDecorators/emitDecoratorMetadata, and any per-project strict opt-out/opt-in.
Common overrides & gotchas
These come up in real migrations — all are legitimate local overrides:
- Need a newer JS built-in (
Array.prototype.toSorted= ES2023, etc.)?@vue/tsconfigand our presets target a conservativelib. Bump it locally:"lib": ["ES2023", "DOM", "DOM.Iterable"]. This only affects type-checking — Vite controls the real browser target. - Code uses
window/DOM but extends/base? Add DOM toliblocally:"lib": ["ES2022", "DOM"]. (Or use/vue-app, which includes DOM.) outFilefor a single bundled.d.ts?outFileis incompatible with bothcompositeandverbatimModuleSyntax. Extend/base, setdeclaration+emitDeclarationOnly+outFile, and override"verbatimModuleSyntax": falselocally.- Node tooling that isn't a config file (e.g. a Node-targeted library)? Extend
/baseand add"types": ["node"]rather than/node(which forcesnoEmit). - Migrating a codebase that predates
noImplicitAny? If turning it on surfaces a large backlog, you may temporarily set"noImplicitAny": falselocally and burn the errors down later — but keep it on inbaseso new/clean code stays strict.
Convention: document the reason for any strict opt-out in the PR description, not as a comment in the
tsconfig— so it doesn't rot when the override is later removed.
