← Blog
Rust14 min czytania

Dioxus — aplikacje wieloplatformowe w Rust z hot-reloadem

Jeden kod źródłowy na web, pulpit, iOS, Androida i serwer, ze składnią bliską JSX i stanem opartym na sygnałach. Najciekawszy jest jednak sposób łączenia klienta z serwerem: jeden atrybut nad funkcją asynchroniczną tworzy trasę na serwerze i typowane wywołanie na kliencie. Omawiamy też, co dokładnie w hot-reloadzie jest gotowe, a co wciąż eksperymentalne, oraz rozmiary pakietów wynikowych.

Nasz stos to Laravel i React, więc pytanie „po co komu framework interfejsu w Rust" jest zasadne i chcę na nie odpowiedzieć uczciwie już na wstępie. Nie po to, żeby zastąpić aplikację internetową klienta. Po to, żeby mieć odpowiedź na trzy sytuacje, w których dziś sięga się po Electron albo po osobny zespół mobilny: narzędzie desktopowe dla klienta, mała aplikacja mobilna do jednego zadania i wewnętrzne narzędzie, które ma po prostu działać na każdym systemie.

Dioxus (github.com/DioxusLabs/dioxus) obiecuje jeden kod źródłowy na web, pulpit, urządzenia mobilne i serwer, ze składnią bliską JSX-owi i stanem opartym na sygnałach. Najciekawsza jest w nim jednak nie wieloplatformowość, a sposób połączenia klienta z serwerem — i od niego zacznę, bo to jedyna rzecz w tym frameworku, która zmieniła moje myślenie o czymś, co robimy codziennie.

Stan projektu

Dane z API GitHuba i crates.io na 8 października 2026:

  • 38 995 gwiazdek i 1876 forków, repozytorium założone 15 stycznia 2021,
  • podwójna licencja MIT albo Apache-2.0 — standard w świecie Rusta,
  • wersja stabilna 0.7.10 z 30 lipca 2026, a w przygotowaniu 0.8.0-alpha.1. Gałąź 0.7 dostała dziesięć wydań poprawkowych od listopada 2025, czyli utrzymanie jest realne, nie deklarowane,
  • ostatni push z początku września, 932 tysiące pobrań w ostatnim okresie przy 2,6 miliona w całej historii,
  • 771 otwartych zgłoszeń — liczba duża i warta odnotowania,
  • README tłumaczone na sześć języków, aktywny Discord, osobna organizacja na GitHubie dla najlepszych bibliotek ekosystemu.

Model utrzymania jest opisany wprost i warto go znać: projekt ma zespół pracujący w pełnym wymiarze, finansowany przez FutureWei, Satellite.im i program akceleracyjny GitHuba, a długoterminowym celem jest samowystarczalność dzięki płatnym narzędziom dla firm. Nie ma tu jeszcze niczego za paywallem, ale kierunek jest zapowiedziany — i to jest uczciwsze niż udawanie, że projekt utrzyma się z dobrej woli.

Trzy linie i sygnały

Podstawowy przykład z README pokazuje wszystko, co trzeba wiedzieć o warstwie widoku:

fn app() -> Element {
    let mut count = use_signal(|| 0);

    rsx! {
        h1 { "High-Five counter: {count}" }
        button { onclick: move |_| count += 1, "Up high!" }
        button { onclick: move |_| count -= 1, "Down low!" }
    }
}

Makro rsx! jest odpowiednikiem JSX-a, a stan trzyma się w sygnałach. Autorzy opisują swoją ergonomię jako połączenie najlepszych cech Reacta, Solida i Svelte i — patrząc na ten fragment — nie jest to przesada: zmiana wartości to count += 1 wprost na sygnale, bez funkcji ustawiającej, a interpolacja w tekście działa jak w szablonie.

Dla osoby piszącej w Reakcie różnica jest przy tym istotniejsza, niż wygląda: reaktywność jest drobnoziarnista. Nie ma tu ponownego uruchamiania całego komponentu przy każdej zmianie ani zależności do wypisywania w tablicach — zmiana sygnału odświeża to, co go faktycznie czyta.

