【electron】electron 入门指引

electron 入门指引

一、什么是electron

Electron 是一个开源的框架,用于构建跨平台的桌面应用程序。它允许开发者使用常见的 Web 技术,如 HTML、CSS 和 JavaScript,来构建原生级别的桌面应用程序,而无需学习额外的桌面开发语言或工具。

二、electron特性

跨平台性

Electron 允许开发者使用一套代码构建同时运行在多个平台上的桌面应用程序,包括 Windows、macOS 和 Linux。这种跨平台性使得开发者能够在不同操作系统下开发和部署应用程序,大大提高了开发效率和应用程序的可用性。

基于 Chromium 和 Node.js

Electron 是基于开源的 Chromium 浏览器引擎和 Node.js 运行时环境构建的。它利用了这两个强大的平台,使得开发者可以在应用程序中使用最新的 Web 技术,并且可以轻松地访问本地系统资源和操作系统功能。

使用 Web 技术

开发 Electron 应用程序与开发网页应用程序非常相似,开发者可以使用熟悉的 Web 技术,如 HTML、CSS 和 JavaScript,来构建桌面应用程序的用户界面和交互逻辑。此外,开发者还可以使用流行的 Web 框架和库,如 React、Vue.js 和 Angular 等,来构建复杂的用户界面。

原生级别的体验

虽然 Electron 应用程序是使用 Web 技术构建的,但它们提供了与原生桌面应用程序相似的用户体验和性能。开发者可以利用 Electron 提供的丰富的 API 来访问本地系统资源和操作系统功能,例如文件系统、窗口管理、系统通知、系统菜单等,从而为用户提供原生级别的体验。

社区和生态系统

Electron 拥有一个庞大的开发者社区和丰富的生态系统,提供了大量的第三方库、工具和插件,以帮助开发者构建和部署高质量的桌面应用程序。开发者可以从社区中获取支持和资源,解决开发中遇到的问题,并且分享经验和技术。

三、新建一个项目

使用Electron Forge

  • 首先,您需要全局安装 Electron Forge,这样您就可以在命令行中使用它的命令行工具

    1
    npm install -g @electron-forge/cli
  • 在命令行中,进入您的项目目录,然后运行以下命令来初始化一个新的 Electron 项目

    1
    electron-forge init my-electron-app
  • 进入项目目录,并安装依赖

    1
    2
    cd my-electron-app
    npm install
  • 使用以下命令在开发模式下运行应用程序

    1
    npm start
  • 当您准备好发布应用程序时,可以使用 Electron Forge 打包应用程序。运行以下命令

    1
    npm run make

四、核心 api

app 模块

app 模块是 Electron 应用程序的入口点,负责控制应用程序的生命周期和事件处理。
可以使用 app 模块来监听应用程序的各种事件,如 ready、window-all-closed、activate 等。
还可以使用 app 模块来设置应用程序的基本信息,如应用程序名称、图标、版本号等。

index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
const { app, ipcMain, dialog, Notification } = require("electron"); // 引入 Electron 模块
const { createMenu } = require("./menu"); // 引入菜单模块
const { createWindow } = require("./window"); // 引入窗口模块
const { createTray } = require("./tray"); // 引入托盘模块

let mainWindow = null; // 声明一个变量来存储主窗口实例

// 处理主进程接收到的消息
const handleMainMessage = async (event, data) => {
// 创建通知
const notification = new Notification({
title: "Jeffrey的城堡",
body: data,
});
notification.show(); // 显示通知

// 显示文件选择对话框
const result = await dialog.showOpenDialog({
title: "选择文件",
defaultPath: app.getPath("home"), // 设置默认打开路径为用户的主目录
filters: [
{ name: "Text Files", extensions: ["txt"] }, // 只允许选择文本文件
{ name: "All Files", extensions: ["*"] }, // 允许选择所有文件
],
properties: ["openFile"], // 只允许选择文件,不允许选择文件夹
});

// 如果用户选择了文件,则向渲染进程发送文件路径
if (!result.canceled) {
const filePath = result.filePaths[0];
mainWindow.webContents.send("file-selected", filePath); // 发送文件路径给渲染进程
}

return '网页,你的来信我收到了! 签名壳'; // 返回一个示例值
};

