Electron 快速入門,順便聊聊 IPC 通信

2021-12-09 騰訊IMWeb前端團隊

收錄於話題 #我也想做前端 17個內容

前陣子將排課系統的一些功能,提供給 solar 編輯器使用,solar 是基於互動課件編輯器 Cocos ICE 進行二次定製和個性化開發的課件製作系統,其底層是 Cocos Creator。而 Cocos Creator 是基於 Electron 進行開發的,所以學習了一些關於 Electron IPC 通信的相關知識,在這裡做一個總結。

文章的開始,先讓我們來了解下 Electron 是什麼。

1. 什麼是 Electron?

Electron 官網只有一句簡單的話: 使用 JavaScript,HTML 和 CSS 構建跨平臺的桌面應用程式。簡單點講,就是有了 Electron,我們就可以用前端技術來寫 web 頁面,它可以轉化為一個桌面應用。
除此之前,Electron 還有其他的一些特性:

2. Electron 能做啥?

Electron 基於 Chromium 和 Node.js,類似一個小型的 Chrome 的瀏覽器,Electron 可以將你寫的 web 頁面(html 文件)本地化,然後打包成一個桌面應用程式。它同時還是跨平臺的,提供了許多功能與原生系統進行交互。

由於是基於 Chromium 的,所以寫 Electron,從此與前端兼容性無緣(真香)。Node.js版本也是固定的,無需考慮版本兼容問題(除非升級大版本)。

所以作為前端開發人員來說,想開發一款桌面端應該,Electron 是再適合不過了。

Electron 官網還舉了一些使用 Electron 進行開發的應用,大名鼎鼎的 VSCode 就是基於Electron。

3. Electron 快速上手

學啥不得先來個 hello world 呢?

3.1. 初始化工程

創建 Electron 工程方式與前端項目別無二致,創建一個目錄,然後用 npm 初始化:

mkdir hello-electron && cd hello-electron
npm init -y

生成之後的 package.json 應該長這樣。

{
"name": "hello-electron",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}

3.2. 安裝依賴

npm install --save-dev electron

安裝過程中,electron 模塊會去 Github 下載 預編譯二進位文件,然而下載速度大家都懂的,可能會出現下載失敗的情況。這裡可以使用 taobao 的鏡像源來下載。

npm config set electron_mirror http://npm.taobao.org/mirrors/electron/
npm config set electron_custom_dir "8.1.1"

為了更方便的啟動我們的程序,可以新增一條命令。

{
"scripts": {
"start": "electron ."
}
}

接下來,就讓我們愉快地編碼吧。

3.3. 創建 HTML

在 Electron 中,每個窗口都可以加載本地或者遠程 URL,這裡我們先創建一個本地的 HTML 文件。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
We are using Electron <span id="electron-version"></span>
</body>
</html>

這裡你可能會注意到, span 標籤裡面是空文本,後面我們會動態插入 Electron 的版本。

3.4. 創建入口文件

類似於 Node.js 啟動服務,Electron 啟動也需要一個入口文件,這裡我們創建 index.js 文件。在這個入口文件裡,需要去加載上面創建的 HTML 文件,那麼如何加載呢? Electron 提供了兩個模塊:

入口文件是 Node.js 環境,所以可以通過 CommonJS 模塊規範來導入 Electron 的模塊。同時添加一個 createWindow() 方法來將 index.html 加載進一個新的 BrowserWindow 實例。

// index.js
const { app, BrowserWindow } = require('electron');

function createWindow () {
const win = new BrowserWindow({
width: 800,
height: 600
})

win.loadFile('index.html')
}

那麼在什麼時候調用 createWindow 方法來打開窗口呢?在 Electron 中,只有在 app 模塊的 ready 事件被激發後才能創建瀏覽器窗口。可以通過使用 app.whenReady() API 來監聽此事件。

// index.js
app.whenReady().then(() => {
createWindow()
})

這樣一來就可以通過以下命令打開 Electron 應用程式了!

# 這裡會自動去找package.json的main欄位對應的文件運行
# 當然 你也可以將命令放進 script 裡面
npx electron .

運行完打開的應用程式如下圖所示。

3.5. 管理窗口的聲明周期

