萬物皆可快速上手之Electron
最近在開發(fā)一款桌面端應(yīng)用,用到了Electron和React。
React作為日常使用比較頻繁的框架,這里就不詳細(xì)說明了,這里主要是想通過幾篇文章讓大家快速上手Electron以及與React完美融合。
本篇是系列文章的第一篇,主要是給大家分享Electron的一些概念,讓大家對(duì)Electron有一個(gè)初步的認(rèn)知。
先來了解一下什么是Electron吧,可能很多小伙伴還沒有聽過Electron,相信很多小伙伴此時(shí)的表情是這樣的:
看下官網(wǎng)[1]的自我介紹:
Electron 是一個(gè)可以使用 Web 技術(shù)如 JavaScript、HTML 和 CSS 來創(chuàng)建跨平臺(tái)原生桌面應(yīng)用的框架。借助 Electron,我們可以使用純 JavaScript 來調(diào)用豐富的原生 APIs。
Electron用 web 頁面作為它的 GUI,而不是綁定了 GUI 庫的 JavaScript。它結(jié)合了 Chromium、Node.js 和用于調(diào)用操作系統(tǒng)本地功能的 APIs(如打開文件窗口、通知、圖標(biāo)等)。
上面這張圖很好的說明了Electron的強(qiáng)大之處。
正因如此,現(xiàn)在已經(jīng)有很多由Electron開發(fā)的應(yīng)用,比如Atom、Visual Studio Code等。我們可以在Apps Built on Electron[2]看到所有由Electron構(gòu)建的項(xiàng)目。
快速開始
前面說了那么多廢話,下面進(jìn)入正題,帶大家用五分鐘(為什么是五分鐘?我猜的 ? )的時(shí)間運(yùn)行一個(gè)Electron的Hello World。
安裝
這一步很簡單:
npm?install?electron?-g
第一個(gè) Electron 應(yīng)用
一個(gè)最簡單的 Electron 應(yīng)用目錄結(jié)構(gòu)如下:
hello-world/
├──?package.json
├──?main.js
└──?index.html
package.json的格式和 Node 的完全一致,并且那個(gè)被 main 字段聲明的腳本文件是你的應(yīng)用的啟動(dòng)腳本,它運(yùn)行在主進(jìn)程上。你應(yīng)用里的 package.json 看起來應(yīng)該像:
{
??"name":?"hello-world",
??"version":?"0.1.0",
??"main":?"main.js"
}
創(chuàng)建main.js文件并添加如下代碼:
const?{?app,?BrowserWindow?}?=?require("electron");
const?isDev?=?require("electron-is-dev");
const?path?=?require("path");
let?mainWindow;
app.on("ready",?()?=>?{
??mainWindow?=?new?BrowserWindow({
????width:?1024,
????height:?680,
????webPreferences:?{
??????nodeIntegration:?true,
??????//?https://stackoverflow.com/questions/37884130/electron-remote-is-undefined
??????enableRemoteModule:?true,
????},
??});
??//?https://www.electronjs.org/docs/api/browser-window#event-ready-to-show
??//?在加載頁面時(shí),渲染進(jìn)程第一次完成繪制時(shí),如果窗口還沒有被顯示,渲染進(jìn)程會(huì)發(fā)出 ready-to-show 事件?。?在此事件后顯示窗口將沒有視覺閃爍
??mainWindow.once("ready-to-show",?()?=>?{
????mainWindow.show();
??});
??const?urlLocation?=?`file://${__dirname}/index.html`;
??mainWindow.loadURL(urlLocation);
});
然后是index.html文件:
html>
<html>
??<head>
????<meta?charset="utf-8"?/>
????<title>Hello?World!title>
????<style?media="screen">
??????.version?{
????????color:?red;
??????}
????style>
??head>
??<body>
????<h1>Hi! 我是柯森!h1>
??body>
html>
到這里main.js, index.html 和 package.json 這幾個(gè)文件都有了。萬事俱備,來運(yùn)行這個(gè)項(xiàng)目。因?yàn)榍懊嬉呀?jīng)全局安裝了electron,所以我們可以使用 electron 命令來運(yùn)行項(xiàng)目。在 hello-world/ 目錄里面運(yùn)行下面的命令:
$?electron?.
你會(huì)發(fā)現(xiàn)會(huì)彈出一個(gè) electron 應(yīng)用客戶端,如圖所示:
到這里,我們已經(jīng)完成了一個(gè)最簡單的electron 應(yīng)用。
但你一定會(huì)對(duì)上面用到的一些api有疑惑,下面我將帶大家深入淺出的了解一下electron的常用概念和api。
相關(guān)概念
Electron 的進(jìn)程分為主進(jìn)程和渲染進(jìn)程。在說這個(gè)之前,我覺得有必要先說一下進(jìn)程和線程的概念。
進(jìn)程和線程
這里參考的是廖雪峰老師關(guān)于進(jìn)程和線程概念的闡述,我覺得說的清晰明了。
對(duì)于操作系統(tǒng)來說,一個(gè)任務(wù)就是一個(gè)進(jìn)程(Process),比如打開一個(gè)瀏覽器就是啟動(dòng)一個(gè)瀏覽器進(jìn)程,打開一個(gè)記事本就啟動(dòng)了一個(gè)記事本進(jìn)程,打開兩個(gè)記事本就啟動(dòng)了兩個(gè)記事本進(jìn)程,打開一個(gè)Word就啟動(dòng)了一個(gè)Word進(jìn)程。
有些進(jìn)程還不止同時(shí)干一件事,比如Word,它可以同時(shí)進(jìn)行打字、拼寫檢查、打印等事情。在一個(gè)進(jìn)程內(nèi)部,要同時(shí)干多件事,就需要同時(shí)運(yùn)行多個(gè)“子任務(wù)”,我們把進(jìn)程內(nèi)的這些“子任務(wù)”稱為線程(Thread)。
主進(jìn)程和渲染進(jìn)程

