12k
All articles

Первый взгляд на Gea — UI-фреймворк, построенный вокруг компилятора

Gea — компилятор-ориентированный JavaScript UI framework с JSX, классами store и прямыми DOM-исправлениями. Статья разбирает props, размеры и зрелость.

OpenReplay Team
OpenReplay Team
Первый взгляд на Gea — UI-фреймворк, построенный вокруг компилятора

Gea — это JavaScript-фреймворк для UI с компилятором во главе угла: плагин для Vite читает ваш JSX на этапе сборки, определяет, какие DOM-узлы зависят от какого состояния, и генерирует прямые патчи DOM. Виртуального DOM здесь нет, и во время выполнения ничего не диффится.

Фреймворки, обещающие «скомпилировать рантайм в ноль», появляются каждые пару месяцев, и анонсирующий пост обычно даёт вам питч и цифры, почти ничего не говоря о компромиссах. Страницы Gea читаются примерно так же, поэтому далее мы разберём модель в том виде, как её описывает проект, укажем, откуда берётся каждая цифра, и подробнее всего рассмотрим то, что действительно отличает Gea от React и Vue: двусторонние объектные props.

Ключевые выводы

  • Gea связывает реактивность на этапе компиляции: плагин Vite анализирует JSX, сопоставляет состояние с DOM-узлами и генерирует точечные патчи вместо того, чтобы поставлять виртуальный DOM.
  • Вся модель сводится к трём правилам: stores — это классы, наследующие Store, компоненты — это классы с методом template() или обычные функции, а вычисляемые значения — это геттеры.
  • Объектный или массивный prop передаёт дочернему компоненту тот же самый прокси, который держит родитель, поэтому запись в дочернем компоненте меняет и состояние родителя. Примитивы, наоборот, копируются.
  • Проект сообщает о 121 Б в brotli для hello world, 4,9 КБ для интерактивного todo и результате 1.02 в js-framework-benchmark — все замеры сделаны на Gea 1.3.0 самим мейнтейнером на его же сборках.
  • С одним мейнтейнером, ранним номером версии и без независимого аудита Gea заслуживает вечернего эксперимента, а не ставки в продакшене.

Что такое Gea?

Gea переносит реактивность из рантайма в сборку. Согласно сайту проекта, его плагин для Vite читает ваш JSX во время компиляции, определяет, какие DOM-узлы зависят от каких фрагментов состояния, и настраивает патчи, затрагивающие только эти узлы. Сравнение с Vue формулирует контраст прямо: там, где Vue заново выполняет render-функцию и диффит получившееся дерево, Gea просто вызывает патч-функции, которые компилятор уже сгенерировал. Ваше состояние живёт в обычных классах, которые Gea оборачивает в глубокий прокси, так что обыкновенного присваивания вроде this.count++ достаточно, чтобы обновить DOM. Нечего оборачивать и нет списка зависимостей, который нужно поддерживать в актуальном состоянии. README представляет эту родословную как продолжение более ранних библиотек мейнтейнера — erste.js и regie — теперь с трансформациями JSX на этапе компиляции.

Что представляет собой модель Gea в трёх строках?

Вся поверхность Gea укладывается в три правила: stores — это классы, наследующие Store, компоненты — это классы с методом template() или обычные функции, а вычисляемые значения — обычные геттеры. Счётчик из README демонстрирует всё сразу:

// counter-store.ts
import { Store } from '@geajs/core'

class CounterStore extends Store {
  count = 0
  increment() { this.count++ }
  decrement() { this.count-- }
}

export default new CounterStore()
// app.tsx
import { Component } from '@geajs/core'
import counterStore from './counter-store'

export default class App extends Component {
  template() {
    return (
      <div>
        <h1>{counterStore.count}</h1>
        <button click={counterStore.increment}>+</button>
        <button click={counterStore.decrement}>-</button>
      </div>
    )
  }
}

Документация по компонентам объясняет, что плагин Vite во время сборки переписывает функциональные компоненты в классовые, и что шаблон компонента выполняется ровно один раз. Всё, что происходит дальше, — это патч, а не повторный рендер.

Чем JSX в Gea отличается от JSX в React?

JSX в Gea выглядит как в React, и одну условность действительно придётся изменить: пишите class там, где React ждёт className. С событиями всё мягче. README записывает их как атрибуты в нижнем регистре — click, input, change, — но сравнение с Vue в документации подтверждает, что onClick, onInput и onChange тоже принимаются, так что обработчики в стиле React переносятся как есть. А ref не принимает ref-объект: Gea кладёт DOM-узел прямо в свойство компонента, как только рендер завершён.

// Привычка из React     // Эквивалент в Gea
<div className="card"    <div class="card"
  onClick={save} />        click={save} />

Паттерн ref в документации — это поле класса, инициализированное как null, атрибут ref={this.videoEl} на элементе, а затем прямое использование this.videoEl после рендера.

Props: объекты — это прокси родителя

Главное отступление от React и Vue — то, что происходит с непримитивным prop. Документация по компонентам Gea объясняет, что дочерний компонент получает тот же самый прокси, которым владеет родитель, поэтому любая запись из дочернего компонента попадает в состояние родителя и отражается в его DOM. Примитивы ведут себя так же, как аргументы функций в JavaScript: дочерний компонент получает копию, и переприсваивание не меняет ничего за его пределами. В документации показан дочерний компонент, делающий ровно это:

export default class Editor extends Component {
  rename() {
    this.props.user.name = 'Bob'   // parent's DOM updates too
  }
  template({ user }) {
    return <button click={this.rename}>{user.name}</button>
  }
}

Правило работает на любой глубине дерева. Передайте ту же ссылку внуку, дайте ему записать в объект — и все предки, наблюдающие за этими данными, перерисуются. Ничего не нужно поднимать наверх через колбэк, и между ними нет ни emit, ни v-model.