雖然現在可以打開一個瀏覽器窗口,但還需要一些額外的模板代碼使其看起來更像是各平臺原生的。應用程式窗口在每個 OS 下有不同的行為,Electron 將在 app 中實現這些約定的責任交給開發者們。

可以使用 process.platform 屬性來為不同的作業系統做處理。

3.5.1. 關閉所有窗口時退出應用(Windows & Linux)

在 Windows 和 Linux 上,關閉所有窗口通常會完全退出一個應用程式。 app 模塊可以監聽所有窗口關閉的事件 window-all-closed,在事件回調裡可以調用 app.quit() 退出應用。

// index.js
app.on('window-all-closed', function () {
// darwin 為 macOS
if (process.platform !== 'darwin') app.quit()
})

3.5.2. 沒有窗口打開則打開一個新窗口(macOS)

用過 macOS 的人應該都知道,一個應用沒有窗口打開的時候,也是可以繼續運行的,這時如果打開應用程式,就會打開新的窗口。 app 模塊可以監聽應用激活事件 activate,在事件回調裡可以判斷當前窗口數量來確定需不需要打開一個新的窗口。因為窗口無法在 ready 事件前創建,你應當在你的應用初始化後僅監聽 activate 事件。通過在您現有的 whenReady() 回調中附上您的事件監聽器來完成這個操作。

// index.js
app.whenReady().then(() => {
createWindow()

app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})

3.6. 預加載腳本

前面講到我們會在 HTML 文件中插入 Electron 的版本號。然而,在 index.js 主進程中,是不能編輯 DOM 的,因為它無法訪問到渲染進程 document 上下文,它們存在於完全不同的進程中。

這時候,預加載腳本就可以派上用場了。預加載腳本在渲染進程加載之前加載,並有權訪問兩個渲染進程全局 (例如 window 和 document) 和 Node.js 環境。

3.6.1. 創建預加載腳本

創建一個名為 preload.js 的新腳本如下:

window.addEventListener('DOMContentLoaded', () => {
const replaceText = (selector, text) => {
const element = document.getElementById(selector);
if (element) element.innerText = text;
}

replaceText('electron-version', process.versions.electron);
})

我們需要在初始化 BrowserWindow 實例的時候,傳入該預加載腳本。

// 在文件頭部引入 Node.js 中的 path 模塊
const path = require('path')

// 修改現有的 createWindow() 函數
function createWindow () {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})

win.loadFile('index.html')
}
// ...

然後重新啟動程序,就可以看到 Electron 的版本了。

4. Electron 的流程模型

前面講到了主進程、渲染進程等概念性知識,初學者可能會對此比較迷惑,不過,進行 Electron,對這一塊內容的掌握是至關重要的,後面的 IPC 進程通信,也與此有關。實際上,Electron 繼承了來自 Chromium 的多進程架構,作為前端工程師,對於瀏覽器進程架構有所了解,也是非常有必要的。

4.1. 主進程

每個 Electron 應用都有一個單一的主進程,作為應用程式的入口點,比如上面的 index.js。主進程在 Node.js 環境中運行,這意味著它具有 require 模塊和使用所有 Node.js API 的能力。主進程一般包括以下三大塊:

窗口管理:使用 BrowserWindow 模塊創建和管理應用窗口。類的每個實例創建一個應用程式窗口,且在單獨的渲染器進程中加載一個網頁。

應用生命周期:主進程可以使用 Electron 提供的 app 模塊來控制應用程式的生命周期。

原生 API:Electron 有著多種控制原生桌面功能的模塊,例如菜單、對話框以及託盤圖標。

4.2. 渲染進程

每個打開的 BrowserWindow 都會生成一個單獨的渲染進程。渲染進程負責渲染網頁實際的內容。因此,渲染進程中運行的代碼,幾乎跟我們編寫的 Web 代碼別無二致。除此之外,渲染進程也無法直接訪問 require 或其他 Node.js API。

注意:實際上渲染進程可以生成一個完整的 Node.js 環境以便於開發。在過去這是默認的,但如今此功能考慮到安全問題已經被禁用。

4.3. 預加載腳本

前面上手的時候已經講過預加載腳本了,預加載(preload)腳本會在渲染進程網頁內容開始加載之前執行,並且可以訪問 Node.js API。由於預加載腳本與渲染器共享同一個全局 Window 接口,因此它通過在 window 全局中暴露任意您的網絡內容可以隨後使用的 API 來增強渲染器。

