Electron應用開發實踐

2024年2月6日 26点热度 0人点赞

前言

Electron是由Github開發,使用Html,Css,Javascript作為開發語言的開源框架, 它將Node.js和Chromium集成於同一運行環境,並且可以打包輸出成多平臺(MacOS,Windows,Linux)運行的桌面應用。此篇文章將介紹如何開發一個Electron應用。

基本介紹

對於前端開發工程師而言,Electron框架也許不算傢喻戶曉,但是由Electron構建出來的應用肯定不會陌生 —— 常用的編輯器VsCode和Atom都是由Electron開發構建。Electron的核心由3部分組成:

· Chromium是Google開源的一個瀏覽器項目,常用的Chrome瀏覽器正是在它的基礎上開發,提供將Html,Css,Javascript渲染成頁面的能力。

· Node.js提供Node執行環境的運行時,這大大提升了視圖層頁面與系統底層Api的交互能力。

· Electron內置的Api提供了構建應用的輔助能力,如程序的菜單欄,全局的快捷鍵,對話框提示等,並且在內部抹平了MacOS,Windows,Linux在系統調用的差異性,提供給開發者相同的調用結構。

核心概念

Electron框架有兩個核心概念:Main Process (主進程) 和 Renderer Process(渲染進程)。我們來分別看一下兩者的構成:

· Main Process(主進程)在一個Electron應用中有且僅能擁有一個,控制著Electron程序的窗口,全局事件,快捷鍵等。所有的Renderer process(渲染進程)都是由Main Process(主進程)的BrowserWindow模塊進行實例化創建的。

· Renderer Process(渲染進程)在Electron應用中可以由多個,每一個由BrowserWindow創建的窗口默認是一個新的渲染進程。每一個窗口等同於瀏覽器的一個渲染窗口,負責將Html,Css,Javascript等渲染成實際的視圖。

一圖以蔽之,主進程和渲染進程的關系就像瀏覽器的操作欄和頁面Tab。

前置工作

首先,我們新建一個electron-demo目錄,並且進行npm初始化生成默認的package.json

mkdir electron-demo
cd electron-demo
npm init -y

這裡我們使用npm的方式安裝Electron:

npm install Electron --save

在安裝過程中Electron的腳本會去根據開發者當前的系統下載Electron可執行應用,由於默認下載源是國外網絡節點,這一步很有可能下載緩慢超時或者失敗,這裡筆者推薦使用國內鏡像源。經過開發測試,華為雲的鏡像雲比較穩定,我們在安裝前加入自定義安裝源參數:

ELECTRON_MIRROR=https://mirrors.huaweicloud.com/electron/
npm install Electron --save

為了後續安裝方便,可以在項目目錄下新建一個.npmrc的文件,保存npm安裝時的變量信息,在文件內添加:

electron_mirror=https://mirrors.huaweicloud.com/electron/

後續就可以使用常規方式安裝了。至此,我們的開發前置工作就大功告成。

開始上手

這裡我們用一個簡單的demo,體驗一下Electron相對於瀏覽器特有的可調用Node.js運行時Api的能力,前端部分選用的是react作為開發框架,項目的代碼在筆者的github Electron-demo(https://github.com/simonwang6666/electron-demo) ,項目目錄結構如下:

electron-demo
test
└──content.txt // 測試讀取的文件
public // 公共資源文件夾
webpack // webpack相關配置
script // 打包構建腳本
src
├── App.js // App應用
├── index.less // 樣式文件
├── main.js // 前端頁面入口文件
electron.main.js // electron應用入口文件
.npmrc // npm相關環境變量
package.json 

下面是視圖頁面的邏輯:

src/App.js
import React, { useEffect, useState } from "react";
import fs from "fs";
import './index.less'
const App = () => {
const [content, setContent] = useState("");
useEffect(() => {
const fileContent = fs.readFileSync(
'./test/content.txt',
"utf-8"
);
setContent(fileContent);
},
);
return (
<div className="app">你用Electron的特性讀取到了文件內容:
<span className="content">{content}</span>
</div>
);
};
export default App;

content.txt是我們測試讀取的一個文件,裡面的內容如下:

test/content.txt

你發現我了

可以看到,我們的App.js中試圖在web應用中調用Node.js的fs模塊去讀取一個文件。相比之下,main.js就熟悉的多了,完全是一個react應用的標準入口文件:


src/main.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(<App />, document.getElementById('root'))

前端服務的webpack配置項和標準的web應用也毫無差別,唯一的區別在於構建target我們使用了"electron-renderer"

webpack/common.config.js line8
target: "electron-renderer"

關於它的說明可以參閱 webpack target配置

可以發現,一個electron項目的渲染進程應用部分與傳統的web應用高度相似,唯一區別是多了一個electron.main.js的主進程入口文件,我們來看看這個文件做了什麼:

electron.main.js
const { app, BrowserWindow } = require('electron')
function createWindow() {
// 創建瀏覽器窗口
let win = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: true,
webSecurity: false,
enableRemoteModule: true
}
})
/*
在執行 npm run start 後,經常會窗口已經顯示出來了,
但代碼還未構建好,此時捕獲到 did-fail-load 事件,在之後延遲重載
*/
win.webContents.on('did-fail-load', function () {
console.log(`createWindow: did-fail-load, reload soon...`)
setTimeout(() => {
win.reload()
}, 1000)
})
if (process.env.NODE_ENV === 'development') {
win.loadURL('http://localhost:9090')
} else {
win.loadFile('./dist/index.html')
}
}
app.whenReady().then(createWindow)

