Привет, Хабр!
Я принёс Specify — open‑source генератор веб‑приложений из спецификации. Это как Framer, только без визуального редактора и без чёрной магии AI под капотом.
Из коробки Specify умеет генерировать PWA (SPA) на Svelte, с бронебойной автономностью, приложение откроется в лифте, на парковке, при белых списках и даже если физически не станет сервера. Есть интеграция с open‑source бэкенд‑платформой Appwrite, о которой я расскажу ниже.
Зачем я это сделал
Вообще, я не разработчик, мой основной опыт в продуктовом дизайне, а программирую я для души).
Примерно 3 года назад у меня возникла мотивация закрыть весь цикл разработки продукта самостоятельно. Если 15–20 лет назад я мог сам создать сайт, сверстать дизайн и натянуть HTML c CSS на CMS, то сегодня этого опыта недостаточно. Чтобы сделать современное веб‑приложение, нужно погрузиться во фронтенд‑фреймворки, сборщики проектов, современный бэк с веб‑сокетами и так далее
Я быстро понял, что ручная верстка и перенос изменений из дизайна в код сильно меня тормозят. И я начал делать, по сути, свой аналог Framer. За 3 года я прошел путь от автоматизации переноса дизайна в код (парсинга JSON‑данных дизайна и полной сборки приложения) — до понимания, что нужно делать свой редактор.
Я хотел сделать свой полноценный визуальный редактор, но пока что на это нет ресурса. Зато я понял, что можно сделать MVP без собственного редактора, используя максимум возможностей IDE для улучшения DX.
Так появился Specify, объединяющий абстракцию дизайна и фронтенда в одной спецификации.
Что такое Specify
Specify — это не фреймворк и не билдер, хотя похоже на одно и другое. Это декларативная спецификация и компилятор (транслятор) спецификации в код приложения.
Я делал его для решения своих задач, но если это поможет другим дизайнерам и командам перестать играть в сломанный телефон между дизайном и разработкой — буду рад.
Дизайнер, если ты ещё здесь, Specify — это не больно:
Спецификация на языке дизайна — здесь почти вся терминология из Figma.
IDE помогает писать: автоподстановка свойств и значений.
Любое изменение мгновенно отображается в браузере (HMR).
Не хочешь писать спецификацию руками — можно попробовать генерировать её с помощью AI и получить что‑то похожее на Google Stitch (в проекте для этого есть Skill).
Лучше один раз увидеть, чем сто раз услышать:
(Слева YAML‑экрана, под ним Svelte‑код этого экрана, который обновляется в реальном времени, справа вкладка браузера.)
Чтобы дизайнерам дальше было проще воспринимать Specify, проведу параллели с Figma.
Стили
В Specify в папке specs/ находится файл tokens.yaml — почти как Figma Variables. В specs/components/ — YAML‑файлы компонентов, в specs/screens/ — экранов. У каждого экрана и компонента есть слои (frame, text, image и так далее), у каждого слоя — стили.
Стили сгруппированы по префиксам и почти дублируют свойства в Figma:
pos— секция Position

lay— секция Auto Layout

vis— секция Appearance

txt— секция Typography

