Skip to content

chore: 12x faster TypeScript type-checking across the monorepo#16796

Merged
AlessioGr merged 6 commits into
mainfrom
perf/ts
May 29, 2026
Merged

chore: 12x faster TypeScript type-checking across the monorepo#16796
AlessioGr merged 6 commits into
mainfrom
perf/ts

Conversation

@AlessioGr

@AlessioGr AlessioGr commented May 29, 2026

Copy link
Copy Markdown
Member

TypeScript was very slow in this repo - worst when editing files in test/, and for ESLint (which runs TypeScript). This PR makes it a lot faster with a few small, type-only changes.

Why it was slow

Two simple root causes:

  1. A few core field helpers and config types made TypeScript build huge piles of throwaway types over and over. The Field config types are big and self-referencing, and these helpers/types kept rebuilding them. A handful of one-line type changes stop that.
  2. Every test folder loaded the entire test/ folder instead of its own files. Each test/<suite>/tsconfig.json only said extends, which made it pull in all ~1,600 test files (plus 87 clashing type declarations) every time you opened one test file.

What changed (4 small fixes)

  1. Field helper functions (fieldAffectsData and ~8 siblings, used in 100+ places): they narrowed a type by combining it with a big list of field types, which made TypeScript build hundreds of throwaway types on every call. Changed them to pick from the types that already exist instead. Same result, almost no new types created.
  2. Dashboard widgets type: the sanitized config ran the whole (big, recursive) field type through the DeepRequired helper, rebuilding it from scratch.
  3. Import-map helper (hasKey): same "combine with a big type" problem. Removed it (callers didn't need it and it's internal-only)
  4. Test folder tsconfigs: gave each test/<suite>/tsconfig.json its own file list so it only loads its own folder, not all of test/.

Benchmarks

Per package (tsc --noEmit, each package's own code)

pnpm exec tsc -b packages/payload && pnpm exec tsc -p packages/payload/tsconfig.json --noEmit --extendedDiagnostics
Package Before After Faster Types before → after
next 62.7 s 5.1 s ~12× 186,227 → 69,716
payload 44.0 s 5.1 s ~9× 492,198 → 110,403
ui 28.7 s 7.1 s ~4× 187,494 → 104,349
db-mongodb 3.2 s 1.8 s ~1.8× 69,486 → 68,060
drizzle 2.3 s 1.6 s ~1.4× 127,890 → 126,040
richtext-lexical 3.6 s 3.3 s ~same 74,602 → 73,993
db-postgres 0.29 s 0.29 s same 24,461 → 24,461
translations 0.33 s 0.30 s same 51,282 → 51,282

The big wins (payload, next, ui) are the packages that use the field types heavily.

Whole monorepo build

pnpm bf
Before After
Full build 2m27 47s

Test packages

Before After
test/fields suite Ran out of memory 20.0 s
Single test file that imports payload 76.6 s, 4.25 GB, 489,838 types 4.6 s, 0.62 GB, 113,079 types

Single-file: ~17× faster, ~7× less memory, ~4× fewer types.

Editor latency (real tsserver - open a file, time until errors show)

Open this file Before (load / errors) After (load / errors)
a test/ file (test/fields/int.spec.ts) 13.7 s / 14.0 s 2.6 s / 2.5 s
a payload source file (config/client.ts) 1.5 s / 3.1 s 1.2 s / 1.4 s

@AlessioGr AlessioGr requested a review from denolfe as a code owner May 29, 2026 18:43
@AlessioGr AlessioGr enabled auto-merge (squash) May 29, 2026 18:44
@github-actions

github-actions Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

📦 esbuild Bundle Analysis for payload

This analysis was generated by esbuild-bundle-analyzer. 🤖
This PR introduced no changes to the esbuild bundle! 🙌

@AlessioGr AlessioGr merged commit 5ca3afb into main May 29, 2026
322 of 326 checks passed
@AlessioGr AlessioGr deleted the perf/ts branch May 29, 2026 20:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants