Learn how to ship impactful customer journeys with Builder

Announcing Visual Copilot - Figma to production in half the time

Builder.io logo
Contact Sales
Platform
Developers
Contact Sales

Blog

Home

Resources

Blog

Forum

Github

Login

Signup

×

Visual CMS

Drag-and-drop visual editor and headless CMS for any tech stack

Theme Studio for Shopify

Build and optimize your Shopify-hosted storefront, no coding required

Resources

Blog

Get StartedLogin

‹ Back to blog

Web Development

This technique makes Valibot’s bundle size 10x smaller than Zod’s!

October 5, 2023

Written By Fabian Hiller

Jared Palmer was right when he complained about the API design of pretty much every JavaScript SDK on August 12th:

With this post, I would like to publish my bachelor’s thesis and share with you some of the main results of my research. I would also like to talk about how Valibot has developed in the last two months and what our next goals are.

Short recap about our last announcement

Two months ago I announced the modular schema library Valibot together with my supervisors Miško Hevery and Ryan Carniato. Valibot was created as part of my bachelor’s thesis at Stuttgart Media University, where I investigated how the bundle size of JavaScript libraries can be reduced.

On Twitter, our announcement reached more than 500,000 impressions, and on GitHub, Valibot was trending in the following days. Within a few weeks, a community of more than 40 contributors and an ecosystem of integrations with projects like tRPCReact Hook FormVeeValidateHono, and Drizzle ORM, just to name a few, had formed.

Since then, Valibot has been downloaded more than 70,000 times on npm and has received more than 3,600 stars on GitHub. I would like to thank everyone who has supported the project in various ways and contributed to this attention.

How can Valibot be less than < 1 kB?

Valibot was announced as a < 1 kB Zod alternative. However, a look at Bundlephobia shows that the actual bundle size is currently 6.6 kB. So where does the < 1 kB specification come from?

The explanation is quite simple. Valibot exports almost every functionality individually. This allows a bundler to remove the code that is not needed in the final build step. In technical language, this process is called tree shaking.

Therefore, Valibot’s simplest schemas start at less than 300 bytes. Considering that a JavaScript function that validates a primitive data type takes about 100 bytes, this is a pretty good achievement.

// 106 bytes minified and gzipped
export function string(input: unknown): string {
  if (typeof input !== "string") {
    throw new Error("Invalid type");
  }
  return input;
}

You may be wondering why Zod is not tree shakable. There are several reasons. However, the most important one is the implementation.

Zod is implemented object oriented. The abstract parent class ZodType contains the functionality that all schemas have in common. This includes many methods like parsesafeParserefine, and default.

export abstract class ZodType<
  Output = any,
  Def extends ZodTypeDef = ZodTypeDef,
  Input = Output
> {
  parse(data: unknown, params?: Partial<ParseParams>): Output {
    const result = this.safeParse(data, params);
    if (result.success) return result.data;
    throw result.error;
  }
  // ...   
}

The classes, for example, ZodString, which validate a specific data type, inherit from ZodType and add more specific methods like emailurl, and regex.

export class ZodString extends ZodType<string, ZodStringDef> {
  email(message?: errorUtil.ErrMessage) {
    return this._addCheck({ kind: "email", ...errorUtil.errToObj(message) });
  }
  // ...
}

Since tree shaking is based on static import and export statements, all unused class methods cannot be removed by this procedure. The result is a bundle size that grows with each feature, whether it is used or not.

Since the difference in bundle size is so extreme, you may wonder why every library is not implemented modularly like Valibot. I think it’s because it takes more effort to design a library with a modular architecture.

With an object-oriented approach, for example, you can expand the library piece by piece in a natural way. With a modular design, you have to consider all the interrelationships of the various functions from the start so that they work together seamlessly.

Now I would like to give you a few do’s and don’ts that I have taken into account with Valibot and that you can also follow with your libraries and SDKs.

Optimizing the library for tree shaking and code splitting requires a modular architecture since every functionality must be exported separately. This excludes the object-oriented approach of Zod. Also, factory functions are also not suitable since they usually contain more functionality than is needed for most use cases.

// Bad: Class with methods
export class StringType {
  schema = 'string';
  // Can't tree shake `email` and `regex`
  email() {
    // ...
  }
  regex() {
    // ...
  }
}