Дочерний обновляет родителяReactVueGea
Объекты/массивыProps-колбэкиemit / v-modelПрямая мутация общего прокси
ПримитивыProps-колбэкиemit / v-modelНевозможно (передача по значению)

Документация подаёт это исключительно как преимущество, а открытые вопросы остаются без ответа. В дереве из 40 компонентов — какой именно компонент изменил этот объект? Однонаправленная конвенция React существует отчасти для того, чтобы у изменений было отслеживаемое происхождение; Gea меняет это на прямолинейность, и пока никто не описал, во что этот обмен обходится на масштабе. То же касается и самой связки на этапе компиляции: как компилятор обрабатывает код, который он не может проанализировать статически, документация не рассматривает.

Размер и скорость — в том виде, как их сообщает проект

Каждая цифра о производительности Gea — это собственное измерение проекта на собственных сборках конкурирующих фреймворков, сделанных мейнтейнером. Таблицы размеров в README дают приложению hello-world 121 Б JavaScript в brotli — рядом со сборками проекта для Solid (3,6 КБ), Svelte (8,5 КБ), Vue (20,7 КБ) и React (50,8 КБ), при 4,9 КБ для интерактивного todo. Оба набора цифр сняты на Gea 1.3.0 со свежих production-сборок Vite 8.0.10.

Результат 1.02 в js-framework-benchmark, где 1.00 — это написанный вручную ванильный JavaScript, получен в собственном прогоне набора тестов проектом на Chrome 147, а не в официальном опубликованном раунде. Фраза «самый быстрый компилируемый UI-фреймворк» на главной странице — это утверждение проекта, построенное на тех же самостоятельно полученных цифрах, а не вывод третьей стороны.

Что поставляется вместе с Gea?

Таблица пакетов в README перечисляет @geajs/core, @geajs/vite-plugin, @geajs/ssr для серверного рендеринга, create-gea для скаффолдинга, плюс @geajs/ui (headless-компоненты на базе Zag.js) и @geajs/mobile для мобильных примитивов. gea-tools — это расширение для VS Code и Cursor, а не npm-пакет.

Более необычный пункт — инструментарий для AI. Команда npx skills add dashersw/gea устанавливает набор agent skills, которые живут в репозитории по пути .cursor/skills/gea-framework и передают AI-ассистенту для кодинга те соглашения, о которых иначе ему пришлось бы догадываться: как работают stores, как объявляются компоненты и чем отличается JSX. Для молодого фреймворка, по которому ни у одной модели нет обучающих данных, поставка соглашений в виде потребляемых редактором skills — прагматичный ход для онбординга.

Зрелость: о чём говорят номера версий

Сравнительные таблицы и таблицы размеров в README сняты на Gea 1.3.0, а @geajs/core с тех пор опубликовал 1.4.0, согласно release notes проекта. Gea распространяется под лицензией MIT, и его поддерживает один человек — Armagan Amcalar. Все цифры по бенчмаркам и размерам являются самоотчётными, и независимого технического обзора фреймворка пока не существует. Для команды вывод прост: модель непротиворечива и достаточно компактна, чтобы оценить её за один вечер, но единственный мейнтейнер, ранний номер версии и непроверенные цифры означают, что пока нет доказательной базы, чтобы ставить на неё продукт.

По-настоящему интересное утверждение Gea — не размер бандла; оно в том, что обычные классы, функции и геттеры способны нести полноценную модель реактивности, если связывание берёт на себя компилятор. Самый быстрый способ оценить это утверждение — собрать счётчик и приведённый выше пример с props и посмотреть, ощущаются ли двусторонние объектные семантики как ясность или как источник проблем при отладке именно в вашем типе кодовой базы.

Частые вопросы

Чем Gea отличается от Solid и Svelte?

Все три опираются на компилятор, но реактивный примитив у них разный. Solid строит реактивность на сигналах, которые выполняются в браузере, а Svelte 5 использует синтаксис рун, скомпилированный из собственного языка шаблонов. У Gea нет ни того, ни другого: состояние хранится в обычных классах, обёрнутых глубоким прокси, а плагин Vite читает стандартный JSX на этапе сборки, чтобы связать прямые патчи DOM.

Какие хуки жизненного цикла есть у компонентов Gea?

Классовые компоненты Gea предоставляют четыре хука. created(props) срабатывает между конструктором и первым рендером — именно туда документация советует помещать логику инициализации. onAfterRender() выполняется, когда элемент компонента уже в документе, а его дочерние элементы смонтированы. onAfterRenderAsync() ждёт следующего requestAnimationFrame. dispose() извлекает компонент из DOM и разрушает его наблюдателей и потомков. Документация направляет вас к классовым компонентам всякий раз, когда что-либо из этого требуется.

Работает ли Gea с TypeScript?

Да. Классовый компонент объявляет форму своих props через 'declare props' — ambient-объявление, которое не порождает JavaScript, но позволяет любому редактору с поддержкой TypeScript дополнять и проверять атрибуты, без участия плагина фреймворка. Аннотация параметра template() как this['props'] переносит эти типы в деструктурированные переменные внутри метода; без неё они откатываются к any. Функциональные компоненты получают то же самое через обычную аннотацию параметра.

Как начать новый проект на Gea?

Создайте каркас командой 'npm create gea@latest' — это инструмент create-gea от проекта. Сборки Gea работают поверх Vite, а @geajs/vite-plugin отвечает за трансформацию JSX, связывание реактивности и горячую перезагрузку. В документации также есть руководство по использованию в браузере для запуска Gea без шага сборки, наряду с руководствами по роутеру, UI-киту и мобильным пакетам.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.