Skip to content

Commit 5c522e8

Browse files
committed
Switch to zod mini to reduce app size by 47 kB
```diff dist/index.html 1.26 kB │ gzip: 0.61 kB dist/assets/index-DqFAHrPz.css 28.97 kB │ gzip: 6.23 kB - dist/assets/index-7NasAjCA.js 469 kB │ gzip: 153.07 kB + dist/assets/index-AU2GL5_c.js 422 kB │ gzip: 141.05 kB ```
1 parent 6ae93e0 commit 5c522e8

File tree

7 files changed

+83
-83
lines changed

7 files changed

+83
-83
lines changed

src/dtcg.schema.ts

Lines changed: 47 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,41 @@
11
// Zod schema based on Design Tokens Community Group specification
22
// does not support JSON Pointer references
33

4-
import { z } from "zod";
4+
import * as z from "zod/mini";
55

66
// token and group names MUST NOT:
77
// - start with '$' (reserved prefix per DTCG spec)
88
// - contain '{' (used in references syntax)
99
// - contain '}' (used in references syntax)
1010
// - contain '.' (used in path separators within references)
11-
export const nameSchema = z
12-
.string()
13-
.refine(
11+
export const nameSchema = z.string().check(
12+
z.refine(
1413
(name) => !name.startsWith("$"),
1514
"Token and group names must not start with '$'",
16-
)
17-
.refine(
15+
),
16+
z.refine(
1817
(name) => !name.includes("{"),
1918
"Token and group names must not contain '{'",
20-
)
21-
.refine(
19+
),
20+
z.refine(
2221
(name) => !name.includes("}"),
2322
"Token and group names must not contain '}'",
24-
)
25-
.refine(
23+
),
24+
z.refine(
2625
(name) => !name.includes("."),
2726
"Token and group names must not contain '.'",
28-
);
27+
),
28+
);
2929

3030
// references use dot-separated paths with curly braces like {colors.primary}
3131
// each segment must be a valid name per nameSchema
3232
// special case: $root token is allowed within references as it uses the $ prefix
33-
export const referenceSchema = z
34-
.string()
35-
.refine(
33+
export const referenceSchema = z.string().check(
34+
z.refine(
3635
(value) => value.startsWith("{") && value.endsWith("}"),
3736
"Reference must be enclosed in curly braces",
38-
)
39-
.refine((value) => {
37+
),
38+
z.refine((value) => {
4039
const content = value.slice(1, -1);
4140
const segments = content.split(".");
4241
return (
@@ -47,7 +46,8 @@ export const referenceSchema = z
4746
return nameSchema.safeParse(segment).success;
4847
})
4948
);
50-
}, "Each segment in reference must be a valid name");
49+
}, "Each segment in reference must be a valid name"),
50+
);
5151

