Electron
Electron是什么
electron可以使用前端技術開發桌面應用,跨平臺性,開發一套應用,可以打包到三個平臺。
electron結合Chromium(谷歌內核)和 Node.js 和Native Api
當使用 Electron 時,很重要的一點是要理解 Electron 不是一個 Web 瀏覽器。 它允許您使用熟悉的 Web 技術構建功能豐富的桌面應用程序
官網:簡介 | Electron
Electron廣泛應用
使用Electron框架開發的知名軟件。
Visual Studio Code、GithubDesktop、Postman、DockerDesktop...
Electron進程模型
主進程(main.js)。運行在node環境
一個應用只會有一個主進程,負責應用的生命周期、展示原生窗口、執行特殊操作和管理渲染進程。
渲染進程(render.js ),運行在瀏覽器環境
一個應用可以有多個渲染進程,渲染器進程負責展示圖形內容。
快速上手
環境安裝
node環境(v20.13.1)、npm環境(10.5.2)
測試安裝是否成功,按下【win+R】鍵,輸入cmd,打開cmd窗口
輸入:node -v // 顯示node.js版本
? npm -v // 顯示npm版本
npm config set registry https://registry.npmmirror.com/
npm i electron -D
main.js
main 文件是 Electron 應用的入口。 這個文件控制 主程序 (main process),它運行在 Node.js 環境里,負責控制您應用的生命周期、顯示原生界面、執行特殊操作并管理渲染器進程 (renderer processes)
const { app, BrowserWindow, ipcMain, Notification, Menu } = require('electron/main')
const path = require('node:path')
const fs = require('fs')function writeFile(_, data) {fs.writeFileSync('D:/hello.txt', data)
}function readFile() {const res = fs.readFileSync('D:/hello.txt').toString();return res
}
const createWindow = () => {const win = new BrowserWindow({width: 1000,height: 800,icon: path.join(__dirname, 'favicon.ico'),title: '簡單網頁',webPreferences: {preload: path.join(__dirname, 'preload.js')}})ipcMain.on('file-save', writeFile)ipcMain.handle('file-read', readFile)//自定義菜單項let menuTemp = [{label: '文件',submenu: [{label: '打開文件',click() {console.log('打開一個具體的文件')}},{ label: '打開文件夾' },{label: '關于',role: 'about'}]},{ label: '編輯' }]//生成自定義菜單let menu = Menu.buildFromTemplate(menuTemp)Menu.setApplicationMenu(menu)win.loadFile('index.html')// 創建并顯示通知const notification = new Notification({title: '主進程通知',body: '恭喜你,學會了求雨之術,風來~雨來~'}).show();// 確保在窗口創建后調用 openDevTools//win.webContents.on('did-finish-load', () => {// win.webContents.openDevTools();//});// 定時發送時間給渲染進程(每1秒)setInterval(() => {if (win && !win.isDestroyed()) {win.webContents.send('main-time', new Date().toLocaleTimeString());}}, 1000);
}
app.whenReady().then(createWindow)
index.html
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';" />
</head><body><div id="time">當前時間:加載中...</div><div class="hint">注意:輸入內容,可以保存到d:/hello.txt,點擊讀取,可以讀取該文件內容</div><input id="input" type="text"><button id="btn2">向D盤輸入hello.txt</button><br><br><hr><button id="btn3">讀取D盤hello.txt</button><script type="text/javascript" src="./render.js"></script>
</body></html>
render.js
const timeElement = document.getElementById('time');
const btn2 = document.getElementById('btn2');
const btn3 = document.getElementById('btn3');
const input = document.getElementById('input');btn2.onclick = () => {myAPI.saveFile(input.value)
}btn3.onclick = async () => {let data = await myAPI.readFile()alert(data)
}// 監聽主進程發送的時間消息
myAPI.onMainTime((time) => {timeElement.textContent = `當前時間:${time}`;
});
preload.js
preload.js執行時機:預加載腳本在渲染器加載網頁之前注入
預加載腳本preload.js(中間人),它在渲染進程運行(瀏覽器環境),人家也能訪問一部分的node api。
const { contextBridge, ipcRenderer } = require('electron')contextBridge.exposeInMainWorld('myAPI', {saveFile: (data) => {ipcRenderer.send('file-save', data)},readFile: () => {return ipcRenderer.invoke('file-read')},// 監聽主進程發送的時間消息onMainTime: (callback) => {ipcRenderer.on('main-time', (event, time) => callback(time))}
})
ipc進程通信
remote模塊
Electron 官方已經明確表示,remote
?模塊是一個遺留的 API,不建議在新項目中使用。 他們鼓勵開發者遷移到 Context Bridge。
remote
?模塊安全風險:?正如之前提到的,remote
?模塊允許渲染進程直接訪問主進程的幾乎所有對象,這帶來了嚴重的安全風險。 惡意代碼可能會利用?remote
?模塊來執行任意代碼,甚至控制整個應用程序。
替代方案: Context Bridge (結合?ipcRenderer
?和?ipcMain
) 提供了更安全、更可控的進程間通信方式。
[渲染進程]調用[主進程] 無返回
需求:比如在頁面上向d盤的文件寫內容
主進程 ipcMain.on,寫在app.whenReady().then()中
ipcMain.on('file-save', writeFile)function writeFile(_, data) {fs.writeFileSync('D:/hello.txt', data)
}
ipcRender.send?通過預加載腳本暴露了一個單行的 saveFile函數。
const { contextBridge, ipcRenderer } = require('electron')contextBridge.exposeInMainWorld('myAPI', {saveFile: (data) => {ipcRenderer.send('file-save', data)}})
最后在渲染器進程,也就是HTML頁面就可以使用暴露出來的saveFile方法了
const btn2 = document.getElementById('btn2');
const input = document.getElementById('input');btn2.onclick = ()=> {myAPI.saveFile(input.value)
}
[渲染進程]調用[主進程] 有返回
雙向 IPC 的一個常見應用是從渲染器進程碼調用代主進程模塊并等待結果,更適合請求-響應模式
需求:在頁面上讀取d盤的文件內容,點擊按鈕彈出d盤hello.txt文件內容
主進程 ipcMain.handle,寫在app.whenReady().then()中
ipcMain.handle('file-read', readFile)function readFile() {const res = fs.readFileSync('D:/hello.txt').toString();return res
}
ipcRender.invoke 通過預加載腳本暴露了一個單行的 readFile函數。
const { contextBridge, ipcRenderer } = require('electron')contextBridge.exposeInMainWorld('myAPI', {readFile: () => {return ipcRenderer.invoke('file-read')}})
最后在渲染器進程,也就是HTML頁面就可以使用暴露出來的readFile方法了
const btn3 = document.getElementById('btn3');
btn3.onclick = async () => {let data = await myAPI.readFile()alert(data)
}
[主進程]調用[渲染進程]
需求:頁面顯示當前時間,每1秒更新一次,來自主進程定時推送
主進程:webContents.send
? 發送消息
// 定時發送時間給渲染進程(每1秒)setInterval(() => {if (win && !win.isDestroyed()) {win.webContents.send('main-time', new Date().toLocaleTimeString());}}, 1000);
ipcRenderer.on?
預加載腳本監聽主進程消息,
暴露出onMainTime函數給渲染進程
callback本質是渲染進程向預加載腳本“注冊”的一個“數據接收器”,讓預加載腳本能把主進程的數據“交給”渲染進程處理。
const { contextBridge, ipcRenderer } = require('electron')contextBridge.exposeInMainWorld('myAPI', {// 監聽主進程發送的時間消息onMainTime: (callback) => {ipcRenderer.on('main-time', (event, time) => callback(time))}
})
渲染進程
渲染進程調用onMainTime時傳遞了一個callback
const timeElement = document.getElementById('time');//監聽主進程發送的時間消息
myAPI.onMainTime((time) => {timeElement.textContent = `當前時間:${time}`;
});
打包應用
electron-builder
需要梯子
安裝打包工具
npm install electron-builder -D
打包應用 electron-builder(做很多自定義的東西)
愉快執行:npm run build,安裝包在dist目錄,74MB,安裝后包含node和chromiun
npm run build
electron-forge
安裝打包工具
npm install --save-dev @electron-forge/cli
npx electron-forge import
執行打包
npm run make
執行成功后,默認輸出目錄為out/make
,里面會生成平臺對應的分發文件,例如:
- Windows:
out/make/squirrel.windows/x64/my-app-1.0.0 Setup.exe
(安裝包)、my-app-1.0.0-full.nupkg
(更新包)。 - macOS:
out/make/dmg/my-app-1.0.0.dmg
(磁盤鏡像)、out/make/zip/my-app-1.0.0.zip
(壓縮包)。 - Linux:
out/make/deb/x64/my-app_1.0.0_amd64.deb
(Debian包)、out/make/rpm/x64/my-app-1.0.0-1.x86_64.rpm
(RPM包)。