Szybkie spojrzenie na Electron JS. Tworzymy aplikację desktopową technologiami webowymi.

grafika

Cześć, programiści i programistki! Zastanawialiście się może kiedyś nad tym, jak by to było napisać aplikację desktopową, działającą zarówno na Linux, Windows, jak i MacOS, ale bez zagłębiania się w skomplikowane API i natywne rozwiązania? Czy myśleliście o tym, jak przyjemnie tworzy się i jak wyglądają dokumenty HTML oraz CSS poparte JavaScriptem? A może mieliście chęć zrobić ze swojej aplikacji webowej jej wersji desktopowej? Pomoże Wam w tym cross-platformowy framework open source: Electron JS.

Zobaczcie, jak to wszystko działa

Electron JS korzysta z takich technologii, jak HTML, CSS oraz JavaScript. Do tego wszystkiego dochodzą popularne moduły Node Package Manager (NPM). Za komunikację z systemem operacyjnym i back-end odpowiada Node.js, a wszystko renderowane jest za pomocą Chromium. Technologia nie wymaga od programistów znajomości żadnych narzędzi natywnych (choć, jak zawsze, są pewne wyjątki). Jakby tego było mało, Electron JS oferuje również obsługę automatycznych aktualizacji czy raportowania błędów. Sporo, prawda? Zatem przybliżmy sobie pokrótce części składowe Electrona.


Node.js to wieloplatformowe środowisko uruchomieniowe aplikacji back-endowych napisanych w języku JavaScript. W Electronie służy do tworzenia aplikacji client-side. W uproszczeniu: wszelką komunikację z systemem operacyjnym, obsługę plików itp. załatwiamy tu.

Node Package Manager pozwala nam zainstalować potrzebne moduły i pakiety funkcjonalności, a są ich setki: obsługa procesów, obsługa plików, moduły pomocne przy animacji, a nawet grafice 3D.

Chromium to projekt przeglądarki internetowej. Jest to silnik takich przeglądarek, jak Google Chrome, Opera czy Edge. W Electronie odpowiada za renderowanie aplikacji, które piszemy w HTML i CSS. Jest to miejsce na warstwę GUI naszego programu.

Skoro przy GUI jesteśmy. W Electronie nie musimy wcale ograniczać się do „gołego” HTML, CSS i JS. Nic nie stoi na przeszkodzie, aby zaprząc popularne frameworki front-endowe, np. Vue czy React.

Electron dba również o wspomniane automatyczne aktualizacje, raportowanie błędów, profilowanie, a co najważniejsze: pozwala zapomnieć o natywnych dla danego systemu oknach dialogowych, powiadomieniach i menu, przez co programista może skupić się na właściwym kontencie swojej aplikacji.

To nie tylko jakiś framework

Po pierwsze: nie wiem, czy wiecie, ale na Electronie zbudowane są popularne aplikacje, co potwierdza zasadność jego istnienia: Skype, Slack czy Discord (aplikacje do social networkingu), Tidal (aplikacja do streamingu muzyki), Visual Studio Code (edytor dla programistów,w którym to będziemy później pisać aplikację Electron – swoista Electronocepcja!). To tylko kilka przykładów, a więcej można zobaczyć tutaj: www.electronjs.org/apps. Po drugie: dla Electrona istnieje bogata dokumentacja, pomagająca wejść w jego świat. Quick Start, opis API, jak i również konkretne przykłady znajdują się tutaj: www.electronjs.org/docs/latest/. Community wokół pomaga przy tworzeniu aplikacji, a z racji tego, że Electron korzysta z webowych rozwiązań, internet jest pełen kursów i tutoriali HTML, CSS czy JavaScript.

“Hello World Electron”

Zrobimy w końcu to, po co tu przyszliśmy. Napiszemy teraz pierwszą prostą aplikację Electron. Na początek musimy choć trochę zrozumieć, jak wygląda architektura aplikacji. W przypadku Electrona mamy do czynienia z modelem wieloprocesowym. Oznacza to, że mamy jeden proces główny (main process) oraz możemy mieć kilka procesów renderujących (renderer processes). Proces główny odpowiada za logikę oraz core aplikacji i odpala procesy renderujące, które wyświetlają użytkownikowi warstwę HTML i CSS. Za komunikację między procesami odpowiadają moduły IPC (Inter-Process Communication). Ważne jest zatem, aby odpowiednio operować procesami. Ciężkie obliczenia rekomenduje się obsługiwać w procesie głównym, aby nie spowalniać aplikacji.

Kolejnym krokiem jest zapoznanie się ze strukturą aplikacji. Podstawowa aplikacja Electron zazwyczaj składa się z trzech plików: package.json (w którym konfigurujemy naszą aplikację – nazwę aplikacji, wersję, plik głównego skryptu aplikacji i tak dalej), main.js (czyli plik głównego skryptu, z którego startujemy okna HTML i obsługujemy logikę; może mieć inną nazwę) i index.html (czyli nasz HTML do warstwy GUI, który również może mieć inną nazwę).


