Привет, Хабр!
Я принёс 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.
ссылка на оригинал статьи https://habr.com/ru/articles/1094938/