DeskGap, czyli lżejszy Electron

Ekosystem front-endu zaskakuje praktycznie każdego dnia. Coś, co kiedyś było przeznaczone tylko do tworzenia aplikacji webowych, dziś może być z powodzeniem wykorzystywane także do projektów desktopowych. Przywitajmy DeskGap!

Pamiętam jakie wrażenie wywarła na mnie informacja o tym, że istnieje możliwość tworzenia aplikacji mobilnych za pomocą JavaScriptu. Praktycznie rozpoczynałem wtedy swoją przygodę z pracą w IT i jako Web developer byłem skupiony wyłącznie na przeglądarkach. Aż tu nagle ktoś wymyślił, że można przecież taką „przeglądarkę” opakować w aplikację natywną i gotowe! Mowa oczywiście o Phonegapie, z którym mam zarówno miłe wspomnienia, jak i koszmarne flashbacki gdy trzeba było doprowadzić taką apkę do użyteczności np. na Androidzie 2.3. Tak czy owak była to na swój sposób rewolucja, ponieważ Phonegap znalazł sposób na tworzenie multiplatformowych aplikacji mobilnych z użyciem technologii webowych.

Obecnie nie jest to jedyna możliwość, bo przecież mamy jeszcze coś takiego jak React Native, czy NativeScript. Nawet niektóre systemy operacyjne traktują HTML5 jako platformę tej samej rangi co języki natywne. Mam tu na myśli Tizena, WebOSa i nieco już podstarzałego Blackberry 10. Mam nadzieję, że zauważyliście jak płynnie przeszedłem z mobilek na telewizory. Front-end ma te środowiska już ogarnięte, ale co z tradycyjnymi aplikacjami desktopowymi?



Czym jest DeskGap?

Jeżeli mowa o środowisku desktopowym, niezależnie czy to Windows, MacOS czy Linux, tutaj króluje dość dobrze przyjęty przez spłeczność Electron. W dużym skrócie działa on na podobnej zasadzie co PhoneGap dla mobilek, ale byłoby to ogromne niedopowiedzenie, dlatego odsyłam zainteresowanych do tego wpisu. Obok Electrona, przebija się również ciekawa idea Microsoftu, aby wykorzystać React Native do tworzenia apek w ekosystemie Windows. Projekt ten zaczyna zdobywać coraz większą popularność na githubie, więc możliwe, że lada moment stanie się ważnym graczem na rynku aplikacji desktopowych.

Jakiś czas temu pojawiła się jeszcze jedna alternatywa do tworzenia multiplatformowych aplikacji na desktop. Jest nią łudząco podobny z nazwy do PhoneGapa, projekt DeskGap. I nie tylko nazewnictwo upodabnia ten framework (bo tak go określają twórcy) do PhoneGapa.

DeskGap umożliwia budowanie multiplatformowych aplikacji desktopowych z użyciem dobrze znanych front-endowcom technologii takich jak JavaScript, HTML i CSS. To co przede wszystkim odróżnia go od Electrona, to bazowanie na domyślnym silniku renderującym w systemie operacyjnym. Wynikowa paczka jest zatem mniejsza, ponieważ nie zawiera np. silnika Chromium jak to ma miejsce w Electronie. Na tym polu DeskGap także ma konkurencje, np. w postaci electrino, który zastosował identyczne podejście do tematu. Twórcy DeskGapa postanowili jednak nieco się wyróżnić i wykorzystać zalety Node.js poprzez dołączenie go do swojego frameworka. Dzięki temu zabiegowi możliwości potencjalnych aplikacji utworzonych z pomocą DeskGapa znacząco wzrosły.

Szybki start

Pora przejść do praktyki. Aby pokazać jak wykorzystać DeskGapa do tworzenia aplikacji desktopowych, zrobię prostą aplikację losującą prawa Murphy’ego. Ot coś innego niż tylko suchy „Hello world!”, choć wciąż nic skomplikowanego. Do tego zobaczymy czy DeskGap polubi się z Reactem.

Wygenerujmy więc za pomocą CRA prostą aplikację Reactową:

npx create-react-app deskgap-murphy

Nazwałem tą aplikację niezbyt oryginalnie, bo deskgap-murphy, ale przynajmniej wiadomo o co chodzi. Pora na główną funkcjonalność, czyli losowanie cytatów. W tym celu zmodyfikujemy App.js i dodamy przycisk do losowania:

const App = () => (
  <div className="App">
    <header className="App-header">
      <img src={logo} className="App-logo" alt="logo" />
      <p>
        Deskgap-murphy - Murphy, React i DeskGap! 
      </p>
      <div className="App-quotes"></div>
      <button className="App-button">
        Losuj cytat!
      </button>
    </header>
  </div>
);

W stosunku do boilerplate’a, który wygenerował create-react-app, zmian jest niewiele. To co nas będzie najbardziej interesowało to przycisk „Losuj cytat!” i kontener o klasie App-quotes do którego trafi wylosowany tekst. Umieszczę moje ulubione cytaty Murphy’ego dotyczące programowania w tablicy quotes:

