[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 提供的 ipcMain
和 ipcRenderer
。
暴露 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>