Znając podstawy, przystąpimy do założenia projektu. Prerekwizyty, jakich będziemy potrzebować, to Node.js (do pobrania z: www.nodejs.org) i programistyczny edytor – w naszym przypadku będzie to Visual Studio Code (code.visualstudio.com).

Po zainstalowaniu potrzebnych narzędzi tworzymy folder na nasz projekt aplikacji Electron. Nazwijmy naszą aplikację Hello-Electron-App. W folderze otwieramy terminal i przystępujemy do inicjalizacji.

Polecenie npm init przeprowadzi nas przez wstępną inicjalizację projektu. Wypełniamy tam potrzebne dane (które zawsze później można zmienić i doprecyzować) i zatwierdzamy. Jeżeli wszystko zrobiliśmy dobrze, w naszym folderze powinien pojawić się plik package.json o mniej więcej takiej strukturze, jak w przykładzie poniżej.

Teraz pora na to, aby zainstalować potrzebne moduły i samego Electrona.

npm install –save-dev electron

Powyższe polecenie (wymaga połączenia z Internetem) zainstaluje framework i wszystkie potrzebne moduły, które zostaną umieszczone w folderze node_modules. Zanim uruchomimy pierwszy test musimy w pliku package.json, w sekcji scripts, dodać odpowiednią komendę start, aby uruchamiać Electrona.

{

  “scripts”: {

    “start”: “electron .”

  }

}

Teraz package.json powinien wyglądać, jak w przykładzie.

Czas na pierwsze uruchomienie aplikacji. W terminalu wpisujemy polecenie npm start, a naszym oczom powinno ukazać się okno.

Jak widzimy, Electron wystartował, ale nie udało nam się poprawnie uruchomić aplikacji. Jest to, oczywiście, wina tego, że nie mamy jeszcze żadnych plików aplikacji. Na ten moment mamy tylko package.json, konfigurację do dalszej pracy. Teraz zabierzemy się za main.js oraz index.html, czyli naszą właściwą aplikację.

Startowym punktem każdej Electronowej aplikacji jest skrypt główny. Skrypt ten kontroluje proces główny, cykl życia aplikacji, zarządza natywnymi interfejsami i wiele innych. Odpowiedzialny jest również za kontrolowanie procesów renderujących. W package.json sekcja main mówi o tym, który plik js jest skryptem głównym. W naszym przypadku, w zgodzie z logiką i utartymi schematami, będzie to plik main.js, choć nazwa jest całkowicie dowolna. Zaraz obok package.json tworzymy nowy pusty plik main.js.

Ponieważ Electron działa na Node.js, w pierwszej kolejności w main.js zaimportujemy moduły, pozwalające kontrolować nam podstawowe zdarzenia aplikacji oraz zarządzające oknami aplikacji.

const { app, BrowserWindow } = require(‘electron’)

Następnie dodamy funkcję createWindow(), która załaduje plik HTML (jego utworzymy za chwilkę) do naszej aplikacji.

function createWindow () {

  const window = new BrowserWindow({

    width: 1000,

    height: 400

  })

  window.loadFile(‘index.html’)

}

Zauważmy, że w funkcji tej możemy kontrolować to, jakie okno ma zostać wyświetlone. W naszym przypadku podajemy tylko wysokość i szerokość, ale z tego poziomu możemy również określić, czy okno ma mieć ramkę, czy ma wystartować zminimalizowane, czy w trybie fullscreen i wiele innych.

Kolejnym krokiem będzie pokazanie użytkownikowi tego okna. 

app.whenReady().then(() => {

  createWindow()

})

Okno może zostać pokazane użytkownikowi dopiero wtedy, kiedy Electron poprawnie wystartuje aplikację.

Obsłużymy jeszcze zamknięcie aplikacji – tak, aby zamknięcie okna zamknęło również całą aplikację (poniżej konkretny przypadek dla Windows i Linux).

app.on(‘window-all-closed’, function () {

  if (process.platform !== ‘darwin’) app.quit()

})

Main.js aktualnie powinien wyglądać teraz, jak w przykładzie.

Jeżeli uruchomimy teraz aplikację (npm start) otrzymamy prawidłowe, lecz puste okno aplikacji Electron (choć nie utworzyliśmy jeszcze index.html).

Co ciekawe, jeżeli otworzymy View > Toggle Developer Tools, ujrzymy znane z Google Chrome narzędzia programisty. Tak, to okno to uproszczona przeglądarka.
Dobrze, wrzucimy teraz jakąś zawartość. Obok naszych plików package.json oraz main.js tworzymy index.html – wypełnijmy plik prostym HTMLem.

<!DOCTYPE html>

<html>

  <head>

    <meta charset=“UTF-8”>

    <title>Hello ELECTRON!</title>

  </head>

  <body>

    <h1>Hello ELECTRON!</h1>

    To moja pierwsza, desktopowa aplikacja Electron.

  </body>

</html>

I wystartujmy naszą aplikację.

Brawo! Właśnie utworzyliśmy pierwszą aplikację desktopową przy użyciu technologii webowych! “Hello ELECTRON!” mamy za sobą.

