Projects / Open Source Package

Open Source Package

tsconfig-base

This one isn't a code package. There's no `src/` folder. No build step. Nothing to compile, because there's nothing written in a language that needs compiling.

This one isn't a code package. There's no src/ folder. No build step. Nothing to compile, because there's nothing written in a language that needs compiling. It's five JSON files, base.json, strict.json, node-lib.json, react-app.json, cli.json, meant to be extends-ed from a real project's tsconfig.json instead of copy-pasted and re-typed every time I start something new.

I'm including it in this list anyway, because "config as a package" is still a real decision, and it's worth explaining rather than skipping past because it doesn't look like a typical piece of software.

The actual problem it solves

Every new TypeScript project starts with the same fifteen minutes of typing out compiler options. strict: true. esModuleInterop: true. skipLibCheck: true, because checking the types inside your dependencies' own .d.ts files is almost never worth the build time it costs. Declaration files, source maps, module resolution: the list is long enough that getting it right from memory, every time, for every new repo, is exactly the kind of repetitive task that should live in one place instead of being retyped with small inconsistencies each time.

base.json is that list, once. strict.json extends it and adds the more aggressive checks (noUncheckedIndexedAccess, exactOptionalPropertyTypes) for projects where I want TypeScript to actually stop me from writing certain classes of bugs, not just the classes it stops by default. node-lib.json is for libraries meant to run in Node, excludes test files from the build. react-app.json layers on jsx: react-jsx, the DOM lib, and noEmit, since a React app doesn't need TypeScript to emit JS; the bundler does that. cli.json sets stripInternal and targets CommonJS, for command-line tools where ESM's extra ceremony isn't worth it.

Why this exists at all

I'm not building one project. I'm building fifteen: the npm packages in this batch alone are five of them, and there's Nexli, Nyxera, Veythar, and whatever comes next on top of that. Every one of those needs a tsconfig. Without a shared base, each one drifts slightly from the others: one has strict on, one forgot it, one has a slightly different module resolution setting because I copied it from a different starting point six months apart. That drift is invisible until it isn't, until a build behaves differently between two projects for a reason that turns out to be "the tsconfig quietly disagreed," and you lose an hour figuring out why identical-looking code compiles in one repo and errors in another.

tsconfig-base is how I stopped that drift from accumulating. One canonical set of defaults, five flavors of it for the shapes of project I actually build, and every new repo starts from the same place instead of from memory.

The one inconsistency worth naming

The README claims moduleResolution: "bundler" is part of the base config. It isn't. That setting only actually appears in react-app.json. It's a small documentation slip. I was probably thinking about the react-app variant while writing the README's description of the base and didn't separate the two clearly enough. Worth fixing, low stakes, the kind of thing that only matters if someone's reading the docs closely enough to extend from base.json expecting bundler-mode resolution and not finding it.

No LICENSE beyond boilerplate, and that's fine

There's nothing to test here, in the conventional sense; you can't unit-test a JSON file's compiler options, only use them and see if the resulting build behaves the way you expect. So there's no test suite, and unlike some of my other packages where that's a real gap, here it's just the nature of what the package is. The "test suite" for tsconfig-base is every project that extends it and either type-checks cleanly or doesn't.

It's the smallest, plainest package in the batch, and it's also one of the ones I reach for the most, in the most literal sense. Every single new repo, the very first commit, before there's any actual application code, there's a tsconfig extending one of these five files. Unglamorous infrastructure is still infrastructure. The projects that actually ship don't get there because of the interesting decisions. They get there because a hundred boring decisions, like "what are my compiler options," got made once and then stopped needing to be made again.

The five variants, and why five and not one

I could have shipped a single tsconfig.json and told everyone to override what they needed. I didn't, because the overrides would have ended up looking almost identical across every project anyway: every React app I build turns off noEmit the same way, every CLI targets CommonJS for the same reason, every library excludes test files from its build output the same way. If the overrides are going to be identical every time, they're not really overrides. They're just the base config for that category of project, and pretending otherwise means writing the same five lines of JSON in fifteen different repos instead of naming the category once and extending it.

That's the actual design principle underneath this: a "base" that's genuinely shared, and a small number of named shapes on top of it for the handful of project types I actually build repeatedly. Not infinite configurability. Five flavors, because I've only ever needed five.

What strict.json catches that base.json doesn't

base.json turns on strict, which is TypeScript's umbrella flag: no implicit any, null checks, the basics everyone should have on by default in 2026. strict.json extends that and adds the options that are still opt-in even under strict: noUncheckedIndexedAccess, which forces you to treat array[i] as possibly undefined instead of assuming the index exists, and exactOptionalPropertyTypes, which stops you from treating { foo?: string } and { foo: string | undefined } as the same thing when they aren't quite.

Those two options catch a specific, real class of bug: the one where code reads perfectly reasonably, passes a normal strict check, and still crashes at runtime because an array was shorter than you assumed or an optional field was explicitly set to undefined instead of just omitted. I don't turn strict.json on for every project. Nexli's client code uses it; a quick internal tool usually doesn't, because the extra rigor costs iteration speed I'm not always willing to spend. That's a judgment call I make per project, and having it as a separate extendable file instead of baked permanently into base.json is what makes that judgment call cheap to make each time.

The honest tradeoff of shared config

There's a real cost to this approach that I don't want to gloss over: when I change base.json, every project extending it inherits that change the next time it installs. Most of the time that's the entire point: fix a compiler option in one place, every repo benefits. Occasionally it means a stricter check I added for Nexli's sake ships silently into a smaller project and produces a new error that has nothing to do with anything that project's owner, meaning me, in a different context, was actually working on that day. I've hit that a couple of times. It's mildly annoying. It's still a better trade than the alternative, which is fifteen tsconfigs slowly drifting apart until nobody, including me, can say with confidence which project is actually running which rules.

Full stack developer. Founder of Yashveer Labs. "extends": "@yashveerlabs/tsconfig-base/react-app.json", and one less thing to think about before the real work starts.

Start a conversation about this.

Whether it's tsconfig-base itself or the next system worth building, the lab is reachable.