跳至主要内容

[note] electron 筆記

概念

在 Electron 中分成 main process 和 renderer process:

  • renderer process:就像是作用在瀏覽器中的頁面一樣,它沒辦法直接取用到 Node.js 提供的 API
  • main process:就如同在 Node.js,它也沒辦法直接取用 DOM API
  • preload:另外有一個特別的橋樑, 稱作 preload,可以透過 webPreferences.preload 來設定,它能夠同時存取 Node.js API(main process)和 DOM(renderer process)

main process

一般取名為 main.js 會是 electron 自己執行的檔案:

  • 在 Node.js 的環境中執行,可以控制 App 的生命週期、作業系統有關的 UI 和操作、管理 renderer process 等等
main process
const { app, BrowserWindow } = require('electron');
const path = require('path');

// Opening your web page in a browser window
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
// 載入 preload script
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
});

// 載入 renderer process
win.loadFile('index.html');
};

app.whenReady().then(() => {
// Browser windows can only be created after the app module's ready event is fired
createWindow();

// Open a window if none are open (macOS)
// Because windows cannot be created before the ready event,
// you should only listen for activate events after your app is initialized
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});

// On Windows and Linux, exiting all windows generally quits an application entirely.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});

renderer

index.html 中載入的 JavaScript scripts 只能使用瀏覽器的功能,不能使用 Node.js 的操作:

main process
// main.js
const createWindow = () => {
// ...
const win = new BrowserWindow({
/* ... */
});

// 載入 renderer process
win.loadFile('index.html');
};
renderer process
<!-- index.html -->
<!doctype html>
<html>
<!-- ... -->
<body>
<!-- 這裡載入的 scripts 屬於 renderer process -->
<script src="renderer.js"></script>
</body>
</html>

preload

  • Preload scripts 是可以同時存取到 renderer(Browser DOM APIs)和 main(Node.js)的程式
  • Preload scripts 會在載入 Web Page 被載入 brower window 前執行
  • 透過在 preload scripts 中使用 contextBridge API 來建立 main process 和 renderer process 間的溝通
main process
// main.js
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
// 載入 preload script
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
});

// ...
};
preload script: 可以同時使用 renderer (window) 和 main (Node.js) 有關的 API
// preload.js
window.addEventListener('DOMContentLoaded', () => {
const replaceText = (selector, text) => {
const element = document.getElementById(selector);
if (element) element.innerText = text;
};

for (const dependency of ['chrome', 'node', 'electron']) {
replaceText(`${dependency}-version`, process.versions[dependency]);
}
});

共用資料

透過 contextBridge.exposeInMainWorld 可以暴露變數,讓變數在主程序(main process)和轉譯器程序(renderer process)間溝通:

建立要在主程序和 renderer 間可以互相溝通的變數,這裡變數名稱是 versions

preload script
// preload.js
const { contextBridge } = require('electron');

// 建立名稱 versions 的物件,這個物件可以在 renderer 被取用
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron,
});

在 main process 註冊 preload:

main process
// main.js
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
});

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

接著在 renderer process 中,即可存取到這個變數:

renderer process
<!-- index.html -->
<html>
<body>
<div id="info"></div>
<script src="./renderer.js"></script>
</body>
</html>
renderer process
// renderer.js
const information = document.getElementById('info');

information.innerText = `
This app is using Chrome (v${versions.chrome()}),
Node.js (v${versions.node()}),
and Electron (v${versions.electron()})
`;

互相溝通

如果要讓 main process 以及 renderer process 間能夠溝通,就需要使用 Electron 提供的 ipcMainipcRenderer

暴露 invoke('ping') 的方法到 renderer:

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

contextBridge.exposeInMainWorld('versions', {
// ...

// 暴露 invoke('ping') 的方法到 renderer
ping: () => ipcRenderer.invoke('ping'),
});

main.js 處理收到事件時的動作(event handler):

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

const createWindow = () => {
const win = new BrowserWindow({
// ...
});

ipcMain.handle('ping', () => 'pong');

// ...
};

在 renderer process 則可以透過呼叫這個方法來與 main process 溝通:

renderer process
// renderer.js
const func = async () => {
const response = await window.versions.ping();
console.log(response); // prints out 'pong'
};

func();

App 打包

使用 Electron Forge 是最簡單的方式。

# https://www.electronjs.org/docs/latest/tutorial/quick-start#package-and-distribute-your-application
$ npm install --save-dev @electron-forge/cli

# Set up Forge's scaffolding
$ npx electron-forge import

# 建立可以被執行 App
$ npm run make

Electron Forge

官方連結
# 進入開發模式,輸入 "rs" 則會重啟 app
$ npm start -- --enable-logging --inspect-electron

小技巧筆記

避免使用者縮放應用程式頁面

keywords: zoom
const { webFrame } = require('electron');

webFrame.setZoomFactor(1);
webFrame.setVisualZoomLevelLimits(1, 1);
webFrame.setLayoutZoomLevelLimits(0, 0);

Disable Zoom @ Electron Github Issues

Kill Electron App

使用 ps aux 找出 Electron 的 PID 後,再使用 kill -9 將它刪除:

$ ps aux | grep electron
$ kill -9 <PID>