主進(jìn)程
在 electron 里面,運(yùn)行 package.json 里面 main 腳本的進(jìn)程被稱為主進(jìn)程。主進(jìn)程控制整個(gè)應(yīng)用的生命周期,在主進(jìn)程中可以創(chuàng)建 Web 形式的 GUI,而且整個(gè) Node API 是內(nèi)置其中。
渲染進(jìn)程
由于 Electron 使用 Chromium 來展示頁面,所以 Chromium 的多進(jìn)程架構(gòu)也被充分利用。每個(gè) Electron 的頁面都在運(yùn)行著自己的進(jìn)程,這樣的進(jìn)程我們稱之為渲染進(jìn)程。
在一般瀏覽器中,網(wǎng)頁通常會(huì)在沙盒環(huán)境下運(yùn)行,并且不允許訪問原生資源。然而,Electron 用戶擁有與底層操作系統(tǒng)直接交互的能力。
主進(jìn)程與渲染進(jìn)程的區(qū)別
主進(jìn)程使用BrowserWindow實(shí)例創(chuàng)建頁面。每個(gè)BrowserWindow實(shí)例都在自己的渲染進(jìn)程里運(yùn)行頁面。當(dāng)一個(gè)BrowserWindow實(shí)例被銷毀后,相應(yīng)的渲染進(jìn)程也會(huì)被終止。
主進(jìn)程管理所有頁面和與之對(duì)應(yīng)的渲染進(jìn)程。每個(gè)渲染進(jìn)程都是相互獨(dú)立的,并且只關(guān)心他們自己的頁面。
在 electron 中,頁面不直接調(diào)用底層 APIs,而是通過主進(jìn)程進(jìn)行調(diào)用。所以如果你想在網(wǎng)頁里使用 GUI 操作,其對(duì)應(yīng)的渲染進(jìn)程必須與主進(jìn)程進(jìn)行通訊,請(qǐng)求主進(jìn)程進(jìn)行相關(guān)的 GUI 操作。
在 electron 中,主進(jìn)程和渲染進(jìn)程的通信主要有以下幾種方式:
ipcMain、ipcRender Remote 模塊
進(jìn)程通信將稍后在下文詳細(xì)介紹。
BrowserWindow 的創(chuàng)建

