Electron a Vite.js s React.js

Electron a Vite.js s React.js

V nedávné době jsem začal dělat na relativně jednoduché aplikaci v Electronu. Jelikož jsem zvyklý pracovat s JavaScriptovými frameworky Vue.js a React.js, přemýšlel jsem, zdali bych je mohl využít i pro tuto aplikaci - samozřejmě, že mohl. Rozhodl jsem se tedy pro Vue.js. Chvíli jsem Googlil, jaké způsoby propjení se nejčastěji používají. Většinou jsem skončil u nějakého již hotového CLI. Rozhodl jsem se tedy, že zkusím Vue CLI plugin Electron Builder. Jak název napovídá jedná se o rozšíření pro Vue CLI a implementuje krom Electronu také Electron Builder, což je balíček, který následně zásadním způsobem zjednoduše vytvoření buildu s hotovou aplikací.

Začal jsem tedy vyvíjet aplikaci, zkusil jsem i několik první buildů. Z počátku se jevilo vše bez problémů, ty přibývali až s tím, jak se rozšiřoval kód aplikace. Tedy snad krom toho, že ani první build na macOS mi nefungoval, ukázalo se ovšem, že se jedná o problém Archiver aplikace, která po rozbalení zipu neobnoví symliky, lze to tedy obejít jiným softwarem pro práci s archivy. Mimo to Apple o chybě ví již od macOS Catalina. U macOS ještě zůstanu, zkoušel jsem také build pro nové MacBooky s procesory Apple Silicon, nejprve se zdálo, že Electron Builder tyto procesory nerozeznává, nicméně po kontrole v Activity Monitoru jsem zjistil, že některé buildy jsou označené jako Apple a jiné Intel. Došlo mi, že buildy se tedy vytváří podle správné architektury, ale záleží, který se vytvoří první - jestli ARM nebo Intel, protože ten druhý ho přepíše. Takových hloupých drobností se vynořila ještě řada, mimo to třeba nešlo vytvořit .dmg ani .pkg. Kamarád mi tedy doporučuje electron-packager. Jenže nahradit builder v téhle CLI utilitě není otázka jednoho npm (Node Package Manager) příkazu. Všechny problémy jsem nakonec vyřešil a buildy mi fungují tak, jak potřebuji, nicméně vedlo mne to k otázce, co kdybych si to řešil celé sám?

Právě tomu bych se chtěl věnovat v tomto článku. Narozdíl od Vue.js jsem se ale rozhodl pro React.js. Rozhodl jsem se také tradiční Webpack nahradit Vite a zkusit, jestli tyhle nástroje půjde kombinovat. Ponechme stranou, že zatímco u Electron bundleru získám uričtou svobodu nad konfigurací, tak s Vite ji zase část obětuji. Nicméně, kombinace těhle nástroju je nakonec jednodušší, než bych čekal. Samozřejmě má svá uskalí a rozhodně není tak pohodlná jak hotové CLI nástroje, tedy pokud se nerozhodnete napsat něco podobného sami. To ovšem součástí tohoto článku nebude.

Vite a React.js

Začneme instalací Vite, jak už jsem říkal jedná se o svým způsobem obdobu Webpacku. Stejně jako Webpack nabízí dev server i nástroj pro zbuildění kódu aplikace. Nicméně, na rozdíl od Webpacku pracuje rovnou s ES (ECMAScript) moduly a nekompiluje je do CommonJS, dále také Express.js web server nahradil minimalistický Koa server. Hot module reloading je také mnohonásobně rychlejší, de facto instantní, na druhou stranu umožňuje použití pouze ES modulů a nikoliv CommonJS nebo AMD. Build kódu provádí v předkonfigurovaném Rollupu, kdežto Webpack tohle řeší sám. Jakousi pomyslnou výhodou je také nutnost minimální konfigurace celého projektu, tedy pokud se rozhodnete použít některý z předdefinovaných presetů, například React nebo Vue.

1npm init @vitejs/app my-react-app

Během tvorby projektu se Vite zeptá na volbu šablony, zvolíme tedy react. Následně se zobrazí výzva s několika příkazy, které bychom měli spustit, aby se vše nainstalovalo, tedy:

1cd my-react-app
2npm install
3npm run dev

Pokud jsme spustili minimálně ty první dva, Vite rozhraní a React.js applikace jsou připravné. Zbývá tedy finální příkaz, a to spustit dev server, abychom ověřili, že vše funguje. V příkazovém řádku by se nám měla zobrazit informace, že aplikace běží na http://localhost:3000 případně nějakém jiném portu, pokud je 3000 již zabraný. Můžeme se tedy pustit do instalace Electronu,

Electron

Je framework pro tvorbu cross-platform desktopových aplikací s pomocí JavaScriptu, CSS a HTML. Díky Electronu tak lze vytvořit nativní aplikace a ty nejtěžší věci jako updatování, nativní menu a notifikace nebo crash reporting za nás řeší právě Electron. Instalace je velice jednoduchá, stačí spustit následující příkaz:

1npm install --save-dev electron