Rzecz najciekawsza: serwer i klient w jednym pliku

Ten przykład z repozytorium wart jest przeczytania dwa razy:

fn main() {
    dioxus::launch(|| {
        let mut message = use_action(get_message);

        rsx! {
            h1 { "Server says: " }
            pre { "{message:?}" }
            button { onclick: move |_| message.call("world".into(), 30), "Click me!" }
        }
    });
}

#[get("/api/:name/?age")]
async fn get_message(name: String, age: i32) -> Result<String> {
    Ok(format!("Hello {}, you are {} years old!", name, age))
}

To jest kompletna aplikacja pełnostosowa. Atrybut #[get("/api/:name/?age")] nad zwykłą funkcją asynchroniczną robi dwie rzeczy naraz: tworzy trasę na serwerze i generuje typowane wywołanie dostępne na kliencie. Serializacja argumentów i wyniku dzieje się sama, a use_action po stronie widoku daje obiekt ze stanem wywołania.

Warto zobaczyć, co z tego wynika, porównując z tym, jak robimy to dzisiaj. W układzie Laravel plus Inertia ta sama funkcjonalność wymaga: trasy, kontrolera, walidacji żądania, po stronie klienta wywołania z odpowiednim adresem i obsługą stanu, a między nimi — umowy o kształcie danych, której nic nie wymusza. Zmiana typu pola po stronie serwera jest tu wykrywana w najlepszym razie testem, a w najgorszym błędem u użytkownika.

W Dioxusie ta umowa jest sygnaturą funkcji sprawdzaną przez kompilator. Zmiana i32 na String psuje kompilację klienta, nie działanie produkcji. Nie twierdzę, że to powód do przepisywania czegokolwiek — twierdzę, że jest to najlepszy argument, jaki widziałem, za jednym językiem po obu stronach, i wart poznania niezależnie od tego, czy kiedykolwiek napiszemy w tym linijkę.

Pełny stos na axum

Warstwa serwerowa nie jest zabawką — integruje się z axum, jednym z dwóch najpoważniejszych frameworków HTTP w Rust. Zestaw gotowych elementów obejmuje WebSockety, zdarzenia wysyłane przez serwer, strumieniowanie, przesyłanie i pobieranie plików, renderowanie po stronie serwera, formularze, oprogramowanie pośredniczące i generowanie stron statycznych z odświeżaniem przyrostowym.

Katalog przykładów w repozytorium jest tu najlepszą dokumentacją tego, co faktycznie działa: osobne pliki na stan serwera, oprogramowanie pośredniczące, formularze wieloczęściowe, strumieniowe przesyłanie plików, WebSockety, przekierowania, dostęp do nagłówków, parametry zapytania, uwierzytelnianie z formularzem logowania oraz — co ważne dla wdrożeń stopniowych — własną obsługę serwera axum. Innymi słowy: istniejący backend w Rust można podłączyć, zamiast pisać go od nowa.

Hot-reload: co gotowe, a co eksperymentalne

To jest w tytule tematu, więc trzeba rozdzielić dwie rzeczy, bo dokumentacja rozdziela je bardzo starannie:

  • dx serve odświeża znaczniki, style i zasoby w milisekundach i jest to funkcja gotowa, wbudowana i reklamowana bez zastrzeżeń,
  • dx serve --hotpatch aktualizuje kod Rusta w czasie działania — i jest opisany jako eksperymentalny. To jest zresztą rzecz technicznie imponująca, bo w języku kompilowanym do kodu maszynowego podmiana logiki bez restartu procesu jest trudna.

Praktyczny wniosek: przy pracy nad wyglądem cykl zwrotny jest jak w projekcie frontendowym, a przy pracy nad logiką trzeba się liczyć z rekompilacją — chyba że zaakceptujemy funkcję eksperymentalną. Przy Rust i jego czasach kompilacji ta różnica jest odczuwalna, więc warto to wiedzieć przed wyceną zadania.

Narzędzie dx, czyli codzienna praca