const quotes = [
  'Dla komputera nie ma rzeczy nie do pomyślenia, a tym bardziej nie ma rzeczy niemożliwych – z wyjątkiem tych, których od niego wymagamy.',
  'Systemy odporne na idiotów obsługiwane są właśnie przez idiotów.',
  'Stare systemy produkują stare błędy.',
  'Nowe systemy produkują tak nowe, jak i stare błędy.',
  'Wszystkie komputery PC są kompatybilne, ale niektóre są kompatybilniejsze od innych.',
  'Wszystko ulega rozkładowi w najmniej odpowiednim momencie.',
  'Rozłożenie dowolnego urządzenia na części jest proste. Ponowne jego złożenie tak, żeby działało, nie jest możliwe.',
  'Systemy złożone wykazują skłonność do popełniania kompleksowych błędów. Systemy proste wykazują zaś skłonność do popełniania elementarnych błędów.',
  'Klientowi nigdy nie przyjdzie na myśl, ile kosztuje projekt, tylko ile można na tym projekcie zaoszczędzić.',
  'Żaden klient nie wie, czego właściwie chce.',
  'Każdy klient wie dokładnie, czego nie chce.',
  'Klient, który najmniej płaci, marudzi najwięcej.',
  'Każdy program w Windows pracuje poprawnie do momentu niczym nieuzasadnionej utraty danych.',
  'Prawo programistów: Jeśli w danym produkcie wszystko działa poprawnie, to znaczy, iż nie jest on wystarczająco zaawansowany technologicznie.',
  'Nie wierz w cuda – zdaj się na nie.',
  'Każdy programista przybywający z innego miasta jest fachowcem.',
  'To, co wygląda na niemożliwe, potrafi rozwiązać nawet twoja teściowa i to bez pomocy komputera.',
  'Program, który zaczyna się źle, kończy się przerażająco.',
  'Liczba osób w zespole programistycznym ma tendencje wzrastające niezależnie od ilości pracy.',
  'Program oddany użytkownikowi w piątek wraca do autora w poniedziałek.',
];

Wybrane cytaty pochodzą z wikiquotes. Pora na funkcję losującą:

const randomQuote = (quotes = []) => {
  return quotes[Math.floor(Math.random() * quotes.length)];
};

Nic skomplikowanego. Po prostu losujemy liczbę od 1 do maksymalnie długości przekazanej tablicy. Teraz wystarczy tylko obsłużyć kliknięcie w przycisk i wylosowanie początkowego tekstu jako wartości domyślnej. Jak wiemy React reaguje rerenderem na zmianę propsów lub stanu. Nie chce się rozdrabniać na inne komponenty (bo to tylko przykład) więc muszę postawić na stan. Skorzystam więc z hooka useState i przekażę do niego wynik randomQuotes jako wartość domyślną:

const [quote, setQuote] = useState(randomQuote(quotes));

Zmienna quote będzie przechowywała wylosowany cytat, a funkcja setQuote posłuży nam do jej zmiany. Zbierając wszystko do kupy, nasz plik App.js powinien wyglądać mniej więcej tak:

import React, { useState } from 'react';
import logo from './logo.svg';
import './App.css';

const quotes = [ ... ];

const randomQuote = (quotes = []) => {
  return quotes[Math.floor(Math.random() * quotes.length)];
};

const App = () => {
  const [quote, setQuote] = useState(randomQuote(quotes));

  const getQuote = () => {
    setQuote(randomQuote(quotes));
  };
  
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Deskgap-murphy - Murphy, React i DeskGap! 
        </p>
        <div className="App-quotes">
          {quote}
        </div>
        <button className="App-button" onClick={getQuote}>
          Losuj cytat!
        </button>
      </header>
    </div>
  );
};

export default App;

Celowo ukryłem na listingu tablicę quotes żeby nie rozwlekać kodu, a samą tablicę przedstawiłem wcześniej. Pora na doinstalowanie do projektu DeskGapa:

yarn add deskgap

Teraz potrzebujemy pliku, który stanie się punktem wejścia dla DeskGapa i stworzy główne okno aplikacji. W katalogu src tworzę więc plik deskgap.js i umieszam w nim następujące instrukcje:

const { app, BrowserWindow } = require('deskgap');

let mainWindow;

app.once('ready', () => {
  mainWindow = new BrowserWindow({
    show: false,
    title: 'DeskGap Murphy',
    width: 800,
    height: 600,
  }).once('ready-to-show', () => {
    mainWindow.show();
  });

  mainWindow.loadURL("http://localhost:3000");

  mainWindow.on('closed', () => {
    mainWindow = null;
  });
});

