Salutare! Ne lovim la muncă de o mică dilemă de performanță și aș vrea să aud părerile voastre, în special de la cei care s-au jucat mai serios cu ambele biblioteci. Suntem în plin proces de refactorizare pentru un API serverless cu vreo 50 de schema-uri de validare scrise în Zod și ne bate gândul să trecem pe Valibot ca să mai tăiem din bundle size.
De unde a plecat problema?
Avem o arhitectură pe AWS Lambda unde fiecare milisecundă la cold start contează. Zod e genial ca DX (Developer Experience), îl folosesc de ani de zile și n-am ce să-i reproșez la nivel de API. Dar are o mare problemă structurală: fiind bazat pe metode înlănțuite (chainable), tree-shaking-ul e aproape inexistent. Importi z și tragi după tine toată librăria, indiferent dacă folosești doar z.string() sau chestii complexe gen .transform() sau .refine().
La cele ~50 de endpoint-uri ale noastre, bundle-ul final pentru fiecare Lambda crește artificial cu vreo 45-50KB doar din cauza Zod. Nu pare mult la prima vedere, dar când ai zeci de mii de invocări pe zi și cold start-uri frecvente, fiecare extra-kilobyte se simte la latența de pornire a containerului.
Ce am testat cu Valibot
Am făcut săptămâna trecută un mic POC (Proof of Concept) pe 5 dintre cele mai folosite endpoint-uri, migrându-le pe Valibot. Rezultatele pe partea de bundle size sunt, într-adevăr, impresionante.
Am salvat cam 35% din greutatea bundle-ului pe acele funcții Lambda. Valibot folosește funcții individuale (v.string(), v.minLength()) în loc de metode pe obiect, ceea ce permite bundler-ului (folosim esbuild) să facă tree-shaking pe bune. Dacă nu folosești regex validation în acel fișier, codul pentru regex pur și simplu nu ajunge în build-ul final de producție.
Trade-off-ul care mă reține
Deși pe hârtie sună ideal, în practică m-am lovit de o problemă destul de mare de lizibilitate. Sintaxa chainable de la Zod e mult mai curată și mai ușor de urmărit de către colegii mai juniori din echipă.
Uite o comparație simplă pe care am observat-o:
- În Zod scrii rapid:
z.string().email().min(5) - În Valibot devine ceva de genul:
v.pipe(v.string(), v.email(), v.minLength(5))
Când ai scheme imbricate, cu obiecte în obiecte, array-uri și uniuni complexe, codul de Valibot devine destul de greu de citit din cauza parantezelor imbricate și a numărului mare de importuri individuale. În plus, ecosistemul e încă pro-Zod. Multe librărițe pe care le folosim au suport nativ sau mult mai stabil pentru Zod decât pentru Valibot.
Întrebările mele pentru voi
A trecut cineva prin migrarea asta la un proiect de dimensiuni medii?
- Cum a fost tranziția pentru echipă? A scăzut productivitatea din cauza sintaxei mai verbose?
- Ați dat de limitări sau bug-uri ciudate în Valibot pe scheme complexe (de exemplu, discriminated unions sau transformări asincrone)?
- Merită efortul de migrare pentru acele zeci de milisecunde economisite la cold start, sau mai bine optimizăm în altă parte?