Skip to content

SheetArticle

Measure68ch · bone, not white

Filed underTypeScript · Tooling · Design systems

Published2026-08-09

9 August 2026 · 7 min

Every build was green through all three configurations

TypeScript 7 broke the declaration build, so I reached for the obvious fallback. Then I measured it before adopting it, and found 257 resolution errors that no build, in any of the three configurations, had ever reported.

TypeScript 7 broke tsup --dts outright. Cannot read properties of undefined, reading useCaseSensitiveFileNames. A library that had been publishing declarations fine for months stopped being able to publish them at all, which is the sort of thing that makes you reach for the nearest fallback and get on with your day.

The nearest fallback is tsc with emitDeclarationOnly. It works, it's built in, and it's what most answers on the internet will tell you to do. I nearly did it, and what stopped me was a habit rather than a suspicion. Measure the thing before you adopt it, even when the thing is boring.

The measurement

Three strategies, checked with arethetypeswrong against node16 ESM resolution:

  1. tsup, bundled (TS 5.9)

    Internal resolution errors (node16 ESM)
    0
  2. tsc tree (TS 7)

    Internal resolution errors (node16 ESM)
    257
  3. tsdown, bundled (TS 7)

    Internal resolution errors (node16 ESM)
    0
Declaration strategyInternal resolution errors (node16 ESM)
tsup, bundled (TS 5.9)0
tsc tree (TS 7)257
tsdown, bundled (TS 7)0
The middle row is the one I was about to ship, and it is 257 worse than the one it replaced.

tsc emits extensionless relative specifiers, so a declaration file that says ./shared/utils/cn doesn't resolve under node16 ESM, which wants the extension. Bundled declarations have no internal relative imports at all, so the question never comes up. tsdown was adopted because it bundles and it runs on TypeScript 7, which is the entire intersection.

The part worth taking away

Every build was green through all three configurations.

That's the whole article, really. The broken one compiled, it produced files, and it published. Anybody installing it would've got 257 unresolvable imports, and not one thing in my pipeline would have mentioned it, because compiling and being usable are different questions and only one of them was ever being asked.

It's a smoke alarm that tests itself by checking the button still clicks. Green light, satisfying beep, absolutely no opinion whatsoever about smoke.

And this is the bit that gets me. Every one of these tools is good. tsup is good. tsc is obviously good. attw is excellent and it's the only reason I know any of this. None of them were broken, and none of them were misconfigured, and between the three of them they produced a package that would have fallen over on first contact with another human being, which is the part I keep coming back to. Nobody was asleep at the wheel, there was just nobody in the seat that mattered.

So here's the question I'd put to anybody shipping a library this week. When did your build last tell you something you didn't already believe? Not when did it fail, everybody's build fails. When did it disagree with you? If you can't remember, it's worth asking what it's actually measuring, because mine had been agreeing with me enthusiastically for months.

I've written before about a test suite that stayed green for eight months because it had never loaded the rule it was meant to enforce. Same shape, smaller scale, and yet again the tooling worked exactly as configured, and yet again exactly as configured turned out to be a completely different thing from correct.

Honestly, this one annoys me more than the AAA one did, and the AAA one was a great deal more embarrassing. The fix was cheap and the check was cheap, and the whole thing came down to one command run before a decision instead of after it, which is about as low a bar as a bar gets. The only thing standing between me and publishing a broken package was a habit I happened to have that morning. A habit is a thin, thin thing to be hanging a guarantee on.

No share buttons, no claps, no newsletter thing sliding up from the bottom. If it's any good you'll send it to somebody yourself, and if it isn't then a row of icons was never going to fix that.