Najważniejszym elementem w powyższym kodzie jest obiekt app, który zarządza stanem całej aplikacji. Gdy nastąpi zdarzenie ready tworzę nowe okno przeglądarki z określonymi wielkościami, tytułem i informacją, że należy je pokazać dopiero wtedy gdy nastąpi zdarzenie ready-to-show. Jest to konieczne żebyśmy nie musieli oglądać procesu wczytywania strony. Następnie z pomocą metody loadURL wczytuję localhosta na porcie 3000, bo jest to domyślna wartość portu gdy mamy odpaloną apkę z użyciem CRA.

Została nam już tylko kwestia uruchomienia samej aplikacji na desktopie. W tym celu musimy dodać do pliku package.json punkt startowy dla DeskGapa:

"main": "src/deskgap.js"

oraz polecenie, które go wywoła:

"scripts": {
  ...
  "deskgap": "deskgap ."
},

I to tak na prawdę wszystko. Teraz wystarczy w konsoli wpisać yarn deskgap i aplikacja będzie prezentowała się następująco:

Aplikacja deskgap-murphy

Oczywiście musimy mieć w tle uruchomionego yarn start dla Reacta. Aby tego nie musieć robić należałoby wybudować wersję binarną dla danego systemu operacyjnego. Ten proces jest już nieco bardziej złożony i chciałbym na to poświęcić osobny wpis. Cały projekt wrzuciłem na repozytorium.

Osoby, które pisały już w Electronie na pewno zobaczą podobieństwo instrukcji. Nawet strona główna DeskGapa odsyła do dokumentacji Electrona. I tu zaczynają się pierwsze schody…

Wady DeskGapa

Choć DeskGap sam w sobie jest ciekawym projektem to niestety cierpi na kilka bolączek, które mogą przekreślić jego wybór w profesjonalnych, komercyjnych zastosowaniach. Część z nich spowodowana jest brakiem dojrzałości tej biblioteki, a część posiada podobną genezę jak w przypadku problemów PhoneGapa.

Brak dokumentacji

DeskGap posiada na swojej stronie opis API, ale jest to głównie zbiór linków kierujących do Electrona. Zamysłem twórców było pewnie aby programiści Electrona mogli łatwo przeskoczyć do ich projektu, ale nie zmienia to faktu, że dokumentacja praktycznie nie istnieje. Jej brak może okazać się najpoważniejszą wadą DeskGapa, ponieważ nikt na poważnie się nim nie zainteresuje jeżeli wsparcie leży i kwiczy. Nie pozostaje póki co nic innego poza analizowaniem dostępnych linków i przytuleniem issues na githubie, gdzie z pewnością przewinie się sporo pytań.

Różne systemy, różne błędy

Jeżeli będziemy chcieli wspierać szeroką gamę systemów, w tym np. Windowsa w wersji 7, to musimy się liczyć z tym, że domyślnym silnikiem renderującym w siódemce jest Trident. A to oznacza zabawę z IE11 i raczej zapomnienie o najnowszych ficzerach front-endowych. Na Windows 10 dostaniemy Edge więc jest lepiej, a w MacOS będzie to Webkit więc już zdecydowanie jest dobrze. Trzeba jednak pamiętać, że każdy z tych silników ma swoje błędy, jedne większe, inne mniejsze. Tworząc apkę multiplatformową będziemy musieli zabezpieczyć się polyfillami i tworzyć nasze funkcjonalności z głową, nie przeginając także z interfejsem graficznym.

Ekosystem dopiero raczkuje

Jakiś poradnik jak podpiąć jakieś zaawansowane biblioteki do DeskGapa? Zapomnijcie, przynajmniej na razie. Na odpowiedzi na Wasze problemy na StackOverflow również możecie sporo poczekać. Sami twórcy opisują dostępne API jako dość ograniczone w stosunku do Electrona. Nie zmienia to jednak faktu, że nie można eksperymentować i w ten sposób zostać pionierem w tworzeniu złożonych aplikacji w DeskGapie. W końcu ktoś tym guru być musi 😉

Podsumowanie

DeskGap to ciekawa alternatywa dla Electrona jeżeli chodzi o tworzenie aplikacji desktopowych. Dzięki dołączeniu Node.js do projektu, programiści mogą korzystać z dobrodziejstw wielu modułów oferowanych przez to środowisko.

Niestety DeskGap jest jeszcze na wczesnej fazie rozwoju i bez wzięcia tego pod uwagę można się wpakować na niezłą mine. Moim zdaniem jest to interesujący wybór jeżeli celujemy tylko w konkretny system operacyjny, znamy bolączki silnika przeglądarki w tym systemie i nie interesuje nas uczenie się języków natywnych. Uzyskamy w ten sposób lżejszą niż w Electronie, skrojoną dla wybranego systemu aplikację. W innym przypadku development może się mocno wydłużyć i szybciej napiszemy aplikacje w Electronie.

Ale nawet przeglądarka Edge migruje na silnik Chrome’a, więc kto wie. Może pewnego dnia będzie to bardziej efektywne rozwiązanie i znajdzie swoje miejsce na rynku.