Framework dostarcza własne narzędzie wiersza polecenia i to ono w praktyce decyduje o wrażeniu z pracy. Instalacja idzie skryptem ze strony projektu albo — co w firmowej infrastrukturze wolę — wprost z repozytorium:

cargo install --git https://github.com/DioxusLabs/dioxus dioxus-cli --locked

Dalej wszystko sprowadza się do trzech poleceń: dx serve uruchamia aplikację z serwerem rozwojowym i odświeżaniem, dx serve --platform android stawia ją w emulatorze albo na urządzeniu, a dx bundle buduje wersję do wydania. Przełączanie platform jest parametrem, nie osobną konfiguracją projektu — i to jest ta część obietnicy „jeden kod źródłowy", która faktycznie widać w codziennym użyciu.

Warto zauważyć jedną rzecz z instrukcji uruchamiania przykładów: domyślną platformą jest pulpit, a uruchomienie tego samego przykładu w przeglądarce wymaga wyłączenia domyślnych funkcji i włączenia funkcji web. Ten wzorzec — platformy jako flagi funkcji kompilacji — przenika cały framework i jest rzeczą, którą trzeba zrozumieć na początku, bo inaczej pierwsze próby kończą się niejasnymi błędami kompilacji.

Dokumentacja i ekosystem

Dwie rzeczy w podejściu do dokumentacji zasługują na wyróżnienie, bo rozwiązują problemy, które w młodych frameworkach są normą.

Pierwsza: wszystkie elementy HTML i wszystkie nasłuchy zdarzeń są udokumentowane opisami z MDN. Czyli podpowiedź w edytorze przy elemencie rsx! mówi to samo, co dokumentacja przeglądarkowa — nie trzeba pamiętać, czy nazwa atrybutu została w tym frameworku przetłumaczona na coś innego.

Druga: dokumentacja jest budowana w ciągłej integracji razem z samym frameworkiem, żeby nie rozjechać się z kodem, a strona projektu jest jednocześnie poligonem dla nowych funkcji — jest osobnym, publicznym repozytorium napisanym w Dioxusie. Wzorzec „nasza własna dokumentacja jest największą aplikacją na naszym frameworku" jest najlepszym możliwym testem użyteczności.

Ekosystem jest przy tym zorganizowany świadomie: biblioteka standardowa rozszerzeń jest prowadzona przez społeczność, a projekt utrzymuje osobną organizację na GitHubie dla najlepszych bibliotek, które dostają darmowe wsparcie przy aktualizacjach. To jest odpowiedź na typowy problem młodego ekosystemu — biblioteki porzucane przy każdej zmianie wersji frameworka.

Renderery, czyli gdzie to w ogóle działa

Lista jest długa i część pozycji jest zaskakująca:

  • web — rysowanie wprost do DOM-u przez WebAssembly, z możliwością wcześniejszego renderowania po stronie serwera i uwodnienia na kliencie,
  • pulpit — przez komponent przeglądarki systemowej, z pełnym dostępem do systemu bez komunikacji międzyprocesowej. To jest różnica wobec architektury, w której warstwa interfejsu rozmawia z warstwą systemową przez kanał,
  • urządzenia mobilne — iOS i Android, z budowaniem plików .ipa i .apk oraz bezpośrednimi wywołaniami do Javy i Objective-C przy minimalnym narzucie,
  • renderowanie po stronie serwera z zawieszaniem i uwodnieniem, oraz liveview, czyli interfejs sterowany z serwera,
  • eksperymentalny renderer na WGPU (silnik Blitz) oraz Freya oparta na Skii — czyli rysowanie natywne bez komponentu przeglądarki,
  • a do tego rzeczy z gatunku „nie spodziewałem się": osadzenie w Bevy, w samym WGPU i uruchamianie na wbudowanym Linuksie.

Rozmiary, na które warto spojrzeć

Największym zarzutem wobec aplikacji desktopowych opartych na technologiach webowych jest waga. Liczby podawane przez projekt są tu konkretne i sprawdzalne: aplikacja internetowa poniżej 50 kilobajtów (README porównuje to z Reactem), aplikacje desktopowe i mobilne poniżej 5 megabajtów, a przenośne pliki wykonywalne poniżej 3 megabajtów.