// 处理第二个实例
const handleSecondInstance = () => {
if (mainWindow) {
if (mainWindow.isMinimized()) {
mainWindow.restore(); // 如果窗口被最小化,则还原窗口
} else {
mainWindow.focus(); // 如果窗口已打开,则聚焦到窗口
}
}
};

// 监听第二个实例事件
app.on("second-instance", handleSecondInstance);

// 应用程序准备就绪时执行
app.on("ready", () => {
// 注册消息处理程序
ipcMain.handle("main-message", handleMainMessage); // 处理主进程接收到的消息

// 创建窗口、菜单和托盘
mainWindow = createWindow(); // 创建主窗口实例
createMenu(mainWindow); // 创建菜单
createTray(mainWindow); // 创建托盘
});

// 监听所有窗口关闭事件
app.on("window-all-closed", () => {
// 在 macOS 上,除非用户显式退出,否则应用程序保持活动状态
if (process.platform !== "darwin") {
app.quit(); // 在非 macOS 平台上退出应用程序
}
});

// 监听激活事件
app.on("activate", () => {
// 在 macOS 上,当所有窗口关闭时且用户点击 Dock 图标时,重新创建窗口
if (!mainWindow) {
mainWindow = createWindow(); // 重新创建主窗口实例
}
});

这段代码是一个 Electron 主进程的脚本,负责处理应用程序的启动、窗口创建、消息通信以及对话框的显示。其中包括了创建主窗口实例、处理主进程接收到的消息、监听窗口关闭和激活事件等功能。

BrowserWindow 模块

BrowserWindow 模块用于创建和控制应用程序的窗口。
可以使用 BrowserWindow 类创建新的窗口,并设置窗口的大小、位置、标题、图标等属性。
还可以使用 BrowserWindow 类来加载 Web 内容,如 HTML 文件、URL 或者远程网页。

window.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
const { BrowserWindow, screen } = require("electron"); // 引入 BrowserWindow 和 screen 模块
const path = require("path"); // 引入 path 模块
const { getIsQuitting } = require("./quitting"); // 引入 quitting 模块中的 getIsQuitting 函数

// 创建窗口的函数
const createWindow = () => {
// 获取屏幕的宽度和高度
const { width, height } = screen.getPrimaryDisplay().workAreaSize;
// 计算窗口的宽度和高度为屏幕宽度和高度的 80%
const windowWidth = Math.floor(width * 0.8);
const windowHeight = Math.floor(height * 0.8);

// 创建 BrowserWindow 实例
const win = new BrowserWindow({
width: windowWidth,
height: windowHeight,
webPreferences: {
nodeIntegration: true, // 启用 Node.js 整合
preload: path.join(__dirname, 'preload.js') // 设置 preload 脚本路径
},
icon: path.join(__dirname, "icon.png"), // 设置窗口图标路径
// autoHideMenuBar: true, // 设置隐藏菜单栏
});

// 加载指定 URL
win.loadURL("http://localhost:8080/");

// 设置窗口标题
win.webContents.on("did-finish-load", () => {
win.setTitle("Jeffrey的魔法城堡");
});

// 监听窗口关闭事件
win.on("close", (event) => {
// 如果不是正在退出状态,则阻止默认关闭行为并隐藏窗口
if (!getIsQuitting()) {
event.preventDefault();
win.hide();
}
});

// 打开开发者工具(仅用于调试)
// win.webContents.openDevTools();

return win; // 返回窗口实例
};

module.exports = { createWindow }; // 导出 createWindow 函数

于创建一个 Electron 窗口,并设置窗口的大小、图标、加载页面的 URL,以及监听窗口关闭事件并做出相应处理,同时注入了一个预加载脚本preload.js。

ipcMain 和 ipcRenderer 模块

ipcMain 和 ipcRenderer 模块用于在主进程和渲染进程之间进行通信。
主进程可以使用 ipcMain 模块监听来自渲染进程的事件,并发送消息给渲染进程。
渲染进程可以使用 ipcRenderer 模块发送消息给主进程,并监听来自主进程的事件。

使用preload.js

preload.js 是 Electron 中用于在渲染进程(网页)加载之前执行的脚本。它的作用是在渲染进程中注入一些预定义的变量、函数或者模块,以便在渲染进程中直接使用这些变量、函数或者模块,而不需要在渲染进程中再次引入。

preload.js

1
2
3
4
5
6
7
8
9
const { contextBridge, ipcRenderer } = require('electron');