5252
// Token types
5353
const tokenType = z.enum([
@@ -90,8 +90,8 @@ const colorSpace = z.enum([
9090
export const colorValue = z.object({
9191
colorSpace: colorSpace,
9292
components: z.array(colorComponent),
93-
alpha: z.number().optional(),
94-
hex: z.string().optional(),
93+
alpha: z.optional(z.number()),
94+
hex: z.optional(z.string()),
9595
});
9696

9797
export const dimensionValue = z.object({
@@ -101,11 +101,11 @@ export const dimensionValue = z.object({
101101

102102
export const fontFamilyValue = z.union([
103103
z.string(),
104-
z.array(z.string()).min(1),
104+
z.array(z.string()).check(z.minLength(1)),
105105
]);
106106

107107
export const fontWeightValue = z.union([
108-
z.number().min(1).max(1000),
108+
z.number().check(z.minimum(1), z.maximum(1000)),
109109
z.enum([
110110
"thin",
111111
"hairline",
@@ -156,7 +156,7 @@ export const strokeStyleString = z.enum([
156156
export const strokeStyleValue = z.union([
157157
strokeStyleString,
158158
z.object({
159-
dashArray: z.array(dimensionValue).min(1),
159+
dashArray: z.array(dimensionValue).check(z.minLength(1)),
160160
lineCap: z.enum(["round", "butt", "square"]),
161161
}),
162162
]);
@@ -181,20 +181,20 @@ const shadowObject = z.object({
181181
offsetY: z.union([dimensionValue, referenceSchema]),
182182
blur: z.union([dimensionValue, referenceSchema]),
183183
spread: z.union([dimensionValue, referenceSchema]),
184-
inset: z.boolean().optional(),
184+
inset: z.optional(z.boolean()),
185185
});
186186

187187
export const shadowValue = z.union([
188188
shadowObject,
189-
z.array(shadowObject).min(1),
189+
z.array(shadowObject).check(z.minLength(1)),
190190
]);
191191

192192
const gradientStop = z.object({
193193
color: z.union([colorValue, referenceSchema]),
194194
position: z.number(),
195195
});
196196

197-
export const gradientValue = z.array(gradientStop).min(1);
197+
export const gradientValue = z.array(gradientStop).check(z.minLength(1));
198198

199199
export const typographyValue = z.object({
200200
fontFamily: z.union([fontFamilyValue, referenceSchema]),
@@ -221,19 +221,19 @@ export const tokenSchema = z.object({
221221
typographyValue,
222222
referenceSchema,
223223
]),
224-
$type: tokenType.optional(),
225-
$description: z.string().optional(),
226-
$extensions: z.record(z.string(), z.unknown()).optional(),
227-
$deprecated: z.union([z.boolean(), z.string()]).optional(),
224+
$type: z.optional(tokenType),
225+
$description: z.optional(z.string()),
226+
$extensions: z.optional(z.record(z.string(), z.unknown())),
227+
$deprecated: z.optional(z.union([z.boolean(), z.string()])),
228228
});
229229

230230
export const groupSchema = z.object({
231-
$type: tokenType.optional(),
232-
$description: z.string().optional(),
233-
$extensions: z.record(z.string(), z.unknown()).optional(),
234-
$extends: referenceSchema.optional(),
235-
$deprecated: z.union([z.boolean(), z.string()]).optional(),
236-
$root: tokenSchema.optional(),
231+
$type: z.optional(tokenType),
232+
$description: z.optional(z.string()),
233+
$extensions: z.optional(z.record(z.string(), z.unknown())),
234+
$extends: z.optional(referenceSchema),
235+
$deprecated: z.optional(z.union([z.boolean(), z.string()])),
236+
$root: z.optional(tokenSchema),
237237
});
238238

239239
export type Token = z.infer<typeof tokenSchema>;
@@ -262,7 +262,7 @@ export const resolverSourceSchema = z.record(
262262
z.string(),
263263
// avoid checking to not cut of nested groups and tokens
264264
// but enforce as a type
265-
z.unknown() as z.ZodType<Token | Group>,
265+
z.unknown() as z.ZodMiniType<Token | Group>,
266266
);
267267

268268
export type ResolverSource = z.infer<typeof resolverSourceSchema>;
@@ -272,8 +272,8 @@ export const resolverSetSchema = z.object({
272272
type: z.literal("set"),
273273
name: nameSchema, // required, unique identifier within resolutionOrder
274274
sources: z.array(resolverSourceSchema), // non-optional, can be empty
275-
description: z.string().optional(),
276-
$extensions: z.record(z.string(), z.unknown()).optional(),
275+
description: z.optional(z.string()),
276+
$extensions: z.optional(z.record(z.string(), z.unknown())),
277277
});
278278

279279
export type ResolverSet = z.infer<typeof resolverSetSchema>;
@@ -289,9 +289,9 @@ export const resolverModifierSchema = z.object({
289289
type: z.literal("modifier"),
290290
name: nameSchema, // required, unique identifier within resolutionOrder
291291
contexts: resolverModifierContextsSchema, // non-optional
292-
description: z.string().optional(),
293-
default: z.string().optional(),
294-
$extensions: z.record(z.string(), z.unknown()).optional(),
292+
description: z.optional(z.string()),
293+
default: z.optional(z.string()),
294+
$extensions: z.optional(z.record(z.string(), z.unknown())),
295295
});
296296

297297
export type ResolverModifier = z.infer<typeof resolverModifierSchema>;
@@ -306,16 +306,16 @@ export type ResolutionOrderItem = z.infer<typeof resolutionOrderItemSchema>;
306306

307307
// Unsupported root-level sets and modifiers
308308
// These reject any object with properties - only allow undefined or empty object
309-
const unsupportedSetsSchema = z.object({}).strict().optional();
310-
const unsupportedModifiersSchema = z.object({}).strict().optional();
309+
const unsupportedSetsSchema = z.optional(z.strictObject({}));
310+
const unsupportedModifiersSchema = z.optional(z.strictObject({}));
311311

312312
// Resolver document following Design Tokens Resolver Module 2025.10
313313
export const resolverDocumentSchema = z.object({
314314
version: z.literal("2025.10"),
315-
name: z.string().optional(),
316-
description: z.string().optional(),
317-
sets: unsupportedSetsSchema.optional(),
318-
modifiers: unsupportedModifiersSchema.optional(),
315+
name: z.optional(z.string()),
316+
description: z.optional(z.string()),
317+
sets: z.optional(unsupportedSetsSchema),
318+
modifiers: z.optional(unsupportedModifiersSchema),
319319
resolutionOrder: z.array(resolutionOrderItemSchema),
320320
});
321321

src/legacy.schema.ts

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Supports dual-format parsing: legacy 2022 format and 2025 standard format
33
// Output is always normalized to 2025 standard format
44

5-
import { z } from "zod";
5+
import * as z from "zod/mini";
66
import { parseColor } from "./color";
77
import {
88
cubicBezierValue,
@@ -32,10 +32,9 @@ const expandShorthandHex = (hex: string): string => {
3232
// #rrggbb or #rrggbbaa or #rgb or #rgba
3333
const legacyColorRegex =
3434
/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/i;
35-
const legacyColorValue = z
36-
.string()
37-
.regex(legacyColorRegex)
38-
.transform((value): ColorValue => {
35+
const legacyColorValue = z.pipe(
36+
z.string().check(z.regex(legacyColorRegex)),
37+
z.transform((value): ColorValue => {
3938
const expanded = expandShorthandHex(value.toLowerCase());
4039
// Extract alpha from last 2 chars if present
4140
let alphaHex = "ff";
@@ -55,14 +54,14 @@ const legacyColorValue = z
5554
delete colorValue.hex;
5655
}
5756
return colorValue;
58-
});
57+
}),
58+
);
5959

6060
// "10px", "0.5rem", "-8px", "-0.5rem"
6161
const dimensionRegex = /^(-?\d+(?:\.\d+)?)(px|rem)$/;
62-
const legacyDimensionValue = z
63-
.string()
64-
.regex(dimensionRegex)
65-
.transform((value): DimensionValue => {
62+
const legacyDimensionValue = z.pipe(
63+
z.string().check(z.regex(dimensionRegex)),
64+
z.transform((value): DimensionValue => {
6665
const match = value.match(dimensionRegex);
6766
if (!match) {
6867
throw new Error(`Invalid dimension: ${value}`);
@@ -71,14 +70,14 @@ const legacyDimensionValue = z
7170
value: Number.parseFloat(match[1]),
7271
unit: match[2] as "px" | "rem",
7372
};
74-
});
73+
}),
74+
);
7575

7676
// "200ms", "1.5s", "-100ms", "-0.5s"
7777
const durationRegex = /^(-?\d+(?:\.\d+)?)(ms|s)$/;
78-
const legacyDurationValue = z
79-
.string()
80-
.regex(durationRegex)
81-
.transform((value): DurationValue => {
78+
const legacyDurationValue = z.pipe(
79+
z.string().check(z.regex(durationRegex)),
80+
z.transform((value): DurationValue => {
8281
const match = value.match(durationRegex);
8382
if (!match) {
8483
throw new Error(`Invalid duration: ${value}`);
@@ -87,31 +86,32 @@ const legacyDurationValue = z
8786
value: Number.parseFloat(match[1]),
8887
unit: match[2] as "ms" | "s",
8988
};
90-
});
89+
}),
90+
);
9191

9292
// "200", "1.5", "-42", "-3.14"
9393
const numberRegex = /^(-?\d+(?:\.\d+)?)$/;
94-
const legacyNumberValue = z
95-
.string()
96-
.regex(numberRegex)
97-
.transform((value): number => {
94+
const legacyNumberValue = z.pipe(
95+
z.string().check(z.regex(numberRegex)),
96+
z.transform((value): number => {
9897
const match = value.match(numberRegex);
9998
if (!match) {
10099
throw new Error(`Invalid number: ${value}`);
101100
}
102101
return Number.parseFloat(match[1]);
103-
});
102+
}),
103+
);
104104

105105
const legacyStrokeStyleValue = z.union([
106106
strokeStyleString,
107107
z.object({
108-
dashArray: z.array(legacyDimensionValue).min(1),
108+
dashArray: z.array(legacyDimensionValue).check(z.minLength(1)),
109109
lineCap: z.enum(["round", "butt", "square"]),
110110
}),
111111
]);
112112

113113
const legacyShadowObject = z.object({
114-
inset: z.boolean().optional(),
114+
inset: z.optional(z.boolean()),
115115
color: z.union([legacyColorValue, referenceSchema]),
116116
offsetX: z.union([legacyDimensionValue, referenceSchema]),
117117
offsetY: z.union([legacyDimensionValue, referenceSchema]),
@@ -140,7 +140,7 @@ const legacyGradientStop = z.object({
140140
color: z.union([legacyColorValue, referenceSchema]),
141141
position: z.number(),
142142
});
143-
const legacyGradientValue = z.array(legacyGradientStop).min(1);
143+
const legacyGradientValue = z.array(legacyGradientStop).check(z.minLength(1));
144144

145145
const legacyTypographyValue = z.object({
146146
fontFamily: z.union([fontFamilyValue, referenceSchema]),
@@ -150,7 +150,7 @@ const legacyTypographyValue = z.object({
150150
lineHeight: z.union([legacyNumberValue, referenceSchema]),
151151
});
152152

153-
export const legacyTokenSchema = tokenSchema.extend({
153+
export const legacyTokenSchema = z.extend(tokenSchema, {
154154
$value: z.union([
155155
legacyColorValue,
156156
legacyDimensionValue,

src/resolver.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ describe("parseTokenResolver", () => {
7070
resolutionOrder: [],
7171
});
7272
expect(result.errors).toHaveLength(1);
73-
expect(result.errors[0].message).toContain("2025.10");
73+
expect(result.errors[0].message).toContain("version");
7474
});
7575

7676
test("rejects root-level sets object with property keys", () => {

src/resolver.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { prettifyError } from "zod";
1+
import * as z from "zod/mini";
22
import { generateKeyBetween } from "fractional-indexing";
33
import {
44
resolverDocumentSchema,
@@ -88,7 +88,7 @@ export const parseTokenResolver = (input: unknown): ParseResult => {
8888
const validation = resolverDocumentSchema.safeParse(input);
8989

9090
if (!validation.success) {
91-
const errorMessage = prettifyError(validation.error);
91+
const errorMessage = z.prettifyError(validation.error);
9292
return {
9393
nodes: [],
9494
errors: [{ path: "resolver", message: errorMessage }],

src/schema.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { z } from "zod";
1+
import * as z from "zod/mini";
22
import {
33
colorValue,
44
cubicBezierValue,
@@ -94,7 +94,7 @@ const shadowItemSchema = z.object({
9494
offsetY: dimensionValue,
9595
blur: dimensionValue,
9696
spread: dimensionValue,
97-
inset: z.boolean().optional(),
97+
inset: z.optional(z.boolean()),
9898
});
9999

100100
const shadowSchema = z.object({
@@ -108,7 +108,7 @@ const rawShadowItemSchema = z.object({
108108
offsetY: z.union([dimensionValue, nodeRefSchema]),
109109
blur: z.union([dimensionValue, nodeRefSchema]),
110110
spread: z.union([dimensionValue, nodeRefSchema]),
111-
inset: z.boolean().optional(),
111+
inset: z.optional(z.boolean()),
112112
});
113113

114114
const rawShadowSchema = z.object({

src/state.svelte.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { formatError } from "zod";
1+
import * as z from "zod/mini";
22
import { createSubscriber } from "svelte/reactivity";
33
import { TreeStore, type Transaction, type TreeNode } from "./store";
44
import {
@@ -123,7 +123,7 @@ export const resolveRawValue = (
123123
const resolvedValue = resolveRawValue(tokenNode, nodes, newStack);
124124
const parsed = RawValueSchema.safeParse(resolvedValue);
125125
if (!parsed.success) {
126-
throw Error(formatError(parsed.error)._errors.join("\n"));
126+
throw Error(z.formatError(parsed.error)._errors.join("\n"));
127127
}
128128
return resolvedValue;
129129
}

0 commit comments

Comments
 (0)