Polecenie dx bundle buduje i pakuje z pełnymi optymalizacjami, a na potrzeby weba dokłada generowanie obrazów w formacie AVIF, kompresję WebAssembly i minifikację. Dla porównania: aplikacja w Electronie startuje od kilkudziesięciu megabajtów, bo dowozi własną przeglądarkę. Dioxus na pulpicie używa komponentu przeglądarki obecnego w systemie — z czego wynika i mały rozmiar, i pewna zależność od wersji tego komponentu u użytkownika.

Komponenty gotowe do wzięcia

Rzecz, która najbardziej obniża próg wejścia dla osoby przychodzącej z Reacta: projekt dostarcza własny zestaw komponentów podstawowych wzorowanych na shadcn/ui i Radix Primitives. Kto pracował z którymkolwiek z nich, zna ten model — komponenty bez narzuconych stylów, z gotową obsługą dostępności i zachowań, do ostylowania po swojemu.

Stylowanie odbywa się przez HTML i CSS, z wbudowaną obsługą TailwindCSS albo dowolnej innej biblioteki. Czyli cała wiedza frontendowa zespołu przenosi się tu bez straty — nowa jest składnia i język, nie sposób budowania interfejsu.

Pułapki

  • Wersje 0.x i zmiany łamiące zgodność między wydaniami pomocniczymi. Ścieżka 0.6 → 0.7 → 0.8 nie jest bezbolesna, a alfa kolejnej wersji już trwa. Przypnij wersję i zaplanuj czas na migracje,
  • Dokumentacja miejscami nie dogania kodu. README odsyła po przykłady dla „najnowszego wydania stabilnego" do gałęzi 0.6, choć stabilne jest 0.7.10. Drobiazg, ale mówiący, jak szybko to się zmienia,
  • 771 otwartych zgłoszeń. Przy frameworku o takim zasięgu to jest sygnał zarówno aktywności, jak i zaległości,
  • Hot-patching kodu Rusta jest eksperymentalny — przy pracy nad logiką licz się z rekompilacją,
  • Renderer natywny jest eksperymentalny. Produkcyjna droga na pulpicie prowadzi przez komponent przeglądarki systemowej,
  • Czasy kompilacji Rusta są tym, czym są, i przy dużym projekcie odczuwalnie zmieniają rytm pracy,
  • Kadry. To jest realny koszt, o którym trzeba powiedzieć klientowi: osoby piszące w Rust są droższe i trudniej dostępne niż osoby piszące w TypeScripcie, a utrzymanie takiego projektu przez kilka lat jest zobowiązaniem,
  • Na weba to WebAssembly — z czego wynika ważenie pakietu, zimny start i inne narzędzia diagnostyczne niż te, które zespół zna,
  • Obsługa urządzeń mobilnych jest młoda. Działa i jest reklamowana jako najszybsza droga do aplikacji natywnej w Rust, ale nie ma za sobą lat wdrożeń, jak ekosystem Reacta Native czy Fluttera.

Gdzie to ma sens w naszej pracy

  • Narzędzie desktopowe dla klienta. Konfigurator, przeglądarka danych, narzędzie dla działu produkcji — coś, co ma działać na Windowsie w hali i nie wymagać przeglądarki. Trzy megabajty zamiast osiemdziesięciu i pełny dostęp do systemu bez komunikacji międzyprocesowej,
  • Mała aplikacja mobilna do jednego zadania. Skanowanie kodów, przyjęcie towaru, obsługa zgłoszeń. Jeden kod na oba systemy, bez zespołu mobilnego,
  • Narzędzia wewnętrzne dla nas samych. Tu koszt kadrowy jest argumentem odwrotnym — nasza własna nauka nikomu nie jest fakturowana, a narzędzie działa na każdym systemie w zespole,
  • Projekty, w których liczy się rozmiar albo brak środowiska uruchomieniowego — wbudowany Linux, urządzenia, kioski,
  • Nauka wzorca umowy sprawdzanej przez kompilator. Nawet zostając przy Laravelu i Inertii, warto zobaczyć, jak wygląda granica klient-serwer bez ręcznie utrzymywanej umowy — bo to zmienia pytania, które zadaje się własnej architekturze.