// 使用 contextBridge 将 ipcRenderer 暴露给渲染进程
contextBridge.exposeInMainWorld('electronSDK', {
// 定义 upload 函数,向主进程发送消息并接收返回值
upload: async (data) => {
return await ipcRenderer.invoke('main-message', data); // 调用主进程的 'main-message' 事件并传递数据
},
});

这段代码是在渲染进程中使用 Electron 的 contextBridge 模块,将主进程中的 ipcRenderer 对象暴露给渲染进程,以便渲染进程可以安全地与主进程进行通信。通过 contextBridge.exposeInMainWorld 方法,将名为 electronSDK 的对象暴露给渲染进程的全局作用域,其中包含了一个名为 upload 的函数。这个函数用于向主进程发送消息,并等待主进程返回结果。在内部,它调用了 ipcRenderer.invoke 方法,向主进程发送名为 main-message 的事件,并传递数据 data,然后返回事件处理结果。

网页端

1
2
3
4
5
window.electronSDK
.upload('您好,壳! 签名网页')
.then((res) => {
console.log(res)
})

这段代码是在渲染进程中调用 Electron 主进程中预加载的名为 electronSDK 的对象的 upload 方法。该方法用于向主进程传递消息,并期望返回一个 Promise 对象。当主进程处理完消息后,会将结果返回给渲染进程,并在 Promise 对象的 then 方法中获取到返回的结果 res,然后进行相应的处理,例如打印日志。

Menu 和 MenuItem 模块用于创建和管理应用程序的菜单和上下文菜单。
可以使用 Menu 类创建菜单栏、应用程序菜单和上下文菜单,并添加菜单项和子菜单。
MenuItem 类表示菜单项,可以设置菜单项的标签、图标、快捷键等属性。

menu.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
const { Menu, app, BrowserWindow } = require("electron");
const { setIsQuitting } = require("./quitting"); // 引入 setIsQuitting 函数

// 创建菜单的函数
const createMenu = () => {
// 菜单模板
const menuTemplate = [
{
label: "应用",
submenu: [
{
label: "刷新",
accelerator: "CmdOrCtrl+R", // 设置快捷键
click: () => { // 刷新菜单项的点击事件
BrowserWindow.getFocusedWindow().reload(); // 获取焦点的窗口并刷新
},
},
{ type: "separator" }, // 分隔线
{
label: "退出",
accelerator: "CmdOrCtrl+Q", // 设置快捷键
click: () => { // 退出菜单项的点击事件
setIsQuitting(true); // 设置正在退出标志为 true
app.quit(); // 退出应用程序
},
},
],
},
];

// 根据菜单模板创建菜单
const menu = Menu.buildFromTemplate(menuTemplate);
// 设置应用程序菜单
Menu.setApplicationMenu(menu);
};

module.exports = { createMenu };

这段代码用于创建 Electron 应用程序的菜单,其中包含了刷新和退出两个功能,分别对应快捷键 “CmdOrCtrl+R” 和 “CmdOrCtrl+Q”,并在退出时设置了一个退出标志。

dialog 模块

dialog 模块用于显示系统对话框,如打开文件对话框、保存文件对话框、警告框、错误框等。
可以使用 dialog 模块打开系统对话框,并获取用户的输入或选择。

临时存放在index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 显示文件选择对话框
const result = await dialog.showOpenDialog({
title: "选择文件",
defaultPath: app.getPath("home"), // 设置默认打开路径为用户的主目录
filters: [
{ name: "Text Files", extensions: ["txt"] }, // 只允许选择文本文件
{ name: "All Files", extensions: ["*"] }, // 允许选择所有文件
],
properties: ["openFile"], // 只允许选择文件,不允许选择文件夹
});

// 如果用户选择了文件,则向渲染进程发送文件路径
if (!result.canceled) {
const filePath = result.filePaths[0];
mainWindow.webContents.send("file-selected", filePath); // 发送文件路径给渲染进程
}

return '网页,你的来信我收到了! 签名壳'; // 返回一个示例值

网页端

1
2
3
4
5
// 监听 "file-selected" 事件
window.electronSDK.on("file-selected", (filePath) => {
// 在这里处理接收到的文件路径
console.log("文件路径" + filePath);
});

shell 模块

shell 模块用于执行一些系统级别的操作,如打开外部链接、打开文件、拷贝文件等。
可以使用 shell 模块打开外部链接、文件、文件夹,并执行一些系统默认的操作。

