Доброго времени суток, уважаемые пользователи Хабра.

Я не то что бы профессиональный разработчик на C++, в основном я занимаюсь геймдевом на UE5 (по крайней мере последнее время). Но последнее время достаточно часто я балуюсь разработкой десктоп приложений под Windows. Для красоты и простоты я задумывался об использовании именно react. Но из выбора что я увидел было 2 варианта:

  1. Tauri - Фреймворк под Rust с отрисовкой фронтенда сделанном на React и т.п.

  2. Electron -  фреймворк для разработки кроссплатформенных настольных приложений с использованием веб-технологий (скопировал описание с гугла)

Electron я отмел сразу, так как хотел писать бэкенд на чем то более удобном чем JS/TS для себя. Tauri мне очень понравился, но изучение Rust заняло немного времени. И смотря на это все я подумал, что можно реализовать какой-нибудь аналог Tauri используя C++.

Немного посидев поизучав информацию, я примерно выстроил себе план работы, из этого вылился первый MVP Shine. Фреймворк на данный момент поддерживает только Windows и тесно связан с vcpkg, так как находится на очень ранней стадии разработки, но его уже вполне себе можно потыкать и использовать.

Репозиторий проекта

А теперь к установке и использовании:

Для начала то, что нам нужно для запуска:

  • CMake

  • Node.JS

  • CLion (можете использовать что-то своё, мне комфортнее в нём)

Дальше мы открываем терминал и прописываем команду:
npm create shine-app@latest

У нас появится простая на данный момент настройка проекта, а именно указание его имени:

CLI создания приложения
CLI создания приложения

Дальше после того как мы введём имя приложение он создаст папку со всем, что нам нужно для реализации приложения

CLI Вывод
CLI Вывод

Структура проекта у нас выглядит следующим образом:

D:.
├───cmake # кастомные cmake функции для удобства сборки и запуска
├───frontend # Тут у нас находится фронтенд, сам UI приложения
│   ├───public
│   └───src
│       └───assets
├───generated # Тут у нас лежат ассеты собранные в бинарник, чтобы не таскать их за собой в релизе
├───scripts # Скрипты сборки для удобства
├───shine # Код библиотеки
│   ├───components
│   │   ├───include
│   │   │   └───shine
│   │   │       └───components
│   │   └───src
│   └───core
│       ├───include
│       │   └───shine
│       │       └───engine
│       └───src
│           └───engine
│               └───win32
└───src # Код нашего приложения (в данном случае main.cpp)

На данный момент я не придумал ничего лучше, чем тянуть за собой код библиотеки в пример, чтобы избежать бед со сборкой с разными компиляторами

Дальше мы открываем проект в CLion (или там где удобно вам) и настраиваем профили CMake, У меня это выглядит так:

Мои настройки CMake
Мои настройки CMake

Дальше идем в View -> Tool Windows -> vcpkg

Открываем vcpkg
Открываем vcpkg

Далее выбираем наш существующий манифест, и нажимаем на карандашик сверху чтобы он работал с нашими CMake профилями:

UI с vcpkg окном
UI с vcpkg окном

Кликаем галочку Add vcpkg integration to existing CMake profiles

Галочка что нужно нажать
Галочка что нужно нажать

Далее кликаем ПКМ по корневому CMakeLists.txt и нажимаем Reload CMake Project.

Расположение Reload CMake Project
Расположение Reload CMake Project

Дальше мы можем спокойно запустить приложение и увидеть что оно работает:

UI Интерфейс template приложения
UI Интерфейс template приложения

Идею темплейта я решил взять просто с Tauri, простое окно где можно ввести имя и при нажатии Greet наше C++ ядро отформатирует сообщение и выведет его во фронтенде:

После нажатия Greet
После нажатия Greet

Со стороны C++ наш handler функции greet выглядит следующим образом:

SHINE_COMMAND(greet) {
    // У функции собранной с SHINE_COMMAND по дефолту есть аргументы
    // В развернутом виде функция выглядит примерно так:
    // nlohmann::json greet(const nlohmann::json& args)
    std::string name_str = args["name"];

    return std::format("Hello, {}", name_str);
}

