Sådan oprettes en Electron Desktop-app i JavaScript: Multithreading, SQLite, Native Modules og andre almindelige smertepunkter

Som ramme for udvikling af desktop-applikationer har Electron meget at tilbyde. Det giver fuld adgang til Nodes API og økosfære. Den implementeres på alle større operativsystemer (med en enkelt codebase). Og med sin webbaserede arkitektur kan du bruge de nyeste funktioner i CSS til at oprette avancerede brugergrænseflader.

Der er mange artikler, der handler om at komme i gang med Electron, men færre dedikeret til at bruge SQLite eller hvordan man går rundt med multithreading. Vi vil se på, hvordan man bruger Electron til at opbygge applikationer, der håndterer store mængder data eller kører mange opgaver.

Især vil vi dække:

  • Hvordan Electron fungerer (i korte træk), og hvordan dets arkitektur påvirker, hvad vi kan gøre
  • Multithreading
  • Brug af lokale databaser som SQLite eller skrivning til enhver fil i en Electron-app
  • Indfødte moduler
  • Et par gotchas at være opmærksom på
  • Pakning af et program ved hjælp af native moduler

Sådan fungerer Electron - forkortet

Det er værd at gentage nøgleprincipperne bag Electrons arkitektur. En elektronapplikation består af mindst to processer. Hovedtråden er indgangen til din applikation og gør alt det nødvendige arbejde for at vise din rendererproces (eller processer) for dine brugere. Der kan kun nogensinde være en forekomst af hovedprocessen.

Renderer-processer bruger Chromium til at gengive din app. Ligesom hver fane kører i sin egen proces, gør det også hver renderer. De indlæses ved hjælp af BrowserWindow-konstruktørens loadURL-metode, som skal pege på en lokal eller ekstern HTML-fil. Det betyder, at den eneste måde at starte en rendererproces på er at bruge en HTML-fil som en post.

Advarsler fra Electrons arkitektur

Enkelheden ved Electron er et af dets største aktiver. Din hovedproces udfører enhver nødvendig konfiguration og sender derefter en HTML-fil eller URL til rendererprocessen. Denne fil kan gøre alt, hvad en almindelig webapplikation kan - og du er god at gå derfra.

Men det faktum, at der kun kan være en hovedproces, gør det uklart, hvordan man implementerer multithreading. Elektrons dokumentation indebærer, at rendererprocesser er strengt designet til at gengive brugergrænseflader (hvilket som vi vil se, ikke er sandt).

Det er vigtigt at vide, at det at gøre noget beregningsintensivt på hovedprocessen vil bremse (eller fryse) dine rendererprocesser. Det er kritisk, at ethvert beregningsintensivt arbejde flyttes fra hovedtråden. Det er bedst at overlade det udelukkende til opgaven at gøre alt, hvad der er nødvendigt for at starte dine rendererprocesser. Da vi ikke kan udføre intensivt arbejde med den samme rendererproces, der gengiver applikationens frontend (da dette også vil påvirke brugergrænsefladen), har vi brug for en anden tilgang.

Multithreading

Der er tre generelle tilgange til multithreading i Electron:

  • Brug webarbejdere
  • Gaffel nye processer til at køre opgaver
  • Kør (skjulte) rendererprocesser som arbejdere

Webarbejdere

Da Electron er bygget oven på Chromium, kan alt, hvad der kan gøres i en browser, gøres i en rendererproces. Dette betyder, at du kan bruge webarbejdere til at køre intensive opgaver i separate tråde. Fordelen ved denne tilgang er enkelhed og bevarelse af isomorfisme med en webapplikation.

Der er dog en meget stor advarsel - du kan ikke bruge native moduler. Teknisk set kan du det, men det gør, at din applikation går ned. Dette er et væsentligt problem, da ethvert program, der har brug for multithreading, muligvis også skal bruge native moduler (såsom node-sqlite3).

Gafler nye processer

Electron bruger Node som en runtime, hvilket betyder at du har fuld adgang til indbyggede moduler som klynge. Nye processer kan forkæles til at køre opgaver, hvilket holder intensivt arbejde væk fra hovedtråden.

