DEV Community

Koutaro Chikuba
Koutaro Chikuba

Posted on

lizod - spiritual successor of zod less than 1kb

(Translated with www.DeepL.com/Translator. Original Japanese version is https://zenn.dev/mizchi/articles/lizod-is-lightweight-zod)

lightweight-zod, hence lizod.

You can use npm install lizod -S.

tl;dr

  • The build size of zod is getting in the way of various frontends and Cloudflare Workers.
  • I've created a zod-like validator that discards the method chain and all the useful utilities.
  • lizod is less than 1kb compared to zod's 57kb

It works.

// Pick validators for treeshake
import {
  $any,
  $array,
  $boolean,
  $const,
  $enum,
  $intersection,
  $null,
  $number,
  $object,
  $opt,
  $regexp,
  $string,
  $symbol,
  $undefined,
  $union,
  $void,
  type Infer,
  type Validator,
} from "lizod";

const validate = $object({
  name: $string,
  age: $number,
  familyName: $opt($string),
  abc: $enum(["a" as const, "b" as const, "c" as const]),
  nested: $object({
    age: $number,
  }),
  static: $const("static"),
  items: $array($object({
    a: $string,
    b: $boolean,
  })),
  complex: $array($union([
    $object({ a: $string }),
    $object({ b: $number }),
  ])),
  sec: $intersection([$string, $const("x")]),
});

const v: Infer<typeof validate> = {
  name: "aaa",
  age: 1,
  familyName: null,
  abc: "b",
  nested: {
    age: 1,
  },
  static: "static",
  items: [
    {
      a: "",
      b: true,
    },
    {
      a: "",
      b: false,
    },
  ],
  complex: [
    { a: "" },
    { b: 1 },
  ],
  sec: "x",
};

if (validate(v)) {
  const _: string = v.name;
  const __: number = v.age;
  const ___: string | void = v.familyName;
  const ____: "a" | "b" | "c" = v.abc;
  const _____: { age: number } = v.nested;
  const ______: "static" = v.static;
  const _______: Array<{
    a: string;
    b: boolean;
  }> = v.items;
}

Enter fullscreen mode Exit fullscreen mode

Why I made it

Recently, there is a high demand to run zod together with remix and others under the 1MB limit of Cloudflare Workers (5MB for paid plans). 220kb of remix is unavoidable since it is a framework, but zod has a size of 57kb. 57kb for a single function validator is a little bit too much.

https://bundlephobia.com/package/zod@3.21.4

bundlephobia says 57kb, but I checked it with my own build and it was about 43kb. However, it is still big.

I need a lightweight zod alternative for Cloudflare Workers, not to mention the build size of the frontend.

So, I made what I think is the best zod alternative.

Why is zod size so big?

In general, libraries that adopt the API style of method chaining do not benefit from the ESM + various bundlers to remove unnecessary code. Imagine jQuery, for example.

Validators such as zod do not need to be method chains and could be represented by function composition, but the Pipeline Operator that allows this to be written naturally is still being hotly debated at Stage 2 and is not expected to be available anytime soon.

https://github.com/tc39/proposal-pipeline-operator

As far as I read the Zod code, there is a lack of optimization for tree shaking by method chaining, and the Locale definition for displaying Error Reporter and Error Message is quite large.

I thought about Forking Zod itself, but I thought I could achieve my goal by implementing the parts I really want in my use case in the way I want.

Features I want in zod

  • A set of validators at the same level as TypeScript type expressions
  • An Infer<T> to the synthesized validators

So, I first created it with this kind of API set in mind

import { $object, $array, $union, $number, $string, type Infer } from "lizod";.

const validate = $object({
  v: $array($union($string, $number))
});

type MyType = Infer<typeof validate>

const input: any = {
  v: [1, "hello", "world", 9]
}
if (validate(input)) {
  // type narrowing in this scope
  const v: Array<number | string> = input.v
}
Enter fullscreen mode Exit fullscreen mode

zod does not mesh with TypeScript type expressions in some places due to the merging of existing validators and instruction sets. lizod can be used with the same type sense as TypeScript's A | B and A & B since the instruction levels are $union and $intersect.

Things you don't need

  • Utilities like email(). It implements $regexp(expr) instead, so the regular expression itself is brought separately.
    • One of the reasons why zod is so heavy is because it wraps a lot of this kind of regexp inside...
  • Method Chain
  • Clever Error Reporter

In the spirit of "create your own missing validators", only primitive validators are implemented in the main body.

Finally

I think there are many people who think that zod is only possible with a collection of utilities, or that not having an error reporter is a shame, etc. For those people, lizod is a great tool. For those people, lizod's small amount of code may be useful as a foundation to fork and tweak.

At least, I expect this kind of functionality from zod, and when I implemented it, it became lizod. I have been able to create it because I have been getting more and more good at type-puzzling recently.

I am waiting for your comments, feature suggestions, and PR.

https://github.com/mizchi/lizod/issues

Top comments (0)