# writing-zod-code-that-minifies
Source: https://speakeasy.com/blog/writing-zod-code-that-minifies


I don't think it's exaggerating when I say that [Zod][zod] has had a major
positive impact on how we write safe TypeScript code that also preserves its
safety guarantees at runtime. It's so powerful that we at Speakeasy based the
design of our [latest TypeScript SDK generator](/post/introducing-universal-ts) on it.

We've been iterating on the TypeScript code we're generating because we're going
through a phase of optimising the size of our SDKs so they work even better in
the browser and one interesting area has been around how we write Zod schemas.

Consider the following (contrived) example of a Zod schema:

```ts
import * as z from "zod";

export const person = z.object({
  name: z.string(),
  age: z.number().optional(),
  address: z.object({
    line1: z.string().optional(),
    line2: z.string().optional(),
    line3: z.string().optional(),
    line4: z.string().optional(),
    city: z.string(),
  }),
  pets: z.array(
    z.union([
      z.object({
        type: z.literal("dog"),
        name: z.string(),
      }),
      z.object({
        type: z.literal("cat"),
        name: z.string(),
      }),
    ])
  ),
});
```

This is a fairly common style of writing zod schemas: you import the whole
library as a single variable and use the powerful chaining API to describe
validation rules.

When running this code through a bundler like esbuild we're going to get subpar
minification performance. Esbuild will remove unnecessary spaces and newlines
but I'm going to keep them in for the sake of readability. Here's the formatted
result:

```ts
import * as t from "zod";

export const person = t.object({
  name: t.string(),
  age: t.number().optional(),
  address: t.object({
    line1: t.string().optional(),
    line2: t.string().optional(),
    line3: t.string().optional(),
    line4: t.string().optional(),
    city: t.string(),
  }),
  pets: t.array(
    t.union([
      t.object({ type: t.literal("dog"), name: t.string() }),
      t.object({ type: t.literal("cat"), name: t.string() }),
    ])
  ),
});
```

The actual minified code comes out to 379 bytes, down from 512 bytes. That's
rougly a 26% reduction.

Notice how not a lot has changed. In fact, the only thing that has changed is
that esbuild renamed `z` to `t`. The only net reduction in this minified code
can be attributed to the removal of unnecessary whitespace characters which
we've kept in here.

So on the current course, if your project is building up more and more Zod
schemas, you'll notice that the minified code isn't tremendously smaller than
the unminified code. The only real gains will be from compressing this code with
gzip or brotli (or whatever you prefer) but that doesn't impact the size of the
code that needs to be parsed. Furthermore, minified can still compress well and
result in overall less text to send down the wire and to parse.

The way to improve the impact of minification on our code will come from using
local variables that can be rewritten by the minifier. Fortunately, Zod exports
much of its API as a standalone functions that can be separately imported.

Lets rewrite the code above using these standalone functions:

```ts
import { object, string, number, array, union, literal, optional } from "zod";

export const person = object({
  name: string(),
  age: optional(number()),
  address: object({
    line1: optional(string()),
    line2: optional(string()),
    line3: optional(string()),
    line4: optional(string()),
    city: string(),
  }),
  pets: array(
    union([
      object({
        type: literal("dog"),
        name: string(),
      }),
      object({
        type: literal("cat"),
        name: string(),
      }),
    ])
  ),
});
```

... and the minified result:

```ts
import {
  object as t,
  string as e,
  number as i,
  array as o,
  union as r,
  literal as a,
  optional as n,
} from "zod";
export const person = t({
  name: e(),
  age: n(i()),
  address: t({
    line1: n(e()),
    line2: n(e()),
    line3: n(e()),
    line4: n(e()),
    city: e(),
  }),
  pets: o(
    r([t({ type: a("dog"), name: e() }), t({ type: a("cat"), name: e() })])
  ),
});
```

That did better. The actual result is now 290 bytes, down from 512 bytes. That's
roughly a 43% reduction!

You can see how the minifier was able to re-alias all the imports to single
letter variables and use that to shrink the remaining code.

The new style of writing Zod schemas might be a little more tedious because you
are no longer carrying the entire library with you with a single variable.
However, if optimising for bundle size is a real concern for you, then this is a
neat trick to keep in your backpocket.

[tsv2]: (/post/introducing-universal-ts)
[zod]: https://zod.dev/