и указание того, что мы можем вызвать эту функцию из фронтенда:

app.GetRouter().AddHandlers({SHINE_HANDLER(greet)});

После этого наш фронтенд знает что мы можем вызывать функцию greet с помощью метода invoke. Выглядит это следующим образом:

    async function handleGreet() {
        if (!name.trim()) return;

        setIsLoading(true);
        try {
            // Вызываем функцию и записываем ответ в setGreetMsg
            const res = await invoke('greet', { name });
            setGreetMsg(res.result || res);
        } catch (error) {
            setGreetMsg("Error: Could not connect to Shine Core");
        } finally {
            setIsLoading(false);
        }
    }

Дальше мы можем как угодно переделывать наш проект React и делать наше UI. При сборке в дебаге приложение просто слушает локалхост с нашим портом, в Shine есть поддержка HMR и при изменении кода, UI в приложении так же изменится. При релизе же, проект фронтенда собирается и после чего переводятся в байты, дальше мы используем эти байты внутри приложения не распаковывая их храним в памяти.

Помимо всего этого у нас есть конфиг файл: shine.conf.json, выглядит он так по стандарту:

{
  "window": {
    "title": "Shine Secure App",
    "width": 900,
    "height": 600
  },
  "capabilities": {
    "allowedCommands": [
      "fs_read_text_file",
      "window_drag"
    ]
  }
}

В нем пока что мы указываем title окна приложения, его размеры. Помимо этого есть ещё 2 параметра конфига:

  • frameless - Отключает рамки у окна

  • resizable - Разрешает / запрещает изменение размера окна

При дебаге конфиг будет подтягиваться из файла, при сборке он так же находится в итоговом приложении, опять же чтобы не тянуть за собой лишние файлы.

Учитывая что мы пишем на C++, и запихиваем ассеты фронтенда прямиком в бинарник, приложение из template имеет достаточно приятный вес - всего 845кб, при этом он не требует зависимостей для запуска и можно отправлять голый .exe пользователю и он запустится на его системе.

Вес релизного приложения
Вес релизного приложения


В планах у меня реализовать поддержку Linux и MacOS, а так же возможность получать состояние элементов фронтенда из плюсов и взаимодействия с ними. Например заполнять прогресс бар условный из C++ при какой-нибудь загрузке и т.п.

Это моя первая попытка сделать что-либо полезное на C++ для опенсорса, буду очень рад объективной критике, советам и контрибьютингу.