fill,stroke,shadow,blur— самостоятельные, без префиксов.
Пример:
styles: lay: column # Auto Layout — Flow: Vertical lay-width: fill-container # Тянем на всю ширину lay-align-h: center # Выравнивание по горизонтали lay-align-v: center # Выравнивание по вертикали
В Figma для состояний делают варианты компонента. В Specify состояния — это стили, почти как в CSS:
hover— наведение курсораfocus— фокус в поле или при навигации с клавиатурыpressed— клик/нажатиеdisabled— неактивный элементselected— выбранный (поддерживает вложенные:hover,focus,pressed,disabled)valid— поля внутри контейнера прошли валидацию (с вложенными состояниями)invalid— не прошли валидацию (тоже с вложенными).
Пример:
styles: fill: "#F5F5F5" hover: fill: "#EBEBEB" pressed: fill: "#E0E0E0" focus: stroke: - color: white weight: 1 position: inside - color: blue weight: 2 position: outside invalid: stroke: - color: red weight: 2 position: outside
Свойства компонентов и реактивность
У компонентов есть:
props— входные данные снаружи (как в Figma).state— внутреннее состояние.stores— глобальные реактивные переменные с логикой, доступны из любого компонента.logic— пользовательский код, подключаемый к компоненту.data— статичные или динамические данные с бэкенда.
У экранов так же, только вместо props — path и params (о них в документации).
Пример:
button: props: label: Text # Объявляем props layers: - text: content: "{{ props.label }}" # Выводим значение props
Вызываем компонент на экране и пробрасываем props:
demo: path: /demo layers: - c.button: props: label: Нажми на меня
Условия и циклы
cond-if позволяет показывать элементы по условию.
demo: path: /demo state: count: 0 min: -10 max: 10 layers: - text: content: "Счетчик лайков: {{ state.count }}" - c.button: # Кнопка отображается, если state.count больше state.min cond-if: "{{ state.count > state.min }}" props: label: 👎 on-click: set-state: count: "{{ state.count - 1 }}" - c.button: # Кнопка отображается, если state.count меньше state.max cond-if: "{{ state.count < state.max }}" props: label: 👍 on-click: set-state: count: "{{ state.count + 1 }}"
Циклы размножают слой по массиву данных:
demo: path: /demo state: isAnnually: false data: pricing: # Массив данных - id: 1 title: Free monthly: 0 annually: 0 - id: 2 title: Pro monthly: 100 annually: 80 - id: 3 title: Max monthly: 200 annually: 150 layers: - frame: for: item in data.pricing # Создаём цикл, этот frame будет повторяться столько раз, сколько записей в массиве key: item.id # Уникальный ключ для корректного диффа в DOM layers: - text: content: "{{ item.title }}" # Выводим название - text: content: "Цена: {{ state.isAnnually ? item.annually : item.monthly }} ₽" # Выводим цену по условию state.isAnnually
Триггеры событий
Аналог Interactions из Figma. События:
on-click— клик.on-mount/on-unmount— появление/исчезновение слоя.on-change— изменение значения (для слоёвfieldиtext).on-submit— отправка формы.on-keydown— нажатие клавиши.
События могут запустить действия:
set-state— изменить state.set-props— для исключительных случаев с редактируемыми props (подробнее в документации).navigate-to— переход на другой экран (с анимацией) или внешнюю ссылку.navigate-back— назад.set-notification— снекбар.post-data— отправка в Appwrite.run— выполнение пользовательского кода, отconsole.log()до вызова своей функции или стора.submit/reset— отправить/сбросить форму.
Пользовательский код
Не все компоненты реально сделать без дополнительной логики. Например, в компоненте slider-md есть функция createSlider, которая следит за захватом ползунка и обновляет value.
Specify даёт возможность подключить свой код:
screenName: path: /demo logic: - fnName # Функция из src/logic stores: - cart # Стор корзины из src/stores on-keydown: - key: Control+S run: "{{ logic.fnName(stores.cart.list) }}" # Вызовем функцию при нажатии Control+S layers: - text: content: "В корзине: {{ stores.cart.list.length }}" # Выведем количество товаров - c.button: props: label: Добавить on-click: run: "{{ stores.cart.add({ name: 'Эспрессо', price: 150 }) }}" # Добавляем в корзину товар
Расширяйте приложение, не затрагивая сгенерированный код.
Интеграция с Appwrite
Немного про Appwrite
Это open‑source бэкенд, разворачивается на своём VPS, не сильно сложнее установки WordPress. Юзерфрендли, закрывает много вопросов через веб‑интерфейс, сам выпускает SSL для привязанных доменов. Очень быстро развивается, в этом году переработали API, добавили MongoDB и PostgreSQL. Тянет большие нагрузки, есть публичный бенчмарк, где один сервер держит больше миллиона одновременных WebSocket‑соединений. По экономике: VPS дороже хостинга, но на одном сервере можно вести несколько проектов и хостить сайты.
Запрос данных (get‑data)
Надстройка над Appwrite SDK. Управляет кешем, мгновенно отвечает данными из кеша и затем возвращает актуальные данные. Не кеширует ответы по отдельности, а работает как локальная БД — сама фильтрует и сортирует. Это позволяет приложению предсказуемо работать в оффлайне. Через get-data можно получить данные из базы, профиля пользователя, команд и файлов. Поддерживается Realtime (обновление по WebSocket, включается флагом realtime) и бесконечный скролл (флаг infinite-scroll). Подробнее в документации.
Отправка данных (post‑data)
Тоже надстройка над SDK. В оффлайне складывает запросы в очередь, а как появится сеть — отправляет в Appwrite. Создаёт, обновляет, удаляет записи в БД. Всё, что касается аккаунта пользователя. Управление командами. Загрузка файлов в Storage. Подробнее в документации.
Расширяемость
Specify не навязывает стек. Трансляция спецификации в итоговый код идёт через Vite‑плагины, и вы можете:
Написать свой Vite‑плагин и транслировать спецификацию в любой фреймворк: React, Vue, Svelte, Solid — что угодно.
Расширить DSL под свой бэкенд: если у вас не Appwrite, а Supabase, Firebase, свой REST или GraphQL — можете дописать свои операторы
get-data/post-dataи работать с ними как с родными.
Дальнейшие планы
Добавить необходимый минимум базовых компонентов.
Добавить Push‑уведомления, подписка/отписка на клиенте.
Расширить документацию и добавить больше примеров.
P. S. Если тебе откликается Specify и ты хочешь принять участие в развитии (код, документация, примеры, дизайн компонентов) — пиши на kov@lenko.email. Также буду рад фидбеку и звёздочкам на GitHub.