1
2
3
4
const { shell, dialog } = require('electron');

// 打开外部链接
shell.openExternal('https://www.example.com');
1
2
3
4
5
6
7
8
9
10
11
12
13
// 打开文件对话框
dialog.showOpenDialog({ properties: ['openFile'] })
.then(result => {
if (!result.canceled) {
const filePath = result.filePaths[0];
// 在这里使用 filePath 打开文件,例如使用 fs 模块读取文件内容
}
})
.catch(err => {
console.error(err);
});

const fs = require('fs');
1
2
3
4
5
6
7
8
9
10
11
// 拷贝文件
const sourcePath = 'path/to/source/file.txt';
const destinationPath = 'path/to/destination/file.txt';

fs.copyFile(sourcePath, destinationPath, (err) => {
if (err) {
console.error('拷贝文件时出错:', err);
return;
}
console.log('文件已成功拷贝');
});

五、完整案例

目录结构

  • src
    • icon.png
    • index.js
    • menu.js
    • preload.js
    • quitting.js
    • tray.js
    • window.js
  • forge.config.js

前面示例中,已经提供了index.js、menu.js、preload.js、window.js文件,这里仅补充其他文件

quitting.js

1
2
3
4
5
6
7
8
9
10
11
12
13
// quitting.js

let isQuitting = false;

function setIsQuitting(value) {
isQuitting = value;
}

function getIsQuitting() {
return isQuitting;
}

module.exports = { setIsQuitting, getIsQuitting };

tray.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// tray.js

const { app, Menu, Tray } = require("electron");
const path = require("path");
const { setIsQuitting } = require("./quitting");

let tray = null;

function createTray(window) {
tray = new Tray(path.join(__dirname, "icon.png"));
const contextMenu = Menu.buildFromTemplate([
{
label: "退出",
type: "normal",
click: () => {
setIsQuitting(true);
app.quit();
},
},
]);
tray.setToolTip("Jeffrey的魔法城堡");
tray.setContextMenu(contextMenu);

tray.on("double-click", () => {
window.show(); // 双击托盘图标时显示窗口
});
}

module.exports = { createTray };

forge.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const { FusesPlugin } = require('@electron-forge/plugin-fuses'); // 引入 Electron Forge 的 Fuses 插件
const { FuseV1Options, FuseVersion } = require('@electron/fuses'); // 引入 Fuse 相关配置和选项

module.exports = {
packagerConfig: {
asar: true, // 打包应用程序时启用 asar 打包
icon: 'src/icon.png', // 指定打包后应用程序的图标路径
platform: 'win32', // 打包目标平台为 Windows
arch: 'x64', // 打包目标架构为 64 位
// 其他打包配置...
},
rebuildConfig: {}, // 重建配置为空对象
makers: [
{
name: '@electron-forge/maker-squirrel', // 使用 Squirrel 进行打包
config: {}, // Squirrel 打包配置为空对象
},
{
name: '@electron-forge/maker-zip', // 使用 Zip 进行打包
platforms: ['darwin', 'win32'], // 指定支持的平台为 macOS 和 Windows
}
],
plugins: [
{
name: '@electron-forge/plugin-auto-unpack-natives', // 使用自动解压原生模块插件
config: {}, // 自动解压原生模块配置为空对象
},
// Fuses 用于在打包前启用/禁用各种 Electron 功能,并在应用程序签名之前
new FusesPlugin({ // 实例化 Fuses 插件并传入配置对象
version: FuseVersion.V1, // 使用 Fuse 版本 1
[FuseV1Options.RunAsNode]: false, // 禁用作为 Node.js 运行
[FuseV1Options.EnableCookieEncryption]: true, // 启用 Cookie 加密
[FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false, // 禁用 Node.js 选项环境变量
[FuseV1Options.EnableNodeCliInspectArguments]: false, // 禁用 Node.js CLI 检查参数
[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true, // 启用嵌入式 asar 完整性验证
[FuseV1Options.OnlyLoadAppFromAsar]: true, // 仅从 asar 加载应用程序
}),
],
};

六、功能演示

网页与壳通信

使用托盘

使用安装包


喜欢这篇文章?打赏一下支持一下作者吧!
【electron】electron 入门指引
https://www.cccccl.com/20230702/electron/electron 入门指引/
作者
Jeffrey
发布于
2023年7月2日
许可协议