Комментарии (15)


  1. Tyiler
    04.05.2026 15:28

    Electron я отмел сразу

    Смотрим код
    Смотрим что такое WebView2

    В общем, в статье речь не о том, что надо бы осветить.


    1. wtf-keaton Автор
      04.05.2026 15:28

      От электрона я отказался не из за использования WebView, это меня как раз таки устроило. Меня не устроил размер и количество файлов на выходе, а так же то что мне не особо понравилось как мне реализовывать там модули на C++, а это был важный критерий для меня.
      В случае с Shine я могу реализовать фунцкию обернув её в SHINE_COMMAND макрос и спокойно вызывать из React интерфейса


      1. Tyiler
        04.05.2026 15:28

        вот и надо было про внутр кухню больше написать, а не скрины делать где какой флажек нажать, чтобы собрать в clion.

        про память на жест диске. не на то вы внимание обращаете совсем, важнее сколько опер памяти берет, а брать будет как и электрон, скорее всего.


        1. wtf-keaton Автор
          04.05.2026 15:28

          Нет ОЗУ берет меньше. Но спасибо за замечание. Мой первый опыт что то писать на Хабре. Учту

          ОЗУ при отправке запросов на C++. Позже сделаю более удобный бенчмарк
          ОЗУ при отправке запросов на C++. Позже сделаю более удобный бенчмарк


  1. Quartix
    04.05.2026 15:28

    Спасибо за ещё одну возможность использовать браузер, чтобы просто рендерить интерфейс. Как будто нам и так веб-слопа на компе недостаточно.


    1. ImagineTables
      04.05.2026 15:28

      Веб-слоп делает веб-слопом не рендеринг интерфейса в браузере, а бизнес-логика десктопного приложения, написанная на джаваскрипте. Конечно, многое ещё зависит от того, какой именно взят браузер. Есть браузер, который весит 18 мегабайт и кушает меньше памяти, чем Блокнот.


      1. Quartix
        04.05.2026 15:28

        Под "рендерингом" я подразумевал все процессы, которые происходят на стороне браузера. Интересно, какой браузер кушает меньше, чем блокнот? В любом случае, смысла тащить web и js в десктоп - нету. Наелись уже от веб-макак. Либо учите натив, либо даже не думайте об этом.


        1. ImagineTables
          04.05.2026 15:28

          Наелись уже от веб-макак.

          А нормальные программисты тут причём? Те, кто знают, какой браузер кушает меньше, чем Блокнот?

          Макаки и среди нативщиков встречаются частенько. Не могут нативно в layout’инг, и делают большие окна non-resizable.


          1. Quartix
            04.05.2026 15:28

            Ну да, в этом есть правда. Но вот лично я не помню, чтобы встречал нативные приложения, у которых будут проблемы с layout. В моём окружении большинство приложений - это qt или gtk. Я чаще встречаю веб приложухи, у которых layout залочен.

            А насчёт такого "браузера" - нигде такое не встречал. Если бы веб жрал меньше, то на него все и пересаживались бы. А так, его много кто использует просто потому что знает html, css и js/ts.


            1. ImagineTables
              04.05.2026 15:28

              Я ваши эмоции частично понимаю. Недавно я задавал вопрос, чем в CI/CD препроцессить HTML, как раз для подобных встроек (чтобы писать его с сокращениями, с прицелом на будущую генерацию на стороне юзера из DSL). Так вот, пришёл некий чел и написал, что всё это бесполезная затея, потому что Vue так всё равно не запихнуть. Эти новые программисты не понимают, как можно что-то писать без какого-нибудь Vue. И это бесит. Я, как мог вежливо, написал, что HTML != Vue, но сдержаться было трудно.

              Но только вот на здоровую-то голову зачем валить? Начинается с того, что вам надоедает каждый раз писать void OnResize() { for (int i = 0; i < _Controls.size(); i++) { … } }, и вы начинаете думать о DSL, который позволил бы этого не делать. И желательно, чтобы он поддерживал условия вида «размер этого компонента — столько-то процентов от общей ширины, но не меньше столько-то пикселей, и не больше столько-то пикселей, в случае нехватки ширины с переносом на следующую строку, в случае совсем уж нехватки ширины — с появлением горизонтального скролбара». Поздравляю, вы изобрели flex, который позволяет писать писать это формулами вида 1 1 100px. Затем вам надо добавить анимации (сворачивание, разворачивание, подсвечивание и т.п.), и вы изобретаете transition (не в цикле же их писать каждую по отдельности). Затем вам надо поддержать HiDPI-мониторы (4К/8К), хотя бы в форме скейлинга (как Telegram). В HTML для этого есть вёрстка в rem/em. (Вот статья, которую я об этом написал, она больше для фронтендеров, но другого способа написать адаптивные приложения (в том числе десктопные) не существует — вам надо или взять готовую реализацию rem/em, или изобрести свою, только забагованную и ни с чем не совместимую). Затем вам надо пройти сертификацию, и поддержать accessibility. Чтобы скринридер мог озвучить, где у вас меню, где тебю. Интерфейс на HTML, если вы сделали его правильно, автоматически совместим со скринридерами, а с приложением, написанным по старинке, обычно надо сделать вдвое больше работы. Потом вам надо сделать так, чтобы юзер мог работать только с клавиатуры. Потом…

              И вы понимаете, что написали свой браузер. (Либо написали колхозное приложение, которое не умеет в современный вид, HiDPI, accessibility и layout’инг).

              18 мегабайт кушает браузер под названием Sciter. Почему его так раздуло — не знаю, пчёлы покусали, наверно )) Я им пользовался, когда он весил 5 мегабайт, и умел ВСЁ вышеперечисленное. Вот это приложение весило вместе с ним 10 мегабайт и кушало памяти… там чистые измерения нельзя провести, как вы понимаете, потому что код находится в адресном пространстве explorer.exe, но если измерить грубо, Блокнот в Windows 11 жрёт больше.

              Кстати, Sciter вырос из HTMLayout, который вообще не поддерживал скрипты. Только разметку и CSS. Обработчики надо было писать на CSS+ и на C++. Тогда он весил 3 мегабайта и тормозить там было нечему.

              Ultralight, про который я написал ниже, весит, конечно, побольше, но он заточен под скорость, и его запилили для геймдевелоперов. На нём предполагается писать HUD для игр. Проценты здоровья, или встроенный планшет с заданиями. Знаете, чем отличается бесплатная версия от платной? Бесплатная залочена на 60fps. Больше не даёт. Вы думаете, там что-то тормозит? А, ну ещё платная компилирует весь JS в нативный код. Хотя JS надо просто избегать.

              Если бы веб жрал меньше, то на него все и пересаживались бы.

              А вы думаете, легко использовать Sciter или правильно использовать Webkit/CEF? Для этого надо уметь в C++/многопоточку/маршаллинг и т.п. Или, может быть, вы думаете, что легко встраивать Ultralight в движок своей игры?


  1. peter23
    04.05.2026 15:28

    Neutralinojs написан на C++ с использованием WebView


  1. ImagineTables
    04.05.2026 15:28

    Это моя первая попытка сделать что-либо полезное на C++ для опенсорса, буду очень рад объективной критике, советам

    Ну, теперь не говорите, что не просили моего совета! ))

    В этой области можно столько полезного на C++ сделать для опенсорса, а выбран был почему-то способ засунуть React в обёртку над WebView2.

    Я сейчас вынужденно пилю обёртку на C++ над Ultralight. (Вам с геймдевным бэкграундом, наверно, будет интересен Ultralight). Потому, что их родная оконная обёртка (ultralight::Window) очень тупая и не позволяет делать layered-окна. А куда же без них? Без них не сделать даже такую штуку, как простое советское контекстное меню c круглыми краями (которые я хочу задавать при помощи border-radius). Пилить эту обёртку мне совершенно не хочется, душа просит чего-то лёгкого и воздушного, например, ваять анимации на CSS, но увы: никто другой не обернул.

    Если не хочется оборачивать чужие коммерческие проекты, можно аналогичным образом обернуть CEF. CEF, конечно, это не Ultralight — сплошные утечки памяти и много процессов (как и у вас сейчас из-за WebView2, кстати говоря), но возможность сделать контекстное меню c круглыми краями и блуром прямо на CSS — это всё равно круто и мегаполезно. (Для этого надо вписывать результаты рендеринга CEF в виндовый композитинг через буфер кадров — и направлять ввод в его псевдоокно).

    Если не хочется брать CEF, то вот тут @drakkonne хвалит WebKit.

    А React или ванилин — это уж второстепенной важности вопрос. Тем более, люди, которым нужен React, скорее всего, всё равно возьмут React Native или что-нибудь похожее. Или просто Electron.


    1. wtf-keaton Автор
      04.05.2026 15:28

      В этой области можно столько полезного на C++ сделать для опенсорса, а выбран был почему-то способ засунуть React в обёртку над WebView2.

      Честно - делал изначально для себя, но получилось вроде бы даже на столько прикольно что выпустил в опенсорс

      Без них не сделать даже такую штуку, как простое советское контекстное меню c круглыми краями (которые я хочу задавать при помощи border-radius). Пилить эту обёртку мне совершенно не хочется, душа просит чего-то лёгкого и воздушного, например, ваять анимации на CSS, но увы: никто другой не обернул.

      Прямо сейчас я как раз балуюсь с этим в рамках Shine (хотя щас переключился на то, чтобы написать тесты и бенчмарк). CEF не хочу использовать, у меня к нему негативный опыт работы, а на счет webkit почитаю посмотрю, спасибо)


  1. yarrrman
    04.05.2026 15:28

    Есть же WebviewUI. И там поддержка кучи бакендов от С и Pascal до Odin и Zig

    У tauri плюс в том, что на нем еще и мобильные приложения можно делать


  1. pavlushk0
    04.05.2026 15:28

    Что только не прилумают люди лиш бы не пользоваться qt.