不過我們不能在預加載腳本中直接給 window 掛載變量,因為 contextIsolation 是默認的。

window.myAPI = { desktop: true }

console.log(window.myAPI) // => undefined

Electron 這樣做是為了將預加載腳本與渲染進程的主要運行環境隔離開來的,以避免洩漏任何具特權的 API 到網頁內容代碼中。(比如有些人會把 ipcRenderer.send 的方法暴露給 web 端,這將允許網站發送任意的 IPC 消息)

我們也可以關閉 contextIsolation,不過不建議這麼做

new BrowserWindow({
// ...
webPreferences: {
// ...
contextIsolation: false
}
})

最好使用 contextBridge 模塊來安全地實現交互:

const { contextBridge } = require('electron')

contextBridge.exposeInMainWorld('myAPI', {
desktop: true
})

console.log(window.myAPI)// => { desktop: true }

5. Electron IPC 通信

Electron 有主進程和渲染進程,之間會有許多通信,這樣就涉及到了進程間通信(IPC,InterProcess Communication)

在 Electron 中,主線程和渲染進程之間進行通信,只要是用到以下兩個模塊:

5.1. 渲染進程給主線程發送消息,主線程回復5.1.1. 普通腳本監聽

普通腳本引入 electron 的 ipcRenderer 模塊,實現發送消息。

在 HTML 文件添加 renderer.js 腳本

const { ipcRenderer } = require('electron')

ipcRenderer.on('main-message-reply', (event, arg) => {
console.log(arg);
});
ipcRenderer.send('message-from-renderer', '渲染進程發送消息過來了');

在 index.js 入口文件引入 ipcMain 模塊,並修改 BrowserWindow 的實例化參數,開啟渲染進程的 Node.js 環境。

const { ipcMain } = require('electron')

function createWindow() {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
// 這裡開啟後 渲染進程就可以用 NodeJS 環境
// 可以引如 Electron 相關模塊
nodeIntegration: true,
contextIsolation: false,
},
});
mainWindow.loadFile('index.html');
}

ipcMain.on('message-from-renderer', (event, arg) => {
console.log(arg);
// 接收到消息後可以回復
event.reply('main-message-reply', '主進程回復了')
})

啟動應用,可以在命令行看到渲染進程發過來的消息了。

 然後渲染進程收到主線程的回覆。

5.1.2. 預加載腳本暴露接口

在預加載腳本中,可以暴露一些全局的接口給到渲染進程,然後渲染進程調用,從而達到通信的目的。這種方式類似於微信 SDK,不用侵入到前端腳本去監聽事件,較為安全。

// preload.js
const { contextBridge, ipcRenderer } = require('electron')

// 這裡暴露一個全局myAPI變量
contextBridge.exposeInMainWorld('myAPI', {
getMessage(args) {
ipcRenderer.send('message-from-proload', args);
consoloe.log('前端調用了:', args)
}
})

renderer.js 直接調用暴露出來的接口。

// renderer.js
window.myAPI.getMessage('postMessage');

index.js 主進程監聽預加載腳本發送過來的信息。

ipcMain.on('message-from-proload', (event, arg) => {
console.log(arg);
// 接收到消息後可以回復
event.reply('main-message-reply', '主進程回復了')
})

5.2. 主線程給渲染進程發送消息

將 renderer.js 改為如下代碼,監聽主線程發送過來的消息。

const { ipcRenderer } = require("electron");

ipcRenderer.on("message", (event, arg) => {
console.log("主進程主動推消息了:", arg);
});

主線程往渲染進程發送消息,需要用到 webContents。 webContents 是一個 EventEmitter,負責渲染和控制網頁,是 BrowserWindow 對象的一個屬性。修改一下 index.js 文件。

function createWindow() {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: true,
contextIsolation: false,
},
});

const contents = mainWindow.webContents;
mainWindow.loadFile('index.html');
contents.openDevTools(); //打開調試工具

contents.on("did-finish-load", () => {
//頁面加載完成觸發的回調函數
contents.send("main-message-reply", "我看到你加載完了,給你發個信息");
});
}

運行應用,就可以在渲染進程中打開看到消息了。

以上的通信方式均為異步,不過 Electron 也提供了同步的通信方式,但是同步的方式會阻塞代碼的執行,最好都使用異步通信。同步用法在這裡不多作介紹。

