## Electron 快速开始
参考文档:https://www.electronjs.org/zh/docs/latest/tutorial/quick-start
### 初始化项目
```bash
mkdir 73biji && cd 73biji
yarn init
yarn add --dev electron
```
### 配置 package.json
在您的 `package.json` 配置文件中的 `scripts` 字段下增加一条 `start` 命令:
```json
{
"scripts": {
"start": "electron ."
}
}
```
### 启动应用
```bash
yarn start
```
## Ubuntu 系统依赖
在 Ubuntu 上运行 Electron 应用前,需要安装以下系统依赖:
```bash
sudo apt update
sudo apt install libgbm1
sudo apt install libatk1.0-0 # 主包
sudo apt install libatk-bridge2.0-0 # 如果还提示缺 bridge
sudo apt install libgtk-3-0 libgtk2.0-0 \
libgdk-pixbuf2.0-0 libglib2.0-0 \
libx11-xcb1 libxcomposite1 \
libxcursor1 libxdamage1 \
libxrandr2 libxtst6 libnss3 \
libasound2 libdbus-1-3
```
### Ubuntu electron-packager 打包
```bash
npm install --save-dev electron-packager
./node_modules/.bin/electron-packager . 73biji --platform=linux --arch=x64
```
**注意**:vscode 环境中可以运行,出了该环境无法在ubuntu桌面运行
### 打包后的目录结构
```bash
xt@ai:~/wks$ cd /home/xt/wks/nodejs/qsbiji/73biji-linux-x64
xt@ai:~/wks/nodejs/qsbiji/73biji-linux-x64$ ls
73biji chrome_crashpad_handler libEGL.so libvk_swiftshader.so LICENSES.chromium.html resources.pak version
chrome_100_percent.pak chrome-sandbox libffmpeg.so libvulkan.so.1 locales snapshot_blob.bin vk_swiftshader_icd.json
chrome_200_percent.pak icudtl.dat libGLESv2.so LICENSE resources v8_context_snapshot.bin
```
xt@ai:~/wks/nodejs/qsbiji/73biji-linux-x64$ ./73biji
### 参考资料
- https://webpack.js.org/
## Windows 打包
### electron-squirrel-startup
`electron-squirrel-startup` 是 windows 平台上的依赖包。
### 打包步骤
#### 1. 创建项目
首先要创建 nodejs 项目并 init。
**注意**:Init 时,描述、作者等提示写的内容不能为空。
#### 2. 初始化简单项目
配置 index.js、index.html,可以 run 之后,再进行下面的打包。
#### 3. 使用 Electron Forge 打包
```bash
npm install --save-dev @electron-forge/cli
npx electron-forge import
npm run make
```
### 打包示例
打包demo:`soft/windows/kaifa/73biji.rar`
## Electron Forge 快速开始
官方文档:https://www.electronforge.io/cli#make
### 安装和创建项目
```bash
npm install --save-dev @electron-forge/cli
npx create-electron-app@latest my-app
```
## Electron Forge
官方文档:https://www.electronforge.io/
### 快速开始
```bash
npx create-electron-app@latest my-app
npx create-electron-app@latest 73biji --template=webpack
```
### 初始化项目
```bash
cd my-app
npm install --save-dev @electron-forge/cli
npx electron-forge init --template=webpack
```
**注意**:如果有 yarn.lock 文件要先删除这个文件。
### 导入和启动
```bash
npx electron-forge import
npm start
```
### GitHub 发布
```bash
npm install --save-dev @electron-forge/publisher-github
```
### Maker 配置
```javascript
// If your config is only in package.json:
// Only showing the relevant configuration for brevity
{
"config": {
"forge": {
"makers": [
{
"name": "@electron-forge/maker-zip",
"platforms": ["darwin", "linux"], // optional
"config": {
// Config here
}
}
]
}
}
}
```
### 打包命令
```bash
npm run make
```
### Package 命令
参考文档:https://www.electronforge.io/cli#package
```bash
npx electron-forge package --arch="x64" --platform="linux"
```
### 完整流程示例
```bash
npm install --save-dev @electron-forge/cli
npx electron-forge init --template=webpack
npx electron-forge import
```
基本架构
## Electron 基本架构教程
官方文档:https://www.electronjs.org/zh/docs/latest/tutorial/tutorial-prerequisites
### Electron 中的 ES 模块 (ESM)
https://www.electronjs.org/zh/docs/latest/tutorial/esm
### 使用预加载脚本来增强渲染器
Electron modules 及 Polyfilled 的全局模块
BrowserWindow 的预加载脚本运行在具有 HTML DOM 和 Node.js、Electron API 的有限子集访问权限的环境中。
#### 预加载脚本沙盒化
从 Electron 20 开始,预加载脚本默认沙盒化,不再拥有完整 Node.js 环境的访问权。
实际上,这意味着你只拥有一个 polyfilled 的 require 函数,这个函数只能访问一组有限的 API。
#### 可用的 API
**app**:控制应用程序的事件生命周期
https://www.electronjs.org/zh/docs/latest/api/app
**process**
https://www.electronjs.org/zh/docs/latest/api/process
**buffer**
https://nodejs.org/api/buffer.html
**clearImmediate**
https://nodejs.org/api/timers.html#timers_clearimmediate_immediate
**setImmediate**
https://nodejs.org/api/timers.html#timers_setimmediate_callback_args
#### Node.js 模块
**events**
https://nodejs.org/api/events.html
**timers**
https://nodejs.org/api/timers.html
**url**
https://nodejs.org/api/url.html
## webPreferences 加载 JS
### Preload 脚本
为了将脚本附在渲染进程上,在 BrowserWindow 构造器中使用 webPreferences.preload 传入脚本的路径。
```javascript
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
// 为了将脚本附在渲染进程上,在 BrowserWindow 构造器中使用 webPreferences.preload 传入脚本的路径。
preload: path.join(__dirname, 'preload.js')
}
})
```
### 配置全局变量
在 `nodejs/73biji/preload.js` 中配置全局变量:
```javascript
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron
// 除函数之外,我们也可以暴露变量
})
```
### 在页面中使用
之后可以在页面或页面中的 JS 直接使用:
```javascript
const information = document.getElementById('info')
information.innerText = `本应用正在使用 Chrome`
```
## 渲染进程与主进程
### 渲染进程
渲染:就是Html加载展示的过程
Electron模块就是渲染进程模块:
- 即Html在Electron中渲染
- 多数情况下,html是在浏览器中渲染的,可以将Electron看作一个迷你浏览器
- 所以Electron的进程也是渲染进程
### 主进程
- 项目是一个Nodejs项目,最初的入口,或者最开始的入口是一个js文件
- 通常是main.js/index.js,这就是主进程
### 在主进程中初始化Electron模块
在主进程中初始化了Electron模型,并将静态资源也传入了进去:
```javascript
// __dirname 字符串指向当前正在执行脚本的路径 (在本例中,它指向你的项目的根文件夹)。
// path.join API 将多个路径联结在一起,创建一个跨平台的路径字符串。
const createWindow = () => {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
// 为了将脚本附在渲染进程上,在 BrowserWindow 构造器中使用 webPreferences.preload 传入脚本的路径。
preload: path.join(__dirname, 'preload.js')
}
})
// 加载 index.html
mainWindow.loadFile('index.html')
// 打开开发工具
// mainWindow.webContents.openDevTools()
}
```
### 效率进程(Utility Process)
Process-specific module aliases (TypeScript)
Electron's npm package also exports subpaths that contain a subset of Electron's TypeScript type definitions:
- **electron/main** includes types for all main process modules.
- **electron/renderer** includes types for all renderer process modules.
- **electron/common** includes types for modules that can run in main and renderer processes.
These aliases have no impact on runtime, but can be used for typechecking and autocomplete.
### 导入示例
```javascript
const { app } = require('electron/main')
const { shell } = require('electron/common')
```
nodejs项目有一部分是js开发,这部分js在main.js中加载,即在主进程中加载
现在页面在Electron渲染进程中,
要想在页面中访问main.js主进程中的方法,可以进行以下设置
main.js:引入 ipcMain
const { app, BrowserWindow, ipcMain } = require('electron/main')
const path = require('node:path')
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
win.loadFile('index.html')
}
app.whenReady().then(() => {
ipcMain.handle('ping', () => 'pong')
createWindow()
})
渲染进程中在preload.js通过ipcRenderer调用main.js主进程的方法
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron,
ping: () => ipcRenderer.invoke('ping')
// 除函数之外,我们也可以暴露变量
})
renderer.js中调用
const func = async () => {
const response = await window.versions.ping()
console.log(response) // 打印 'pong'
}
func()
window-all-closed事件
// quitting the app when no windows are open on non-macOS platforms
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
app事件
The main process also controls your application's lifecycle through Electron's app module.
This module provides a large set of events and methods that you can use to
add custom application behavior (for instance, programmatically quitting your application,
modifying the application dock, or showing an About panel).
其他事件参考
https://www.electronjs.org/zh/docs/latest/api/app
进程间通信
https://www.electronjs.org/zh/docs/latest/tutorial/ipc
main.js:主进程通过ipcMain.on定义一个事件
ipcMain.on('set-title', (event, title) => {
const webContents = event.sender
console.log("webContents")
console.log(webContents)
const win = BrowserWindow.fromWebContents(webContents)
win.setTitle(title)
})
// 加载 index.html
mainWindow.loadFile('index.html')
主进程的日志会打印在IDE控制台
渲染器预加载脚本preload.js设置electronAPI.setTitle与主进程中的set-title绑定
const { contextBridge, ipcRenderer } = require('electron/renderer')
contextBridge.exposeInMainWorld('electronAPI', {
setTitle: (title) => ipcRenderer.send('set-title', title)
})
渲染器页面调用渲染器的方法
const setButton = document.getElementById('btn')
const titleInput = document.getElementById('title')
setButton.addEventListener('click', () => {
const title = titleInput.value
window.electronAPI.setTitle(title)
})
main.js
const { app, BrowserWindow, ipcMain, dialog } = require('electron/main')
const path = require('node:path')
async function handleFileOpen () {
const { canceled, filePaths } = await dialog.showOpenDialog()
if (!canceled) {
return filePaths[0]
}
}
function createWindow () {
const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
mainWindow.loadFile('index.html')
}
app.whenReady().then(() => {
ipcMain.handle('dialog:openFile', handleFileOpen)
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
使用 ipcMain.handle 监听事件
在主进程中,我们将创建一个 handleFileOpen() 函数,
它调用 dialog.showOpenDialog 并返回用户选择的文件路径值。
每当渲染器进程通过 dialog:openFile 通道发送 ipcRender.invoke 消息时,此函数被用作一个回调。
然后,返回值将作为一个 Promise 返回到最初的 invoke 调用。
preload.js
const { contextBridge, ipcRenderer } = require('electron/renderer')
contextBridge.exposeInMainWorld('electronAPI', {
openFile: () => ipcRenderer.invoke('dialog:openFile')
})
renderer.js
const btn = document.getElementById('btn')
const filePathElement = document.getElementById('filePath')
btn.addEventListener('click', async () => {
const filePath = await window.electronAPI.openFile()
filePathElement.innerText = filePath
})
出于保留历史的目地,会有一些其他的方法。我们建议尽可能使用 ipcRenderer.invoke 。
main.js中添加菜单
const menu = Menu.buildFromTemplate([
{
label: app.name,
submenu: [
{
click: () => mainWindow.webContents.send('update-counter', 1),
label: 'Increment'
},
{
click: () => mainWindow.webContents.send('update-counter', -1),
label: 'Decrement'
}
]
}
])
Menu.setApplicationMenu(menu)
click 处理函数通过 update-counter 通道向渲染器进程发送消息(1 或 -1)。
click: () => mainWindow.webContents.send('update-counter', -1)
preload.js
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
onUpdateCounter: (callback) => ipcRenderer.on('update-counter', (_event, value) => callback(value))
})
renderer.js
const counter = document.getElementById('counter')
window.electronAPI.onUpdateCounter((value) => {
const oldValue = Number(counter.innerText)
const newValue = oldValue + value
counter.innerText = newValue.toString()
})
回调
从 ipcRenderer.on 回调中将回复发送回主进程。
preload.js
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
onUpdateCounter: (callback) => ipcRenderer.on('update-counter', (_event, value) => callback(value)),
counterValue: (value) => ipcRenderer.send('counter-value', value)
})
renderer.js
const counter = document.getElementById('counter')
window.electronAPI.onUpdateCounter((value) => {
const oldValue = Number(counter.innerText)
const newValue = oldValue + value
counter.innerText = newValue.toString()
window.electronAPI.counterValue(newValue)
})
main.js
// ...
ipcMain.on('counter-value', (_event, value) => {
console.log(value) // will print value to Node console
})
// ...
https://www.electronjs.org/zh/docs/latest/tutorial/message-ports
常用功能
Electron 应用程序的结构非常相似。
作为应用开发者,你将控制两种类型的进程:主进程 和 渲染器进程。
这类似于上文所述的 Chrome 的浏览器和渲染器进程。
主进程
每个 Electron 应用都有一个单一的主进程,作为应用程序的入口点。
主进程在 Node.js 环境中运行,这意味着它具有 require 模块和使用所有 Node.js API 的能力。
窗口管理
The main process' primary purpose is to create and
manage application windows with the BrowserWindow module
BrowserWindow 类的每个实例创建一个应用程序窗口,且在单独的渲染器进程中加载一个网页。
You can interact with this web content from
the main process using the window's webContents object.
const { BrowserWindow } = require('electron')
const win = new BrowserWindow({ width: 800, height: 1500 })
win.loadURL('https://www.bing.com')
const contents = win.webContents
console.log(contents)
Note: A renderer process is also created for web embeds such as the BrowserView module.
嵌入式网页内容也可访问 webContents 对象。
由于 BrowserWindow 模块是一个 EventEmitter,
所以您也可以为各种用户事件 ( 例如,最小化 或 最大化您的窗口 ) 添加处理程序。
当一个 BrowserWindow 实例被销毁时,与其相应的渲染器进程也会被终止。
参考