Hovedproblemet er, at i modsætning til rendererprocesser kan underordnede processer ikke bruge metoder fra elektronbiblioteket. Dette tvinger dig til at opretholde en kommunikationskanal med hovedprocessen via IPC. Renderer-processer kan bruge fjernmodulet til at fortælle hovedprocessen at udføre hovedopgaver uden dette ekstra trin.

Et andet problem er, at hvis du bruger ES-moduler eller TC39-funktioner i JavaScript, skal du sikre dig, at du kører transpilerede versioner af dine scripts. Du bliver også nødt til at medtage disse i din pakkede applikation. Dette problem påvirker ethvert Node-program, der forkaster processer, men det tilføjer endnu et lag af kompleksitet til din byggeproces. Det kan også blive vanskeligt, når man afbalancerer kravene til emballering af din applikation med udviklingsværktøjer, der bruger funktioner såsom live-genindlæsning.

Brug af rendererprocesser som arbejdertråde

Renderer-processer behandles traditionelt som brugt til at gengive dit brugergrænseflade. De er dog ikke bundet til denne eneste opgave. De kan skjules og køres i baggrunden ved at konfigurere det visflag, der sendes til BrowserWindow.

At gøre dette har mange fordele. I modsætning til webarbejdere har du friheden til at bruge native moduler. Og i modsætning til forked-processer kan du stadig bruge elektronbiblioteket til at fortælle hovedprocessen at gøre ting som at åbne en dialog eller oprette OS-meddelelser.

En udfordring ved brug af Electron er IPC. Mens det er simpelt, kræver det en stor mængde kedelplade og pålægger vanskelighederne med at debugge et stort antal begivenhedslyttere. Det er også en anden ting, du skal enhedstest. Ved at bruge en rendererproces som en arbejdstråd kan du omgå dette fuldstændigt. Ligesom med en server, kan du lytte til en lokal port og modtage anmodninger, så du kan bruge værktøjer som GraphQL + React Apollo. Du kan også bruge websockets til kommunikation i realtid. En anden bonus er, at du ikke behøver at bruge ipcRenderer og kan holde dine elektron- og webapplikationer isomorfe (hvis du ønsker at bruge en delt codebase til en desktop- og webapplikation).

I tilfælde af avanceret brug kan denne tilgang kombineres med klyngedannelse for at få det bedste ud af alle verdener. Den eneste ulempe er, at du bliver nødt til at angive en HTML-fil som post for dine medarbejder renderer processer (som føles som noget af et hack).

Sådan bruges SQLite (eller noget, du skal skrive til)

Der er flere tilgange til statsadministration, der ikke kræver native moduler. For eksempel håndtering af hele din tilstand i sammenhæng med en renderer med Redux.

Men hvis du har brug for at håndtere store mængder data, er dette ikke tilstrækkeligt. Vi ser især på, hvordan man bruger SQLite i en elektronapplikation.

For at implementere din Electron-applikation skal du først pakke den. Der er en række værktøjer til rådighed til at gøre det - det mest populære er Electron Builder. Electron bruger ASAR arkivformat til at samle din applikation i en enkelt, ukomprimeret fil. ASAR-arkiver er skrivebeskyttede - hvilket betyder at du ikke kan skrive nogen data til dem. Dette betyder, at du ikke kan medtage din database i dit ASAR-arkiv sammen med resten af ​​din kode (i elektronbygger ville dette være under "filer").

Inkluder i stedet din database i ressourcemappen på din elektronpakke. Filstrukturen for en pakket elektronapplikation, og hvor du skal placere din database, kan ses nedenfor:

Det pakkede ASAR-arkiv kaldet app.asar findes i ./Contents/Resources. Du kan placere din database eller en hvilken som helst fil, du vil skrive til, men inkludere i din pakkede applikation, i samme bibliotek. Dette kan opnås med Electron Builder ved hjælp af konfigurationen “ekstraResources”.

En anden tilgang er at oprette en database i en anden mappe helt. Men du bliver nødt til at tage højde for at slette denne fil på alle platforme, hvis brugerne beslutter at afinstallere din applikation.

Emballage med native moduler

