zod
                                
                                 zod copied to clipboard
                                
                                    zod copied to clipboard
                            
                            
                            
                        TS compilation perf: steal faster extendShape type from tRPC
I have some empirical evidence in a decently sized closed-source project that liberal use of ZodObject.extend() seems to degrade TS compilation and intellisense performance quite rapidly. I remembered seeing an equivalent implementation of extendShape in tRPC (in that project, called Overwrite) that looked simpler. I did some light profiling on it using said closed-source project and here are the results:
// Zod@jussisaurio
% npx tsc --noEmit --extendedDiagnostics --incremental false
Files:                        1248
Lines of Library:            39145
Lines of Definitions:       126023
Lines of TypeScript:         40594
Lines of JavaScript:             0
Lines of JSON:                   0
Lines of Other:                  0
Identifiers:                183660
Symbols:                    290202
Types:                      147133
Instantiations:            2361427
Memory used:               345043K
Assignability cache size:    34109
Identity cache size:          1470
Subtype cache size:            708
Strict subtype cache size:   22394
I/O Read time:               0.04s
Parse time:                  0.38s
ResolveModule time:          0.09s
ResolveTypeReference time:   0.01s
ResolveLibrary time:         0.01s
Program time:                0.60s
Bind time:                   0.17s
Check time:                  1.73s
printTime time:              0.00s
Emit time:                   0.00s
Total time:                  2.49s
// [email protected]
% npx tsc --noEmit --extendedDiagnostics --incremental false
Files:                        1248
Lines of Library:            39145
Lines of Definitions:       126004
Lines of TypeScript:         40594
Lines of JavaScript:             0
Lines of JSON:                   0
Lines of Other:                  0
Identifiers:                183556
Symbols:                    286851
Types:                      152232
Instantiations:            7692583
Memory used:               356978K
Assignability cache size:    33682
Identity cache size:          1464
Subtype cache size:            708
Strict subtype cache size:   22394
I/O Read time:               0.04s
Parse time:                  0.39s
ResolveModule time:          0.10s
ResolveTypeReference time:   0.01s
ResolveLibrary time:         0.01s
Program time:                0.60s
Bind time:                   0.17s
Check time:                  2.80s
printTime time:              0.00s
Emit time:                   0.00s
Total time:                  3.57s
// Zod@jussisaurio, run #2
% npx tsc --noEmit --extendedDiagnostics --incremental false
Files:                        1248
Lines of Library:            39145
Lines of Definitions:       126023
Lines of TypeScript:         40594
Lines of JavaScript:             0
Lines of JSON:                   0
Lines of Other:                  0
Identifiers:                183660
Symbols:                    290202
Types:                      147133
Instantiations:            2361427
Memory used:               345221K
Assignability cache size:    34109
Identity cache size:          1470
Subtype cache size:            708
Strict subtype cache size:   22394
I/O Read time:               0.04s
Parse time:                  0.40s
ResolveModule time:          0.10s
ResolveTypeReference time:   0.01s
ResolveLibrary time:         0.01s
Program time:                0.63s
Bind time:                   0.17s
Check time:                  1.80s
printTime time:              0.00s
Emit time:                   0.00s
Total time:                  2.60s
// [email protected], run #2
% npx tsc --noEmit --extendedDiagnostics --incremental false
Files:                        1248
Lines of Library:            39145
Lines of Definitions:       126004
Lines of TypeScript:         40594
Lines of JavaScript:             0
Lines of JSON:                   0
Lines of Other:                  0
Identifiers:                183556
Symbols:                    286851
Types:                      152232
Instantiations:            7692583
Memory used:               356570K
Assignability cache size:    33682
Identity cache size:          1464
Subtype cache size:            708
Strict subtype cache size:   22394
I/O Read time:               0.04s
Parse time:                  0.40s
ResolveModule time:          0.10s
ResolveTypeReference time:   0.01s
ResolveLibrary time:         0.01s
Program time:                0.63s
Bind time:                   0.16s
Check time:                  2.81s
printTime time:              0.00s
Emit time:                   0.00s
Total time:                  3.61s
Main thing (apart from faster runtime) is the dramatically lower number of type instantiations.
Deploy Preview for guileless-rolypoly-866f8a ready!
Built without sensitive environment variables
| Name | Link | 
|---|---|
| Latest commit | 5b2ad4c95321e8441619a217f71b1e8b313be7f8 | 
| Latest deploy log | https://app.netlify.com/sites/guileless-rolypoly-866f8a/deploys/651d9277c512c00008157d30 | 
| Deploy Preview | https://deploy-preview-2839--guileless-rolypoly-866f8a.netlify.app | 
| Preview on mobile | Toggle QR Code...Use your smartphone camera to open QR code link. | 
To edit notification comments on pull requests, go to your Netlify site configuration.
I feel like there are edge cases where dropping flatten breaks the inferred type signature...but I can't find them.
I'll do a little more testing then merge. Thanks!
FWIW I can try to run some perf tests on how much keeping flatten has an impact. I'm guessing though that replacing identity with something like & {} in the definition of flatten would also make it perform better due to less instantiations
Used this: https://github.com/jussisaurio/zod-ts-perftest (there are already pregenerated zod schemas in the repo)
Running npm run build-bench, three runs each:
master: 4.51s, 4.56s, 4.51s PR #2839 (this one): 3.69s, 3.61s, 3.72s PR #2839 + flatten added back: 3.77s, 3.77s, 3.72s PR #2845: 2.29s, 2.20s, 2.21s Both PRs: 2.14s, 2.14s, 2.16s
Not too much of a difference at all between keeping or removing flatten, but for some reason this benchmark is really sensitive to the changes in #2845 😲 I'm sure this particular benchmark is not very well rounded (zod-ts-perftest only generates objects, strings, numbers, booleans, plus a crapton of extends and omits).
We use extend quite a bit. This PR is a marked improvement.
zod 3.22.4:
npx tsc --noEmit --extendedDiagnostics --incremental false
Files:                         3394
Lines of Library:             40241
Lines of Definitions:        310600
Lines of TypeScript:          73937
Lines of JavaScript:              0
Lines of JSON:                    0
Lines of Other:                   0
Identifiers:                 592152
Symbols:                     950503
Types:                       311682
Instantiations:             8492589
Memory used:               1151591K
Assignability cache size:    273407
Identity cache size:          23254
Subtype cache size:            5426
Strict subtype cache size:     7230
I/O Read time:                0.04s
Parse time:                   0.65s
ResolveModule time:           0.20s
ResolveTypeReference time:    0.00s
ResolveLibrary time:          0.01s
Program time:                 1.02s
Bind time:                    0.34s
Check time:                   7.75s
printTime time:               0.00s
Emit time:                    0.00s
Total time:                   9.12s
zod 3.22.4 with this patch:
npx tsc --noEmit --extendedDiagnostics --incremental false
Files:                         3394
Lines of Library:             40241
Lines of Definitions:        310610
Lines of TypeScript:          73937
Lines of JavaScript:              0
Lines of JSON:                    0
Lines of Other:                   0
Identifiers:                 592160
Symbols:                     950391
Types:                       310870
Instantiations:             6238717
Memory used:               1058367K
Assignability cache size:    273343
Identity cache size:          23254
Subtype cache size:            5426
Strict subtype cache size:     7230
I/O Read time:                0.04s
Parse time:                   0.66s
ResolveModule time:           0.21s
ResolveTypeReference time:    0.00s
ResolveLibrary time:          0.01s
Program time:                 1.03s
Bind time:                    0.35s
Check time:                   7.32s
printTime time:               0.00s
Emit time:                    0.00s
Total time:                   8.70s
The time to check the whole app isn't a big deal, but VSCode's TS intellisense is a pain. It slowed down substantially after I rewrote a bunch of schemas using extend. We have a number of nested extend schemas in big unions.
Merged a variant in https://github.com/colinhacks/zod/pull/2845
Thanks!