Czego bym nie robił: nie przepisywałbym na to działającej aplikacji internetowej klienta i nie proponowałbym Dioxusa jako domyślnego wyboru na sklep czy panel. Nie z powodów technicznych, a kadrowych i utrzymaniowych — a te przy projekcie prowadzonym latami ważą więcej niż elegancja rozwiązania.

Podsumowanie

  • Jeden kod na web, pulpit, iOS, Androida, serwer i kilka miejsc, których się nie spodziewasz,
  • Makro rsx! zamiast JSX-a i sygnały zamiast haków, z reaktywnością drobnoziarnistą i mutacją wprost na sygnale,
  • Jeden atrybut nad funkcją asynchroniczną tworzy trasę i typowane wywołanie klienta — najciekawsza rzecz w tym frameworku,
  • Umowa klient-serwer jest sprawdzana przez kompilator, nie utrzymywana ręcznie,
  • Pełny stos na axum, z WebSocketami, strumieniowaniem, formularzami i możliwością podłączenia istniejącego backendu,
  • dx serve odświeża wygląd w milisekundach; hot-patching kodu Rusta jest eksperymentalny,
  • Aplikacja internetowa poniżej 50 kB, desktopowa poniżej 5 MB, plik wykonywalny poniżej 3 MB,
  • Komponenty podstawowe wzorowane na shadcn/ui i Radix plus stylowanie HTML-em i CSS-em z Tailwindem,
  • Platformy są flagami funkcji kompilacji, a nie osobnymi projektami — o czym trzeba wiedzieć przy pierwszym uruchomieniu,
  • Elementy HTML udokumentowane opisami z MDN, a dokumentacja budowana w CI razem z frameworkiem,
  • Wersje 0.x — przypnij wersję i zaplanuj migracje,
  • Zespół w pełnym wymiarze, finansowany zewnętrznie, z zapowiedzią płatnych narzędzi dla firm w przyszłości,
  • Największym kosztem nie jest technologia, a kadry — i to trzeba powiedzieć klientowi wprost.

Licencja: Dioxus jest dostępny na MIT albo Apache-2.0, do wyboru — to standardowy układ podwójny przyjęty w ekosystemie Rusta i najbardziej wygodny z możliwych. Obie licencje są permisywne, więc wolno używać komercyjnie, modyfikować, wpinać w produkty zamknięte i redystrybuować; różnica sprowadza się do tego, że Apache-2.0 dokłada jawną licencję patentową od kontrybutorów wraz z klauzulą odwetową, a MIT jest krótszy i prostszy. Praktyczna zaleta wyboru polega na tym, że projekt nie narzuca nam ścieżki: przy audycie licencji u klienta wskazujemy ten wariant, który pasuje do jego polityki, i sprawa jest zamknięta. Nie ma tu open core, edycji korporacyjnej ani funkcji za paywallem — cały framework, narzędzie wiersza polecenia, warstwa serwerowa i komponenty są w tym samym repozytorium na tych samych warunkach. Warto natomiast pamiętać o dwóch rzeczach, które od licencji są niezależne. Po pierwsze, wkład do projektu jest domyślnie objęty tym samym podwójnym licencjonowaniem, o ile kontrybutor nie zastrzeże inaczej — o czym trzeba wiedzieć, wysyłając poprawkę w czasie pracy dla klienta. Po drugie i ważniejsze przy planowaniu budżetu: zespół rdzenia zapowiada dążenie do samowystarczalności przez płatne narzędzia dla firm. Dzisiaj nie ma żadnego elementu odpłatnego i licencja tego nie przewiduje, ale kierunek jest ogłoszony — a to sygnał, żeby przy wieloletnim wdrożeniu obserwować, gdzie w przyszłości przebiegnie granica między częścią darmową a płatną.