BrowserWindow用于創(chuàng)建和控制瀏覽器窗口。像上面的hello-world中:
mainWindow?=?new?BrowserWindow({
??width:?1024,
??height:?680,
??webPreferences:?{
????nodeIntegration:?true,
????//?https://stackoverflow.com/questions/37884130/electron-remote-is-undefined
????enableRemoteModule:?true,
??},
});
const?urlLocation?=?`file://${__dirname}/index.html`;
mainWindow.loadURL(urlLocation);
創(chuàng)建了一個(gè)1024*680的窗口,并通過loadURL方法來加載了一個(gè)本地的html文件。
這里一般會(huì)通過區(qū)分環(huán)境加載對(duì)應(yīng)不同的文件。
進(jìn)程間的通信
在計(jì)算機(jī)系統(tǒng)設(shè)計(jì)中,不同的進(jìn)程間內(nèi)存資源都是相互隔離的,因此進(jìn)程間的數(shù)據(jù)交換,會(huì)使用進(jìn)程間通訊方式達(dá)成。而不同于一般的原生應(yīng)用開發(fā),Electron 的渲染進(jìn)程與主進(jìn)程分別屬于獨(dú)立的進(jìn)程中,而且進(jìn)程間會(huì)存在頻繁的數(shù)據(jù)交換,這時(shí)選擇一個(gè)合理的進(jìn)程間通訊方式顯得尤為重要。下面是 Electron 中官方提供的進(jìn)程間通訊方式:
window.postMessage,LocalStorage
在前端開發(fā)中,鑒于瀏覽器對(duì)本地?cái)?shù)據(jù)有嚴(yán)格的訪問限制,所以一般通過該兩種方式進(jìn)行窗口間的數(shù)據(jù)通訊,該方式同樣適用于 Electron 開發(fā)中。然而因?yàn)?API 設(shè)計(jì)目的僅僅是為了前端窗口間簡單的數(shù)據(jù)傳輸,大量以及頻繁的數(shù)據(jù)通訊會(huì)導(dǎo)致應(yīng)用結(jié)構(gòu)松散,同時(shí)傳輸效率也值得懷疑。
使用IPC進(jìn)行通信
Electron 中提供了 ipcRender 、ipcMain 作為主進(jìn)程以及渲染進(jìn)程間通訊的橋梁,該方式屬于 Electron 特有傳輸方式,不適用于其他前端開發(fā)場景。Electron 沿用 Chromium 中的 IPC 方式,不同于 socket、http 等通訊方式,Chromium 使用的是命名管道 IPC ,能夠提供更高的效率以及安全性。
主進(jìn)程收發(fā)信息
詳細(xì)參考
ipcMain
主進(jìn)程接收渲染進(jìn)程發(fā)送的信息
ipcMain.on("message",?(e,?msg)?=>?{
??console.log(msg);
});
主進(jìn)程(主窗口)發(fā)送信息給渲染進(jìn)程
mainWindow.webContents.send('message',?{?name:?'from?the?main?by?cosen'?});
渲染進(jìn)程收發(fā)信息
通過
ipcRenderer發(fā)送或接收
渲染進(jìn)程接收主進(jìn)程發(fā)送的信息
ipcRenderer.on("message",?(e,?msg)?=>?{
??console.log(msg);
});
渲染進(jìn)程發(fā)送信息給主進(jìn)程
ipcRenderer.send("message",?{?name:?"Cosen"?});
使用remote實(shí)現(xiàn)跨進(jìn)程訪問
remote 模塊提供了一種在渲染進(jìn)程(網(wǎng)頁)和主進(jìn)程之間進(jìn)行進(jìn)程間通訊(IPC)的簡便途徑。
Electron中, 與GUI相關(guān)的模塊(如 dialog, menu 等)只存在于主進(jìn)程,而不在渲染進(jìn)程中 。為了能從渲染進(jìn)程中使用它們,需要用ipc模塊來給主進(jìn)程發(fā)送進(jìn)程間消息。使用 remote 模塊,可以調(diào)用主進(jìn)程對(duì)象的方法,而無需顯式地發(fā)送進(jìn)程間消息。
總結(jié)
本小節(jié)我們大概的了解了Electron的一些概念以及運(yùn)行了一個(gè)入門的hello-world程序。但這遠(yuǎn)遠(yuǎn)還不夠,下一節(jié)我會(huì)講一下如何將Electron與React完美融合,畢竟還是要更貼近業(yè)務(wù)的~
好了,不早了,我要去開啟我的網(wǎng)易云時(shí)光了 ?
參考資料
官網(wǎng): https://www.electronjs.org/
[2]Apps Built on Electron: https://www.electronjs.org/apps
