Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .changeset/tsconfig-app-preset.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"@btravstack/tsconfig": minor
---

`app.json`: `base.json` with `declaration` and `declarationMap` off, for a
workspace that emits no declarations.

Declaration emit is type-checked even under `noEmit`, so `declaration: true`
costs an application the errors it buys a library. Measured on a DI composition
root with one unmet dependency: two `TS4023` lines about a library's internal
`ID` / `SERVICE` brand symbols printed **first**, and the sentence naming the
missing port printed third. An application that ships no `.d.ts` has nothing to
gain from that check and pays for it on every mistake, internals-first.

A library keeps `base.json`, where the check is the guarantee that its consumers
can build.
26 changes: 26 additions & 0 deletions packages/tsconfig/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,32 @@ pnpm add -D @btravstack/tsconfig
`moduleDetection: "force"`, and friends, on `NodeNext` / `ES2022`. Override
anything in your own `compilerOptions`.

## `app.json`, for a package that emits no declarations

An application — a deployment, an example, a test workspace — extends
`app.json` instead, which is `base.json` with `declaration` and
`declarationMap` off:

```jsonc
// tsconfig.json
{
"extends": "@btravstack/tsconfig/app.json",
"compilerOptions": { "noEmit": true },
"include": ["src/**/*"],
}
```

The flag is not free when nothing consumes the declarations it type-checks:
declaration emit is checked even under `noEmit`, so an exported value whose
type reaches a library's unexported brand symbols reports `TS4023` — **before**
the error the developer actually made. Measured on a DI composition root with
one unmet dependency: two `TS4023` lines about internal `ID` / `SERVICE`
symbols came first, and the sentence naming the missing port came third. With
`app.json` the actionable diagnostic is the only one.

A library keeps `base.json`: there the declaration check is the guarantee that
its consumers can build.

It does **not** set `types` — TypeScript auto-includes every reachable
`@types/*` package (Node included). This avoids forcing each consuming package to
declare a direct `@types/node` just to satisfy a `types: ["node"]` list.
Expand Down
8 changes: 8 additions & 0 deletions packages/tsconfig/app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./base.json",
"compilerOptions": {
"declaration": false,
"declarationMap": false
}
}
2 changes: 2 additions & 0 deletions packages/tsconfig/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
"directory": "packages/tsconfig"
},
"files": [
"app.json",
"base.json"
],
"type": "module",
"exports": {
"./app.json": "./app.json",
"./base.json": "./base.json"
},
"publishConfig": {
Expand Down
8 changes: 7 additions & 1 deletion scripts/validate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const check = (name, fn) => {

// Every package ships exactly the files it lists, and those files load.
const shipped = {
tsconfig: ["base.json"],
tsconfig: ["app.json", "base.json"],
typedoc: ["base.json"],
oxlint: ["base.json"],
oxfmt: ["base.json"],
Expand All @@ -47,6 +47,12 @@ check("tsconfig/base.json is strict", () => {
if (tsc.compilerOptions?.strict !== true) throw new Error("strict must be true");
});

check("tsconfig/app.json extends base and emits no declarations", () => {
const app = json("packages/tsconfig/app.json");
if (app.extends !== "./base.json") throw new Error("app.json must extend ./base.json");
if (app.compilerOptions?.declaration !== false) throw new Error("declaration must be false");
});
Comment thread
btravers marked this conversation as resolved.

check("typedoc/base.json loads the markdown plugin", () => {
const td = json("packages/typedoc/base.json");
if (!td.plugin?.includes("typedoc-plugin-markdown")) {
Expand Down
Loading