| name | tsconfig |
| description | TypeScript config conventions for this monorepo: the two-base layering, the eight leaf tiers, and the never-redeclare list. Use when adding a package, editing any tsconfig.json, picking a tier for a new app, or debugging module resolution. |
| metadata | {"author":"epicenter","version":"1.0"} |
tsconfig conventions
Every package here is source-only .ts: exports point at ./src/*.ts,
there is no build step, and consumers (Bun, Vite, WXT, Tauri, the Cloudflare
Worker) operate on raw .ts. That single fact decides the whole config.
The one rule
A leaf tsconfig.json may set only: types, library-only strictness
(noUnusedLocals/noUnusedParameters), checkJs (SvelteKit), and genuinely
package-specific options (jsx, paths, customConditions, include).
Anything else belongs in a base. If a leaf option repeats a base value or a
TypeScript default, delete it.
Two base files
tsconfig.base.json universal: target/lib[ESNext], module preserve, strict,
noEmit, isolatedModules, verbatimModuleSyntax, types []
tsconfig.dom.json extends base; its ONLY job is lib [ESNext, DOM, DOM.Iterable]
There is no tsconfig.base.lib.json and no project references. Source-only
packages never emit, so composite/declaration/outDir have no place here.
Module strategy: one, repo-wide
module: "preserve" + moduleResolution: "bundler". preserve implies
bundler (TS 5.4+); the explicit pair is kept for legibility. Never use
NodeNext anywhere: nothing in this repo is published as emitted Node ESM,
and bundler resolution already reads package.json imports/exports.
The eight leaf tiers
Pick the tier, copy the shape, change nothing else.
| Tier | extends | Leaf adds |
|---|
| Bun library | "../../tsconfig.base.json" | types:["bun"], noUnusedLocals, noUnusedParameters |
| Node library | "../../tsconfig.base.json" | types:["node"], noUnusedLocals, noUnusedParameters |
| Svelte library | "../../tsconfig.dom.json" | types:["bun"], noUnusedLocals, noUnusedParameters |
| SvelteKit app | ["../../tsconfig.base.json", "./.svelte-kit/tsconfig.json"] | checkJs:true, types:["bun"] |
| Cloudflare Worker | "../../tsconfig.base.json" | jsx, jsxImportSource, types, include |
| Bun app | "../../tsconfig.base.json" | types:["bun"], include |
| Astro app | ["../../tsconfig.base.json", "astro/tsconfigs/strict"] | astro include/exclude |
| WXT extension | ["../../tsconfig.dom.json", "./.wxt/tsconfig.json"] | customConditions, paths, include |
A generated config (./.svelte-kit/tsconfig.json, ./.wxt/tsconfig.json) goes
last in the array so its lib/module win where they must. Never hand-edit
a generated config.
The two canonical library shapes in full:
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"types": ["bun"],
"noUnusedLocals": true,
"noUnusedParameters": true
}
}
{
"extends": "../../tsconfig.dom.json",
"compilerOptions": {
"types": ["bun"],
"noUnusedLocals": true,
"noUnusedParameters": true
}
}
Never redeclare these (base value or TS default)
Putting any of these in a leaf is dead weight. Delete on sight.
| Do not write in a leaf | Why |
|---|
module: "preserve" | already in tsconfig.base.json |
moduleResolution, target, noEmit, strict, isolatedModules | already in the base |
lib: ["ESNext"] | the base default; use tsconfig.dom.json if you need DOM |
noPropertyAccessFromIndexSignature: false | that is already the TS default |
useDefineForClassFields: true | default when target >= ES2022 |
forceConsistentCasingInFileNames | default true since TS 5.0 |
resolvePackageJsonExports | default true under moduleResolution: bundler |
sourceMap | no-op under noEmit |
single-element extends array | use the string form: "extends": "../../tsconfig.base.json" |
types is opt-in, on purpose
The base sets types: [] to disable auto-inclusion of every node_modules/@types
package. Each leaf opts in: ["bun"] for almost everything, ["node"] for the
one package that uses @types/node. Do not hoist ["bun"] to a base: a package
without @types/bun installed would then fail with Cannot find type definition.
include rules
- TypeScript does not merge
include across extends. A leaf include
fully replaces the inherited one. For SvelteKit apps this means: do not set
your own include, or you drop the generated ambient.d.ts/$types globs.
- Only set
include when you must narrow scope for a real reason; comment why.
Adding a new package
- Scaffold via the
monorepo skill's boilerplate.
- Pick a tier from the table above; copy that exact shape.
package.json uses exports only, no main/types. Entry point is
./src/index.ts.
bun install at the repo root, then bun typecheck.
Background
The full rationale, the migration that established this layout, and the baseline
typecheck state are in specs/20260522T190000-modernize-monorepo-tsconfig.md.