Specify — open-source Framer на спеках

—

от автора

Привет, Хабр!

Я принёс 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 — это не больно:

  1. Спецификация на языке дизайна — здесь почти вся терминология из Figma.

  2. IDE помогает писать: автоподстановка свойств и значений.

  3. Любое изменение мгновенно отображается в браузере (HMR).

  4. Не хочешь писать спецификацию руками — можно попробовать генерировать её с помощью 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 и работать с ними как с родными.

Дальнейшие планы

  1. Добавить необходимый минимум базовых компонентов.

  2. Добавить Push-уведомления, подписка/отписка на клиенте.

  3. Расширить документацию и добавить больше примеров.

P.S. Если тебе откликается Specify и ты хочешь принять участие в развитии (код, документация, примеры, дизайн компонентов) — пиши на kov@lenko.email. Также буду рад фидбеку и звёздочкам на GitHub.

ссылка на оригинал статьи https://habr.com/ru/articles/1094938/