Wiem, że chcecie więcej!

Rozszerzymy teraz naszą aplikację o dodatkową stronę/ekran, na którym będziemy monitorować aktualne użycie procesora. Dodamy również trochę koloru przez arkusz styli CSS. Zrozumiecie w ten sposób podstawową komunikację między procesem głównym a procesem renderującym. Dodatkowo użyjemy modułu Node.js o nazwie os-utils, dzięki któremu możemy zobaczyć informacje o naszym systemie.


Zacznijmy od zainstalowania potrzebnego modułu.

npm install os-utils

Przygotujemy teraz kolejny plik HTML (pod ekran monitorowania użycia procesora). Nazwijmy go cpuMonitor.html. Od razu, za ciosem stworzymy arkusz styli: cpuMonitorStyle.css. Zostawmy je na ten moment puste.

Pora na komunikację między procesami, wspomniany wcześniej Inter-Process Communication (IPC). W naszym przypadkuw głównym procesie, a za razem w głównym pliku main.js, utworzymy funkcję, która co sekundę będzie wysyłać informację o użyciu procesora (dzięki modułowi os-utils). Proces renderujący, a konkretnie nasz cpuMonitor.html, tę informację wykorzysta i wyświetli w odpowiednim miejscu na ekranie.

Aby możliwa była taka komunikacja, musimy przebudować trochę nasze okno aplikacji w pliku main.js.

let window = null

function createWindow () {

    window = new BrowserWindow({

      width: 1000,

      height: 400,

      webPreferences: {

        nodeIntegration: true,

        contextIsolation: false

      }

    })

    window.loadFile(‘index.html’)

  }

Po pierwsze: window jest teraz zmienną, widoczną poza funkcją createWindow() (dzięki temu będziemy mogli słać komunikaty), a po drugie: „włączyliśmy” integrację z modułami Node.js.

Nasza interwałowa funkcja korzystać ma z modułu os-utils, zatem zaimportujmy go do main.js.

const os = require(‘os-utils’)

Sama funkcja ma postać, jak poniżej.

  setInterval(() => {

    os.cpuUsage(function(v){

      window.webContents.send(‘cpu’,v*100)

    })

},1000)

Korzystamy tu z modułu os-utils, konkretnie z cpuUsage(). Ślemy do naszego okna (window) co sekundę informację o procencie wykorzystania procesora. Zanim tę informację „odczytamy” w pliku cpuMonitor.html, podsumujmy to jak aktualnie wygląda main.js.

Naszym startowym ekranem jest index.html. My chcemy, aby ekran cpuMonitor.html odczytywał informacje o użyciu procesora. Przełączanie między ekranami załatwimy po prostu znacznikiem <a>. W index.html będzie to poniższy zapis.

<a href=“cpuMonitor.html”>Zobacz aktualne użycie procesora</a>

Z ekranu cpuMonitor.html powrotem do ekranu głównego zapis będzie wyglądał, jak niżej.

<a href=“index.html”>powrót</a>

Kolejny krok to odczyt informacji wysłanej z głównego procesu. W pliku cpuMonitor.html umieszczamy poniższy skrypt.

 <script>

    const electron = require(‘electron’)

    const ipcRenderer = electron.ipcRenderer

    ipcRenderer.on(‘cpu’, (event, data) => {

      document.getElementById(‘cpu’).innerHTML = data.toFixed(2)

    })

  </script>

Na początek musimy „podpiąc” Electrona oraz ipcRenderer (to nasz „słuchacz” komunikatów). ipcRenderer, otrzymawszy informację, (zdarzenie/event) wypełni element HTML o id równemu cpu wartością otrzymaną z procesu głównego (w naszym przypadku procentem użycia procesora). Będzie działo się to za każdym razem, kiedy proces główny „odpali” event (czyli co sekundę).

Tak wygląda sam HTML.

<div class=“box”>

  <span class=“label”>Użycie CPU wynosi %:</span>

  <span id=“cpu”></span>

  <a href=“index.html”>powrót</a>

</div>

Element <span id=”cpu”>-</span> domyślnie przyjmuje „-”. W tym miejscu pojawi się procentowe użycie procesora. Wszystko zamykamy w odpowiednie znaczniki, aby je wystylizować w arkuszu cpuMonitorStyle.css. Zanim przejdziemy do stylizacji, oto, jak wygląda cpuMonitor.html.

Na koniec szybka stylizacja (plik cpuMonitorStyle.css).

Uruchamiamy naszą aplikację (npm start). Ekran użycia procesora powinien wyglądać następująco.

Kolejne brawa! Udało nam się zbudować coś więcej w Electron JS!

To dopiero początek przygody

Znamy już podstawy, teraz czas na własną eksplorację. Jeżeli zainteresował Was Electron JS, ruszajcie dalej: napiszcie więcej funkcjonalności, wystylizujcie po swojemu, a na koniec zbudujcie aplikację do pliku *.exe, aby każdy mógł ją zobaczyć.