💡 superstruct: библиотека для трансформации и валидации данныхКак-то не так давно на трансляции я рассказывал о том, что для клиентской части Платформера заинтересовался библиотеками для трансформации и валидации данных. В частности, мне нужно было проверять, что пришедший объект — это JSON объект в виде строки с определенным списком полей конкретного типа.
Для этих целей я всегда использовал либо
yup, либо
zod. В
State of Frontend 2024 мы узнали, что наибольшей популярностью сейчас пользуются
zod и
joi, но я решил проверить — а есть ли что-то лучше?
Зачастую, мы используем библиотеки лишь потому, что они просто решают какие-то из наших проблем, но чаще всего не задумываемся на тему того, насколько вообще эффективно их использовать. Тем не менее, чем меньше разрабатываемый вами проект, тем больше вы должны об этом думать.
Ввиду того, что для Платформера я писал лаунчер мини-приложений, который чем меньше весит, тем лучше, для меня было важным, чтобы внешние библиотеки точно так же ничего не весили. Думаю, что скорость загрузки здесь критична — объяснять необходимости нет. Таймлайн загрузки мы и без того увеличиваем за счет дополнительных запросов и вставку iframe, так еще и если скрипты долго будут грузиться, будет совсем плохо.
Спросив, что ChatGPT думает на тему минималистичных библиотек для валидации данных, я получил следующие библиотеки:
zod,
yup,
ajv и
superstruct. Первые две я знаю, а про оставшиеся ни разу не слышал. Посмотрев на
ajv я понял, что от первых двух библиотек, эта, в контексте решения моих проблем, не особо отличается.
Но вот что зацепило моё внимание, так это
superstruct. А знаете чем зацепило? Знакомым мне функциональным, оптимальным подходом. В первую очередь я обратил внимание на то, как происходят импорты из библиотеки. Вы не импортируете всё и вся как в zod — через импорт переменной
z (да, там можно делать отдельные импорты, но на конечный результат это, к сожалению, не влияет), а импортируете только тот функционал, который вам нужен.
Автор библиотеки додумался разнести утилиты и валидаторы по разным функциям. Таким образом, используя функции для создания типов (object, string, number, etc.), вы не импортируете функции для валидации, создания экземпляров типов, и всего остального. Всё импортируется лишь по запросу, достаточно гранулярно. Этого же подхода я придерживался при написании SDK, потому что только тогда tree-shaking проявляет себя на все 100%.
Да, библиотека по функциональности уступает всем ранее озвученным, но этот функционал при помощи самой библиотеки можно и нагнать при необходимости.
Если исходить из того, что предоставляет bundlephobia,
zod весит 14.8kb,
yup 12.9kb, а
superstruct всего лишь 3.4kb. Тут стоит упомянуть, что bundlephobia считает размер библиотек так, будто вы используете оттуда всё, что можете. То есть если библиотека поддерживает нормальный tree-shaking (что superstruct и делает), то размер может оказаться сильно меньше.
Вот как выглядит простейший пример использования библиотеки (взял прямо из документации):
import { is, object, number, string } from 'superstruct'
const User = object({
id: number(),
name: string()
})
const data: unknown = { ... }
if (is(data, User)) {
// TypeScript knows the shape of `data` here, so it is safe to access
// properties like `data.id` and `data.name`.
}
А вот как выглядит приведение типов:
import { coerce, number, string, create } from 'superstruct';
const Float = coerce(number(), string(), (value) => parseFloat(value));
const output = create(data, '3.14');
// 3.14
Да, выглядит это не так привычно, как хотелось бы, но опять же — это просто привычка. Функциональный подход чаще всего менее интуитивно понятен разработчикам, но это лишь дело времени.