Vite, SvelteKit, TypeScript i Rust, czyli Tauri w akcji
Vite, SvelteKit, TypeScript i Rust, czyli Tauri w akcji
Tworzenie aplikacji wieloplatformowych w technologiach webowych nie jest czymś nowym. Jednocześnie to podejście charakteryzuje niższa wydajność oraz elastyczność, w porównaniu do natywnego podejścia. Z czasem jednak, granica pomiędzy jednym a drugim, zaczyna się zacierać. Obecnie na rynku istnieje kilka sprawdzonych rozwiązań (np. React Native czy Electron).
Tym razem skupimy się na Tauri, który osobiście utożsamiam z najlepszym Developer Experience z jakim miałem do czynienia. Tym bardziej, że do stworzenia swojej pierwszej aplikacji, wykorzystałem także Vite oraz SvelteKit.
Po co mi aplikacja desktopowa?
Obecnie technologie webowe i możliwości przeglądarek są tak rozwinięte, że nawet złożone aplikacje nie wychodzą poza okno Arc czy Chrome. Pytanie więc po co mi aplikacja, która wykracza ponad to? W moim przypadku odpowiedzią są globalne skróty klawiszowe. W praktyce chcę aby moja aplikacja działała "w tle". Chciałem aby po wciśnięciu określonych przycisków pojawiało się okno, lub uruchamiała się konkretna akcja. W przeglądarce nie jest to możliwe.
Do tej pory moje doświadczenie z aplikacjami nie-webowymi, było bardzo ograniczone. Zrealizowałem pojedyncze, proste projekty aplikacji mobilnych i kilka prywatnych na desktop. Na pewnym etapie nawet zacząłem tworzyć interfejsy z pomocą Keyboard Maestro (tak powstał projekt https://www.designmaestro.io).
Jednak teraz w związku z moim zainteresowaniem Rust oraz Svelte, chciałem sprawdzić jak w praktyce wypadnie Tauri, który także jest na moim radarze od pewnego czasu.
I tak wystarczyło kilka godzin, aby moja aplikacja nie tylko zaczęła działać, ale nawet posiadała wszystkie wymagane przeze mnie funkcje!
Szybki przegląd Tauri
Tauri w skrócie umożliwia tworzenie aplikacji desktopowych (i mobilnych, aktualnie w wersji Alpha) poprzez połączenie JavaScriptu z Rust.
Mówimy tutaj o stosunkowo nowe rozwiązanie (3 lata) i jest to bardzo zauważalne (negatywnie). Brakuje materiałów do nauki, kursów i obszernych artykułów. Na szczęście społeczność na Discordzie jest niezwykle pomocna i przepełniona gotowymi rozwiązaniami często spotykanych problemów.
Według State of JS 2022, jego rozpoznawalność jest na poziomie 32%. Z drugiej strony jest na górze listy "Want to Learn" oraz "Would use Again", co dobrze zwiastuje na przyszłość.
Pomimo tego, że Tauri adresuje ten sam problem co Electron czy React Native, robi to w zupełnie inny sposób. W przeciwieństwie do React Native, pozwala pracować z dowolnym frameworkiem. Jednocześnie nie robi tego tak jak Electron. Zamiast pracować na Chromium i Node.js, wykorzystuje wbudowany w większość systemów "webview". W rezultacie rozmiar aplikacji może być mniejszy niż 600KB (a w moim przypadku ~3MB). Dla porównania "hello world" w Electronie to ~45-50MB).
Kolejną zaletą (i jednocześnie barierą) jest Rust. Nie mogę powiedzieć, ze go doskonale znam. Pomimo tego, jego obecność na tym etapie w zupełności mi nie przeszkadzała. Wręcz przeciwnie, pokazała mi wiele zalet i jeszcze bardziej zachęciła do dalszej nauki. Podłączenie pluginu czy nawet napisanie własnej akcji (tzw. Command) odbyło się dość łatwo. Szczególnie, że na wspomnianym Discordzie, trafiłem na wątek poruszający dokładnie problem, który rozwiązywałem (możliwość wyświetlenia okna z przeźroczystym tłem i zaokrąglonymi rogami).
No i ostatnią, najważniejszą dla mnie zaletą Tauri, jest fakt, że mogłem skorzystać z ulubionego frameworka. W moim przypadku jest to Svelte a konkretnie SvelteKit (Svelte + Vite).
Pierwsze minuty
Sięgając po Tauri, spodziewałem się konieczności przejścia przez złożone konfiguracje i związane z nią problemy na etapie budowania aplikacji. Chwilę potem zrozumiałem, że byłem w błędzie.
Po instalacji Rust z pomocą Rustup, wystarczyło uruchomić polecenie pnpm tauri init, zainstalować zależności (pnpm install) i włączyć wersję dev (pnpm tauri dev). Efekt:
To działająca aplikacja, z natychmiastowym odświeżaniem po wprowadzeniu zmian i dostępem do konsoli developerskiej (prawy przycisk -> zbadaj element). Dokładnie jak w przypadku aplikacji webowej, z pomocą HTML/CSS/JS możemy rozwijać interfejs.
Mając to, w pierwszej kolejności chciałem zweryfikować czy Tauri umożliwi mi zrealizowanie najważniejszych założeń mojego projektu. Zawsze gdy korzystam z nowych narzędzi, upewniam się nawet w minimalnym stopniu, czy dadzą mi to, czego potrzebuję. Na tym etapie nie interesuje mnie jakość tworzonego kodu czy obsługa błędów. Moim celem jest wyłącznie eksploracja oraz sprawdzenie gdzie leżą granice dostępnych rozwiązań.
W moim przypadku były to:
- globalne skróty klawiszowe
- dostęp do schowka
- minimalistyczny design
- możliwość uruchamiania AppleScript
- możliwość uruchamiania skryptów Shell
Niemal natychmiast stało się jasne, że konieczne jest wykorzystanie Rust. Interesujące jednak okazało się to, jak wygląda jego połączenie z JS.
Rust i JavaScript
UI powstaje w JavaScript ale samą aplikacją zarządza Rust. Zatem logiczne wydaje się więc tworzenie akcji w Rust i sięganie po nie w JS. Schemat taki sam jak w przypadku Front-endu i Back-endu. Z jednej strony faktycznie tak jest. Mamy do dyspozycji polecenia (Command) umożliwiające dwustronną komunikację i zdarzenia (Event) uruchamiane poprzez określony trigger.
W praktyce jednak wiele funkcji Rust, dostępna jest natychmiast z poziomu JavaScriptu. Przykładowo tak wygląda dostęp do schowka:
To kod znajdujący się w pliku .ts a pomimo tego ma dostęp do akcji pochodzącej z API Tauri. Jednak sama ta funkcja nie wystarczy aby uzyskać dostęp do schowka. Konieczne jest także dodanie odpowiednich ustawień do pliku konfiguracyjnego. Tak wygląda przykład z dokumentacji:
Dokładnie w ten sposób odbywa się odblokowanie funkcji niezbędnych dla naszej aplikacji. Wszystkie pozostałe nie są dostępne i tym samym zwiększają bezpieczeństwo tworzonego przez nas oprogramowania. Lista dostępnych API wpływa także na rozmiar końcowy aplikacji. Sterowanie tymi ustawieniami poprzez jeden plik JSON jest bardzo wygodne i praktyczne.
Na pewnym etapie dochodzimy jednak do sytuacji w której konieczne jest stworzenie własnej logiki realizowanej po stronie Rust. Wówczas znajomość tego języka okazuje się niezbędna, aczkolwiek sam wspomagam się Github Copilotem oraz ChatGPT.
Domyślnie w pliku main.rs znajdziemy taką komendę:
Zanim skorzystamy z niej po stronie JavaScriptu, konieczne jest zarejstrowanie:
A samo jej uruchomienie sprowadza się do jednej linii:
W ten sposób możemy nie tylko przesyłać informacje do Rust ale także je odbierać, ponieważ invoke
zwraca obiekt Promise.
Pozostałe elementy dotyczące rozwoju interfejsu i Svelte, wyglądają bardzo podobnie jak w tworzeniu aplikacji webowej. Różnicą jest obecność API Tauri oraz fakt, że rezultat końcowy widzimy bezpośrednio w oknie naszej aplikacji … lub przeglądarce! W ten sposób możemy weryfikować to w którym środowisku aktualnie się znajdujemy i dostosowywać zachowanie aplikacji.
Rezultaty
Wystarczyło mi kilka godzin aby przygotować aplikację, która:
- posiada UI idealnie odwzorowujący projekt z Figmy
- uruchamia się po wciśnięciu globalnego skrótu klawiszowego
- łączy się z zewnętrznym API
- obsługuje wewnętrzne i globalne skróty klawiszowe
- ma dostęp do schowka i pojedynczych poleceń AppleScript
- zapisuje dane użytkownika lokalnie (Plugin-Store)
- waży 5MB i działa fenomenalnie szybko
Proces budowania aplikacji, podobnie jak cała reszta, sprowadził się do jednego polecenia: pnpm tauri build. W tym momencie aplikacja znajduje się jeszcze na etapie testów (wersje Intel i Apple Silicon, wersji na Windows/Linux raczej nie będzie).
Po drodze spotkałem wyłącznie bardzo uzasadnione problemy, wynikające bezpośrednio z moich błędów. Sam framework wydaje się działać bezproblemowo a przynajmniej na tym etapie żadnego nie doświadczyłem (mowa o platformie MacOS).
Myślę, że z powodzeniem mogę polecić spędzenie przynajmniej godziny z Tauri, przejście przez proces instalacji oraz uruchomienie pierwszej aplikacji. Jeżeli pracujesz jako full-stack, zapewne będzie to dla Ciebie bardzo pozytywne doświadczenie.
Trudno jest mi się wypowiedzieć z perspektywy osoby, która programuje aplikacje natywne. Domyślam się, że w takiej sytuacji, Tauri może być interesującą ciekawostką lub rozwiązaniem dla konkretnego rodzaju projektów. Wątpię też aby w pełni był w stanie zastąpić całkowicie natywne rozwiązania.
Najlepszy DX jaki widziałem
Powyższe przykłady podkreślają widocznie kilka elementów Rust, które decydują o tym, że kojarzę go z najlepszymi programistycznymi doświadczeniami, z jakimi miałem do czynienia.
Mam tutaj na myśli w szczególności:
- szybkie uruchomienie projektu
- świetna konfiguracja (wiele funkcji dostępnych od razu)
- ogromna elastyczność (dowolny framework JS)
- bezpieczeństwo i wydajność (konfiguracja w pliku JSON)
- API dostępne w JS
- Rust na back-endzie
- Łatwe budowanie i uproszczony projekt publikacji
Pomimo bardzo ograniczonego doświadczenia w zakresie tworzenia aplikacji wieloplatformowych, w Tauri czułem się jak w domu. Wszystko jest niezwykle intuicyjne i przemyślane. Nawet trudno znaleźć mi jakiekolwiek minusy. Dla mnie bez wątpienia największym problemem był bardzo ograniczony dostęp do materiałów. Pomimo świetnej dokumentacji, zabrakło mi bardziej konkretnych przykładów wykorzystania bezpośrednio w aplikacjach.
Poza tym moje doświadczenie z Tauri jest jeszcze niewystarczające aby w pełni zarysować obraz tego frameworka. Chętnie przekonam się jak sprawdzi się w aplikacjach działających na nieco większej skali. Do tego czasu, z pewnością stworzę w nim jeszcze kilka narzędzi, na własne potrzeby. Tym bardziej, że już teraz możliwe jest generowanie aplikacji mobilnych (nie miałem jeszcze okazji tego sprawdzić). Biorąc pod uwagę tempo rozwoju Tauri, wiele sugeruje, że mamy do czynienia z poważnym graczem w swojej kategorii.