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.
80
100%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
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.
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.
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: "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.
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:
// bun library: packages/workspace, filesystem, util, sync, cli, ...
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"types": ["bun"],
"noUnusedLocals": true,
"noUnusedParameters": true
}
}
// svelte library: packages/ui, svelte-utils
{
"extends": "../../tsconfig.dom.json",
"compilerOptions": {
"types": ["bun"],
"noUnusedLocals": true,
"noUnusedParameters": true
}
}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 purposeThe 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 rulesinclude 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.include when you must narrow scope for a real reason; comment why.monorepo skill's boilerplate.package.json uses exports only, no main/types. Entry point is
./src/index.ts.bun install at the repo root, then bun typecheck.The full rationale, the migration that established this layout, and the baseline
typecheck state are in specs/20260522T190000-modernize-monorepo-tsconfig.md.
cb12bcc
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.