// Bad: Factory function
export function createStringType() {
  return {
    schema: 'string',
    // Can't tree shake `email` and `regex`
    email() {
      // ...
    },
    regex() {
      // ...
    },
  };
}

In order to achieve the smallest possible initial bundle size, it should be ensured that the individual functions are reduced to the bare minimum and only contain the code required for each execution. Program code that is necessary only in individual cases should be able to be supplemented, for example, with dependency injection.

// Good: String schema function
export function string(pipe: PipeFn<string>[]) {
  return {
    schema: 'string',
    // ...
  }
}

// Good: Email validation function
export function email(): PipeFn<string> {
  return (value: string) => {
    // ...
  }
}

// Good: Regex validation function
export function regex(arg: RegExp): PipeFn<string> {
  return (value: string) => {
    // ...
  }
}

If only the regex validation is needed, this can now be passed as an argument to the string function. Since email is not imported and not used anywhere within the library, the function can be removed from the final bundle by tree shaking.

import { regex, string } from './validation';

const schema = string([regex(/^ID-\d{4}$/)]);

If you are interested in more details that are crucial for a small bundle size, I recommend having a look at my bachelor’s thesis.

Although the initial implementation of a modular architecture is usually more challenging, once the basic structure is established, development becomes much easier. This is because each component represents a single functionality and usually has no or very few dependencies.

As a result, the complexity of a single function is much lower with a modular design. This has advantages not only for testing, but also for collaboration with other open-source contributors. New developers usually only need to read a few lines of code for a feature or bug fix to create a PR, instead of studying the entire source code.

In addition, modularity makes it possible to extend the functionality of a library with new functions without increasing the bundle size for all users. Moreover, it also provides the flexibility to replace individual building blocks with custom code to customize its functionality.

However, perhaps the biggest advantage for websites and serverless systems is the faster startup time due to the smaller bundle size and, in the case of Valibot, the much faster initialization. This is reflected in the TTI benchmark and has a positive impact on SEO and the user experience.

Valibot has shown that a modular API design and implementation can significantly reduce the bundle size of a JavaScript library. Also, the numbers indicate that the project is addressing an existing problem and that there is a need for innovation in this space.

The research results, as well as the proven interest in Valibot, can provide a foundation to disrupt JavaScript libraries in various areas through a modular architecture. It can create awareness of the impact of large bundle sizes and encourage rethinking.

Since modularity requires a completely different approach for API design and implementation, my thesis also offers, beyond the theory, a practical starting point that can be used for inspiration with the source code of Valibot.

A lot has happened since our first announcement two months ago. There have been more than 20 releases with extreme performance improvements and many new features. The most important guides in our docs have also been completed.

Until we reach v1, our goal is to achieve feature parity with Zod as much as it makes sense. Also, the API reference of the documentation should be filled with content and the unit tests should be extended and improved.

Furthermore, we are looking for organizations that would like to support the project and further research, both financially and ideally. The Seidenberg School at Pace University, where I’m currently doing a master’s in Computer Science, has already offered to collaborate. I could also imagine partnering with, for example, Google’s Chrome Web Framework & Tools Performance Fund.

Maybe this is the start of a bigger movement. Let’s make the web better together!

Fabian Hiller

@FabianHiller

Entrepreneur, Software Engineer, Content Creator and Open Source Community Member of Solid JS, Qwik, and programmier.bar

Find Fabian online

Introducing Valibot, a < 1kb Zod Alternative

Valibot is a schema library for validating structural data, comparable to Zod, Ajv, Joi and Yup. Valibot is the modular design of the API.

Introducing Visual Copilot: convert Figma designs to high quality code in a single click.

Try Visual Copilot

Share

Twitter
LinkedIn
Facebook
Hand written text that says "A drag and drop headless CMS?"

Introducing Visual Copilot:

A new AI model to turn Figma designs to high quality code using your components.

Try Visual Copilot
Newsletter

Like our content?

Join Our Newsletter

Continue Reading
AI8 MIN
How to Build Reliable AI Tools
November 15, 2024
Web Design11 MIN
Design Smarter with Figma Auto Layout
November 13, 2024
Web Development10 MIN
A Guide to Server-Side Rendering
November 12, 2024