ipcMain 和 ipcRenderer 模塊還有一些其他的通信 API,不過大抵都是類似的通信方式,需要了解的同學可以自行去查閱文檔。

6. 最後

到這裡文章的介紹就差不多了,不過在實際寫代碼的時候,感覺 Electron 的原生 IPC 通信機制,寫起來還是有點繁瑣。VSCode 的事件通信機制,聽聞封裝得比較好,後面有時間再去讀讀它的源碼,寫一篇文章看看。

相關焦點

  • Electron實戰
    本文主要介紹了桌面應用程式的歷史和 electron 的歷史,分析了 electron 原理和 electron-vue 項目架構,快速上手了 electron-vue 項目。四、 Electron的入門(一) 應用程式結構圖 5 electron的架構
  • VSCode源碼解讀--IPC通信機制
    於是,在 Electron 中,可以通過以下方式來進行主進程和渲染器進程的通信:利用 ipcMain和 ipcRenderer模塊進行 IPC 方式通信,它們是處理應用程式後端( ipcMain)和前端應用窗口( ipcRenderer)之間的進程間通信的事件觸發。利用 remote模塊進行 RPC 方式通信。
  • 使用Electron開發Web桌面應用程式
    可以看到,原始文件名為electron.exe。 如果你用mac,也可以看mac下解壓後軟體包的描述:我們總結起來有如下兩個好處:1.沒有跨瀏覽器兼容性問題2.無需加載遠端資源(無延遲)桌面應用程式還有一些其它的潛在優勢,如訪問連接設備(藍牙,usb等通信)、脫機離線瀏覽等優點。桌面應用程式還有一個優點就是可以將所有資源和代碼打在一個包中,直接從用戶的計算機加載。
  • 秋招之路-深刻理解 Linux 進程間七大通信(IPC)
    Linux 下的進程通信手段基本上是從 Unix 平臺上的進程通信手段繼承而來的。如圖所示:從上圖可以看出,全局數據結構 struct ipc_ids msg_ids 可以訪問到每個消息隊列頭的第一個成員:struct kern_ipc_perm;而每個 struct kern_ipc_perm 能夠與具體的消息隊列對應起來是因為在該結構中,有一個 key_t 類型成員 key,而 key 則唯一確定一個消息隊列。
  • IPC國際線上研討會推薦:IPC-CFX 電子製造商的工業4.0標準
    IPC-CFX對機器與機器之間的通信進行了簡化和標準化,同時也促進了機器對企業/企業對機器的解決方案。 與會者可以了解到組裝製造業的全球工業物聯網通信標準、工業4.0、智能工廠和數字工廠解決方案。本次會議還旨在與印度電子製造行業的IPC成員進行互動交流,並與印度MSME /大型企業分享如何增加商機和節約成本的知識。
  • 使用electron+vue開發一個跨平臺todolist(便籤)桌面應用
    # 2在使用electron期間,我順便寫了一個簡單的todolist(便籤)應用,用於學習和嘗試;項目地址:https://github.com/xiajingren/xhznl-todo-list 界面參考了小黃條便籤。
  • 內網滲透 | 基於IPC的橫向移動
    IPC$的概念IPC$(Internet Process Connection)是共享」命名管道」的資源,它是為了讓進程間通信而開放的命名管道,可以通過驗證用戶名和密碼獲得相應的權限,在遠程管理計算機和查看計算機的共享資源時使用。IPC$的作用利用IPC$,連接者可以與目標主機建立一個連接,利用這個連接,連接者可以得到目標主機上的目錄結構、用戶列表等信息。
  • Electron 7.0.0 發布,跨平臺桌面應用開發工具
    在此版本中這三者分別升級內容如下: Chromium 78.0.3905.1 Node.js 12.8.1 V8 7.8 增加了 Windows on ARM(64位) 版本  為異步請求/響應的 IPC 添加了 ipcRenderer.Invoke() 和 ipcMain.Handle
  • Electron+Vue開發跨平臺桌面應用
    npm install -g electron或者cnpm install -g electron為了驗證是否安裝成功,可以使用如下的命令。創建運行項目Electron官方提高了一個簡單的項目,可以執行以下命令將項目克隆到本地。
  • IPC標準委員會把AMQP定為互聯工廠的通信傳輸協議
    更多CFX信息,請點擊www.ipc.org.cn。
  • Electron的代碼調試
    Electron,嘗試調試程序時,竟無從下手,所以把這個過程做了下記錄參考工程 根據Electron的官方文檔:使用 VSCode 進行主進程調試:https://electronjs.org/docs/tutorial/debugging-main-process-vscode下載相應的github工程:https://github.com/octref/vscode-electron-debug
  • 手摸手Electron + Vue實戰教程(一)
    一個由淺入深(或者說從入門到放棄)的套路,一步步帶你敲代碼實現一個小項目,希望能給大家帶來一點點啟發。2 項目搭建網上有個比較火的腳手架electron-vue,github 上 12.2k 的 star,大家應該都聽說過或者使用過,但現在我們不使用它,electron-vue是vue-cli2.0的版本,現在都已經出道 4.0 了,再者electron-vue已經很久沒有更新,我們可以使用 vue 最新的腳手架加上插件vue-cli-plugin-electron-builder
  • 一口氣, 了解 Qt 的所有 IPC 方式 | Qt 速學
    示例https://doc.qt.io/qt-5/qtcore-ipc-localfortuneserver-example.html這個例子演示如何使用 QLocalServer 實現一個隨機應答伺服器。
  • linux-kernel-pwn-csaw-2015-stringipc
    分析閱讀源碼後,知道了程序的功能是可以創建ipc通道,並對ipc通道進行相應的讀、寫、擴展以及縮小。其中漏洞代碼存在於縮小函數中,代碼如下:staticint realloc_ipc_channel ( struct ipc_state *state, int id, size_t size, int grow ){struct ipc_channel *channel;size_t new_size;char*new_data; channel
  • 翻譯 | 《JavaScript Everywhere》第18章 帶Electron的桌面應用程式(^_^)
    以這種方式開發我們的應用程式將使我們能夠快速將桌面應用程式發送給感興趣的用戶,同時為我們提供了以後為桌面用戶引入自定義應用程式的靈活性。我們將如何構建它要構建我們的應用程式,我們將使用Electron,它是一個開放原始碼的框架,用於使用Web技術構建跨平臺的桌面應用程式。
  • 使用 Macaca 測試 Electron 桌面 App
    通過 Macaca 官方示例來 分析1下原理,精簡一下代碼const wd = require('macaca-wd');var browser = process.env.browser || 'electron' || 'puppeteer';browser = browser.toLowerCase();describe('macaca-test
  • 使用Web前端技術開發桌面應用---Electron Remote模塊的使用
    那我們話不多說,直接按照上節課的程序進行改寫,在項目根目錄下,新建一個demo2.html文件,然後快速生成html的基本結構,編寫一個按鈕,引入渲染的js頁面。代碼如下:<!const btn = this.document.querySelector('#btn')const BrowserWindow =require('electron').remote.BrowserWindowwindow.onload = function(){ btn.onclick = ()=>{
  • Android跨進程通信之AIDL快速入門
    本篇來自 蛇髮女妖 的投稿,演示了如何使用AIDL進行進程間通信。文章簡單易懂,希望大家喜歡。蛇髮女妖 的博客地址:http://www.jianshu.com/users/d2aa06a908d5「Android進程通信」,乍一聽感覺好深奧的東西。到底什麼是進程通信呢?
  • 開放式數控系統中IPC與PLC通信技術
    二、IPC與PLC之間的通信  數控系統中的通信接口主要有兩個,一個通過RS232串行接口進行數據指令的傳輸,一個是通過數據線與計算機進行程序的輸出與寫入工作,該接口也是通過RS232的串行接口,通過RS232數據線進行程序的傳輸和讀寫操作。
  • 用electron加Vuejs開發桌面程序
    electron是一個js的桌面程序框架,有很多程序基於它開發,比如VsCode,Atom等。而Vue.js是我們國人開發的著名js框架。用這兩個結合開發就是強強聯手,只要會網頁開發就能寫桌面程序,感覺不要太好首先我們需要先安裝nodejs+npm, 因為太慢,就需要用國內的鏡像伺服器了,比如阿里的然後執行以下命令建一個基於vue的electron項目,項目名要替換成你的目錄最後就會看到一個程序啟動