Teď už jen zbývá upravit soubor package.json a přidat tzv. entry point pro Electron aplikaci, nejčastěji zvaný main.js. Nicméně v tuto chvíli přichází na řadu první háček, Electron podporuje pouze CommonJS, zatímco Vite jen ES. Jednou možností je smířit se s tím, že části pro Electron budou v CommonJS a části pro Vite/React v ES, to ale trochu znesnadní možnost sdílení kódu. Jelikož Vite preferuje ES je jednodušší kód pro Electron kompilovat z ES do CommonJS. Náš entry point bude tedy vypadat následovně:

main.js

1import { app, BrowserWindow } from 'electron'
2import path from 'path'
3
4const IS_DEV = process.env.IS_IN_DEVELOPMENT || false
5
6function createWindow () {
7  // Vytvoříme hlavní okno Electronu
8  const win = new BrowserWindow({
9    width: 800,
10    height: 600,
11    webPreferences: {
12      nodeIntegration: true,
13      enableRemoteModule: true
14    }
15  })
16
17  if (IS_DEV) {
18    // Pokud jsme ve vývojovém režimu načteme obsah z localhost serveru - vite
19    // a otevřeme vývojářské nástroje
20    win.loadURL('http://localhost:3000')
21    win.webContents.openDevTools()
22  } else {
23    // Ve všech ostatních případech načteme soubor index.html ze složky dist
24    win.loadURL(`file://${path.join(__dirname, '..' ,'dist', 'index.html')}`)
25  }
26}
27
28app.whenReady().then(createWindow)
29
30app.on('window-all-closed', () => {
31  // Na macOS je běžné, že aplikace a její menu bar zůstanou
32  // aktivní dokud uživatel aplikaci nevypne skrze zkratku Cmd + Q
33  if (process.platform !== 'darwin') {
34    app.quit()
35  }
36})
37
38app.on('activate', () => {
39  // Pokud je na macOS aplikace v docku, je běžné, že se okno vytvoří po
40  // kliknutí na ikonu v docku, pokud nejsou žádná okna aktivní
41  if (BrowserWindow.getAllWindows().length === 0) {
42    createWindow()
43  }
44})

Snad jedím rozdílem proti tomu, který najdeme na webu Electronu je použití ES a deklarace globální proměnné IS_DEV, kterou rozlišíme zdali má Electron instance načíst web z Vite serveru nebo vygenerovanou HTML stránku ve složce ./dist. Následně je potřeba upravit package.json tak aby hodnota "main" odkazovala na náš soubor, tedy ne tak úplně:

package.json

1{
2  "main": "./dist/main.js",
3}

Možná vás překvapí, že odkazuji do složky dist, kdežto náš soubor není v žádné složce. Hned vysvěltím.

ESBuild

Jak už jsem psal, Electron podporuje pouze CommonJS, zatímco Vite a tím pádem i React pracuje pouze ES. Co s tím? Nejjednodušším řešením je použít nějaký bundler, v tomto případě esbuild, neboť jej používá i Vite. Zkoušel jsem také Babel, nicméně je v konfilktu s Vite a aplikaci se pak nepodaří zbuildit. Nainstalujeme tedy esbuild:

1npm install --save-dev esbuild

Do části "scripts" přidáme následující řádky:

1{
2  "scripts": {
3    "esbuild-cjs": "esbuild main.js --format=cjs --outfile=./dist/main.js",
4    "electron:dev": "npm run esbuild-cjs && IS_IN_DEVELOPMENT=true electron ."
5  }
6}

První skript, tedy esbuild-cjs transformuje ES kód do CommonJS a tím pádem jej učiní stravitelným pro Electron. Když si jej pořádně přečteme, tak také zjistíme, že výstupní soubor se ukládá do složky ./dist/main.js, tedy tam, kam jsme odkázali v hodnotě "main" v package.json souboru. Druhý skript potom slouží ke spuštění vývojové instance Electronu. Pokud jsme tedy vše udělali správně, spuštěním příkazu npm run electron:dev se nejprve transformuje soubor main.js do CommonJS a až následně se spustí Electron. Pokud nám zároveň běží Vite server, zobrazí se uvodní stránka React aplikace - shodná s tou, kterou jsme viděli v prohlížeči po navštívení stránky http://localhost:3000. Teď už zbývá jen zvolit si ten správný builder a můžeme vyvíjet aplikace s Vite, Reactem a Electronem.

Electron Packager

Po úvodním fiasku s Electron Builderem jsem se rozhodl, že pro tento článek zkusím alternativu, tedy Electron Packager. Musím uznat, že dokumentace se nedá srovnávat, Builder ji má mnohem lepší, ale zkusit se má všechno. Nainstalujeme tedy Electron Packager:

1npm install --save-dev electron-packager

Po instalaci stačí přidat do package.json do hodnoty "scripts" další skript, a to:

1{
2  "scripts": {
3    "electron:build": "npm run esbuild-cjs && electron-packager --out=release ."
4  }
5}

Skript opět nejprve využije již existujícího esbuild skritpu k tomu, aby soubor main.js převedl do CommonJS a následně zavolá Electron Builder, který v tomto případě vytvoří package aplikace pro naši aktivní paltformu. V případe Windows pro Windows, macOS pro macOS a Linuxu pro Linux. Tím máme hotovo a nezbývá než si pohrát se samotnou aplikací a konfigurací všech součástí.