Langt størstedelen af ​​Node-moduler er scripts skrevet i JavaScript. Native moduler er moduler skrevet i C ++, der har bindinger til brug med Node. De fungerer som en grænseflade til andre biblioteker skrevet i C / C ++ og er typisk konfigureret til at kompilere efter installationen.

Som moduler på lavt niveau skal de kompileres til målarkitekturer og operativsystemer. Et oprindeligt modul, der er kompileret på en Windows-maskine, fungerer ikke på en Linux-maskine - selvom et almindeligt modul ville. Dette er et problem for Electron, da vi til sidst har brug for at pakke alt i en .dmg (OSX), .exe (Windows) eller .deb (Linux) eksekverbar.

Elektronapplikationer, der bruger native-moduler, skal pakkes på det system, de målretter mod. Da du vil automatisere denne proces i en CI / CD-pipeline, skal du opbygge dine oprindelige afhængigheder inden emballering. For at opnå dette kan du bruge et værktøj udviklet af Electron-teamet kaldet elektron-rebuild.

Hvis du udvikler et ikke-kommercielt open source-projekt, kan du bruge TravisCI (Linux, OSX) og Appveyor (Windows) til automatisk at oprette, teste og implementere din applikation gratis.

Opsætningen til dette kan være vanskelig, hvis du har integrationstest, da du bliver nødt til at installere visse afhængigheder for, at hovedløse tests fungerer. Et eksempel på en konfiguration til OSX og Linux med TravisCI kan findes her, og et eksempel på en leverandørkonfiguration her (disse eksempler er baseret på konfigurationen i elektron-reager-kedelpladeprojektet med tilføjelse af OSX og implementering).

Gotchas

Når din elektronapplikation er pakket, opfører nogle indbyggede egenskaber ved node, der vedrører stier, sig muligvis ikke, som du forventer, og opfører sig ikke som de gør, når du kører den forudbyggede binær til at betjene din applikation.

Variabler som __dirname, __filename og metoder som process.cwd opfører sig ikke som forventet i et pakket program (se problemer her, her og her). Brug i stedet app.getAppPath.

En sidste note om emballagen

Mens du udvikler en elektronapplikation, kan du bruge både produktion (servering af bundtet kode med den forudbyggede binær) og udvikling (ved hjælp af webpack-dev-server eller webpack-serve til at se dine filer).

For at bevare din fornuft skal du opbygge og servere dine bundter fra samme bibliotek som din kildekode. Dette betyder, at når du vælger disse filer til emballering, forbliver antagelser om filstier ensartede på tværs af disse tilstande og din pakke.

I det mindste skal din hovedproces pege på filstien til dine rendererprocessers HTML-fil. Hvis du flytter denne fil til en anden mappe som en del af din byggeproces, bliver du tvunget til at opretholde filstrukturantagelser, og dette kan hurtigt blive et andet beskatningslag af komplikation, du har brug for at vedligeholde.

Fejlfinding i forbindelse med forkerte filveje i en pakket applikation er i høj grad et tilfælde af forsøg og fejl.

Resumé

Der er flere tilgange til multithreading i Electron. Webarbejdere er bekvemme, men mangler evnen til at bruge native moduler. Forking af nye processer fungerer som i Node, men manglen på evne til at bruge elektronbiblioteket tvinger brugen af ​​IPC til fælles opgaver og kan hurtigt blive kompliceret. Brug af gengivelsesprocesser som arbejdere giver fuld effekt af alle tilgængelige Node-serverværktøjer som erstatning for kommunikation via IPC, samtidig med at der opretholdes adgang til native moduler og metoder fra Electron renderer-biblioteket.

Da Electron pakker filer i et skrivebeskyttet ASAR-arkiv, kan enhver fil, vi skal skrive til (såsom en SQLite-database), ikke medtages. I stedet kan disse placeres i ressourcemappen, hvor de forbliver i den pakkede applikation.

Endelig skal du være opmærksom på, at nogle nodeegenskaber i en pakket applikation ikke opfører sig som du forventer. Og af hensyn til klarhedens skyld skal du matche din pakkede applikations filstruktur med din kildekode.