我們引用到了Electron在主進程最核心的兩個模塊 —— app模塊和BrowserWindow模塊。app模塊控制著整個app的行為,並且處理app在不同階段的事件響應。而每一個BrowserWindow實例即是一個渲染進程。首先來看一下渲染進程的創建部分,主要邏輯在createWindow函數:

electron.main.js -> createWindow() line5 -- line13
let win = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: true,
webSecurity: false,
enableRemoteModule: true
}
})

這裡我們新建了一個渲染進程實例,設定了渲染進程的相關參數,例如初始的高度,寬度。這裡我們開啟的nodeIntergration(Node.js的api在渲染進程的註入),關閉了web安全校驗(也就是跨域攔截),更多配置參數可以參考文檔 BroserWindow模塊

electron.main.js -> createWindow() line26 - line30
if (process.env.NODE_ENV === 'development') {
win.loadURL('http://localhost:9090')
} else {
win.loadFile('./dist/index.html')
}

我們根據NODE_ENV參數判斷是否處於開發環境, 這和我們開發一個node作為服務端的web應用非常相似。在開發環境去加載我們使用webpackDevServer啟動的可以熱更新,調試的本地服務,而在生產環境的應用去加載web應用的最終產物。

electron.main.js -> createWindow() line34
app.whenReady().then(createWindow)

最後,在Electron主進程執行完成自己內部的初始化邏輯後,我們在其暴露出的"whenReady"方法後開啟應用。

開發模式

在開發模式中,我們需要做兩件事:

1. 啟動Electron主進程邏輯

2. 構建Electron渲染進程所渲染的web應用

在package.json中加入開發環境所需要的腳本:

package.json line4 - line8
"main": "main.electron.js",
"scripts": {
"start": "NODE_ENV=development concurrently \"npm run serve\" \"npm run electron \"",
"serve": "node script/serve.js",
"electron": "electron . ",
},

其中,serve命令是通過webpackDevServer啟動了一個前端頁面的服務,electron命令即是通過main字段對應的文件作為主進程開啟一個Electron應用。我們將整體邏輯合並到start命令中,由於主進程啟動和web應用的開發服務構建不存在依賴耦合性,所以我們加入了concurrently幫助我們同時執行兩個命令。

這時候大傢可能會有疑問:如果Electron服務啟動好的時候,web應用還沒有構建完成該怎麼辦呢?我們在介紹main.electron.js的時候遺漏了一段邏輯介紹,位於createWindow函數中:

electron.main.js -> createWindow() line19 - line24
/*
在執行 npm start 後,經常會窗口已經顯示出來了,
但代碼還未構建好,此時捕獲到 did-fail-load 事件,在之後延遲重載
*/
win.webContents.on('did-fail-load', function () {
console.log(`createWindow: did-fail-load, reload soon...`)
setTimeout(() => {
win.reload()
}, 1000)
})

通過捕獲加載失敗的事件,每隔1s去重新請求一下web應用的本地開發服務,直到服務完成構

建啟動,正所謂“讓子彈飛一會兒”。最終運行效果如下:

可以看到,在視圖文件中成功讀取到了content.txt文件中的內容,Electron渲染層對於Node.js的Api調用能力誠不欺我。

調試

通過Cmd Shift I, 也可以像瀏覽器一樣打開調試者工具,使用方式和Chrome體驗完全一致,可以進行dom節點,樣式,網絡請求查看操作。在運行時,也可提通過渲染進程實例的
win.webContents.openDevTools( ) 進行調試工具的打開。

構建打包

關於electron應用的打包,目前主流的方式是使用electron-builder或者electron-packger。這裡筆者推薦使用electron-builder,隻需要簡單的一點配置即可生成不同系統可運行的安裝文件。我們來添加一點基礎的配置:

package.json line40 - line84
"build": {
"asar": false,
"appId": "electron-demo",
"icon": "public/logo.png",
"productName": "Electron示例",
"directories": {
"output": "./bin"
},
"win": {
"target": [
"nsis",
"zip"
]
},
"files": [
"dist/**/*",
"*.js",
"!node_modules"
],
"dmg": {
"sign": "false",
"contents": [
{
"x": 410,
"y": 150,
"type": "link",
"path": "/Applications"
},
{
"x": 130,
"y": 150,
"type": "file"
}
]
},
"nsis": {
"oneClick": false,
"allowElevation": true,
"allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "Electron-demo"
}
}

每個字段對應的含義可以參照 Electron-buidler配置文檔 。從簡單的產品名稱,產品圖標配置,到windows自定義安裝的復雜配置都有涵蓋,我們通過electron-builder提供的命令行工具對項目按照配置項進行構建,為了方便使用,繼續在package.json中添加打包成各個平臺可執行文件的腳本:


package.json line11 - line13
"compile:mac": "electron-builder --mac --arm64", // for macos
"compile:win64": "electron-builder --win --x64", // for win64
"compile:win32": "electron-builder --win --ia32" // for win32

由於輸出成不同操作系統的安裝包需要下載不同操作系統所依賴的文件,這裡也會涉及到資源下載的問題,這裡我們繼續使用electron-builder的鏡像進行下載:

.npmrc line3

electron_builder_binaries_mirror=https://npm.taobao.org/mirrors/electron-builder-binaries/

我們以構建Mac平臺的產出為例

npm run complie:mac

最後產物如下:

ok,熟悉的dmg安裝包,大功告成!

總結

Electron作為一個將node.js和chromium結合的框架大大拓展了Javascript調用系統Api的能力,多端復用的特性也可以有效減少多平臺開發成本,有興趣的同學不妨一試。

參考資料:

Electron主進程和渲染進程

Electron-builder打包

作者:王上明

來源:微信公眾號:貝殼產品技術

出處
:https://mp.weixin.qq.com/s/v3D-Cv2B1VJpOMGaZJk1VQ