文章目录
-
- 概要
- [elctron 生命周期及窗口应用](#elctron 生命周期及窗口应用)
- 主进程与渲染进程交互
- 技术细节
- [electron 中需要注意的安全问题](#electron 中需要注意的安全问题)
概要
一、Electron简介
Electron是一个开源框架,它允许开发者使用JavaScript、HTML和CSS构建跨平台的桌面应用程序。它基于Chromium(谷歌浏览器的开源核心)和Node.js,这意味着开发者可以利用前端和后端的技术来创建功能丰富的桌面软件,像著名的Visual Studio Code就是用Electron构建的。
二、应用安装
- 开发环境搭建
- 首先,需要安装Node.js,因为Electron依赖于Node.js的模块管理和脚本运行环境。可以从Node.js官方网站(https://nodejs.org/)下载适合操作系统的安装包进行安装。安装完成后,通过命令行工具(如Windows的命令提示符或PowerShell,Mac和Linux的终端)输入`node -v
和
npm -v`来验证是否安装成功,这两个命令分别会显示Node.js和npm(Node.js的包管理器)的版本号。 - 然后,通过npm来安装Electron。可以在项目目录下运行
npm install electron
命令来安装Electron。也可以使用yarn
(另一种流行的包管理器),通过yarn add electron
进行安装。
- 首先,需要安装Node.js,因为Electron依赖于Node.js的模块管理和脚本运行环境。可以从Node.js官方网站(https://nodejs.org/)下载适合操作系统的安装包进行安装。安装完成后,通过命令行工具(如Windows的命令提示符或PowerShell,Mac和Linux的终端)输入`node -v
- 创建和运行基本应用
- 安装好Electron后,可以创建一个简单的Electron应用。首先创建一个项目目录,在目录下创建
main.js
(主进程文件)、index.html
(渲染进程文件)和package.json
(项目配置文件)。 - 在
main.js
中,代码示例如下:
- 安装好Electron后,可以创建一个简单的Electron应用。首先创建一个项目目录,在目录下创建
javascript
const { app, BrowserWindow } = require('electron');
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600
});
win.loadFile('index.html');
}
app.whenReady().then(() => {
createWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
app.on('window - all - closed', () => {
if (process.platform!== 'darwin') {
app.quit();
}
});
- 在
index.html
中,可以编写简单的HTML代码,如:
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF - 8">
<title>My Electron App</title>
</head>
<body>
<h1>Hello, Electron!</h1>
</body>
</html>
- 在
package.json
中,添加启动脚本,例如:
json
{
"name": "my - electron - app",
"version": "1.0.0",
"main": "main.js",
"scripts": {
"start": "electron."
},
"devDependencies": {
"electron": "^27.0.0"
}
}
- 然后在项目目录下通过命令行运行
npm start
或者yarn start
,就可以启动应用程序,看到一个简单的窗口显示"Hello, Electron!"。
三、页面交互
- 主进程和渲染进程通信
- Electron应用有主进程和渲染进程之分。主进程负责管理应用的生命周期、创建和管理窗口等,渲染进程负责展示页面内容。它们之间需要通信来实现功能。
- 例如,从渲染进程向主进程发送消息。在渲染进程的
index.html
中添加一个按钮:
html
<button id="myButton">Send Message to Main</button>
- 在对应的JavaScript代码(可以在
index.html
中通过<script>
标签引入,或者作为一个单独的.js
文件引入)中:
javascript
const { ipcRenderer } = require('electron');
const myButton = document.getElementById('myButton');
myButton.addEventListener('click', () => {
ipcRenderer.send('message - from - renderer',
'This is a message from the renderer process');
});
- 在主进程
main.js
中接收消息:
javascript
const { ipcMain } = require('electron');
ipcMain.on('message - from - renderer', (event, arg) => {
console.log(arg);
// 可以在这里根据接收到的消息进行其他操作,比如更新窗口标题等
const currentWindow = BrowserWindow.getFocusedWindow();
if (currentWindow) {
currentWindow.setTitle(arg);
}
});
- 渲染进程内交互
- 在渲染进程内部,就像普通的网页开发一样,可以使用JavaScript来处理各种交互。例如,实现一个简单的表单提交功能。在
index.html
中添加一个表单:
- 在渲染进程内部,就像普通的网页开发一样,可以使用JavaScript来处理各种交互。例如,实现一个简单的表单提交功能。在
html
<form id="myForm">
<input type="text" id="inputField" />
<button type="submit">Submit</button>
</form>
- 对应的JavaScript代码:
javascript
const myForm = document.getElementById('myForm');
myForm.addEventListener('submit', (e) => {
e.preventDefault();
const inputValue = document.getElementById('inputField').value;
console.log('Input value:', inputValue);
// 可以在这里进行更多关于输入值的处理,比如显示在页面其他地方等
});
四、打包部署安装
- 打包工具选择
- 常用的Electron打包工具是Electron - Packager和Electron - Builder。Electron - Packager可以将应用程序打包成特定平台的可执行文件格式。Electron - Builder功能更强大,它支持多种平台,并且可以生成安装包、自动更新等功能。
- 以Electron - Builder为例,首先需要安装它。在项目目录下运行
npm install electron - builder - - save - dev
或者yarn add electron - builder - - dev
。
- 配置打包脚本
- 在
package.json
中配置打包脚本。例如:
- 在
json
{
"name": "my - electron - app",
"version": "1.0.0",
"main": "main.js",
"scripts": {
"start": "electron.",
"package": "electron - builder"
},
"build": {
"appId": "com.example.my - electron - app",
"productName": "My Electron App",
"mac": {
"category": "public.app - category.utilities"
},
"win": {
"target": [
"nsis"
]
}
},
"devDependencies": {
"electron": "^27.0.0",
"electron - builder": "^23.6.0"
}
}
- 这里配置了应用的标识(
appId
)、产品名称(productName
),以及针对Mac和Windows的一些打包选项,如在Windows下使用NSIS(Nullsoft Scriptable Install System)来创建安装程序。
- 打包操作
- 配置好后,在项目目录下通过命令行运行
npm run package
或者yarn package
就可以开始打包。打包完成后,在项目的dist
目录(具体位置可能根据配置有所不同)下会生成可执行文件或安装包。对于Windows,会得到一个.exe
安装程序或者解压后可直接运行的文件;对于Mac,会得到一个.app
文件。
- 配置好后,在项目目录下通过命令行运行
- 部署和安装
- 对于打包好的应用,可以通过各种方式进行部署。可以将安装包发布到官方网站、软件下载平台等。用户下载安装包后,像安装普通软件一样进行安装。在Windows上双击
.exe
安装程序,按照安装向导的步骤进行安装;在Mac上,将.app
文件拖移到"Applications"文件夹或者直接双击运行来安装。
- 对于打包好的应用,可以通过各种方式进行部署。可以将安装包发布到官方网站、软件下载平台等。用户下载安装包后,像安装普通软件一样进行安装。在Windows上双击
elctron 生命周期及窗口应用
- 管理生命周期
app
模块的使用 :在Electron中,app
模块是控制应用程序生命周期的核心。例如,app.whenReady()
方法用于在Electron初始化完成后执行一些操作。- 以下是一个典型的
main.js
文件中对应用生命周期管理的示例:
javascript
const { app } = require('electron');
function createWindow() {
// 创建窗口的代码将在后面介绍
}
// 当Electron初始化完成后,创建窗口
app.whenReady().then(() => {
createWindow();
// 当应用被激活时(例如从Dock或任务栏重新打开),如果没有窗口则创建一个
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
// 当所有窗口都关闭时,在非macOS平台上退出应用
app.on('window - all - closed', () => {
if (process.platform!== 'darwin') {
app.quit();
}
});
- 在这个例子中,
app.whenReady()
确保在Electron准备好后才创建窗口。app.on('activate')
监听应用激活事件,app.on('window - all - closed')
监听所有窗口关闭的事件,根据平台决定是否退出应用。在macOS上,即使所有窗口关闭,应用通常也会保留在Dock中,直到用户明确退出。
- 创建窗口
BrowserWindow
类的应用 :使用BrowserWindow
类来创建窗口。可以设置窗口的各种属性,如大小、标题、是否可调整大小等。- 示例代码:
javascript
const { app, BrowserWindow } = require('electron');
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
title: 'My Electron App',
webPreferences: {
nodeIntegration: true
}
});
win.loadFile('index.html');
return win;
}
- 在这里,创建了一个
BrowserWindow
实例,设置了窗口的宽度为800像素、高度为600像素,标题为My Electron App
。webPreferences
中的nodeIntegration: true
允许在渲染进程中使用Node.js的功能。然后通过win.loadFile('index.html')
加载了一个HTML文件作为窗口的内容。这个createWindow
函数可以返回创建的窗口实例,方便后续对窗口进行管理。
- 管理窗口
- 获取和操作窗口实例 :可以使用
BrowserWindow.getAllWindows()
获取所有打开的窗口实例列表,BrowserWindow.getFocusedWindow()
获取当前获得焦点的窗口。 - 例如,在主进程中修改窗口标题:
- 获取和操作窗口实例 :可以使用
javascript
const { ipcMain, BrowserWindow } = require('electron');
ipcMain.on('change - window - title', (event, newTitle) => {
const currentWindow = BrowserWindow.getFocusedWindow();
if (currentWindow) {
currentWindow.setTitle(newTitle);
}
});
- 这个例子中,通过
ipcMain
接收一个名为change - window - title
的消息,当接收到消息后,获取当前获得焦点的窗口(如果有),并将其标题修改为消息中传递的新标题。还可以对窗口进行其他操作,如最小化、最大化、关闭等。例如:
javascript
const { BrowserWindow } = require('electron');
const win = BrowserWindow.getFocusedWindow();
if (win) {
win.minimize(); // 最小化窗口
win.maximize(); // 最大化窗口
win.close(); // 关闭窗口
}
主进程与渲染进程交互
-
Electron创建窗口
- 基础窗口创建
- 在Electron的主进程(通常是
main.js
文件)中,使用BrowserWindow
类来创建窗口。首先需要引入BrowserWindow
和app
模块,如下所示:
- 在Electron的主进程(通常是
- 基础窗口创建
javascript
const { app, BrowserWindow } = require('electron');
- 然后,定义一个函数来创建窗口。在这个函数中,通过
new BrowserWindow()
来实例化一个窗口对象,并传入一个配置对象来设置窗口的属性。例如:
javascript
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
});
win.loadFile('index.html');
return win;
}
-
在上述代码中,创建的窗口宽度为800像素,高度为600像素。
webPreferences
中的nodeIntegration: true
是一个重要的设置,它允许在渲染进程(加载的HTML页面)中使用Node.js的功能。win.loadFile('index.html')
这一行是将一个本地的HTML文件加载到窗口中作为显示内容。这个index.html
文件就是渲染进程的入口文件。- 多窗口创建
- 如果需要创建多个窗口,可以在适当的事件触发时调用
createWindow
函数。例如,当用户点击一个按钮或者满足某个条件时创建新窗口。假设在主进程中有一个事件监听器,当接收到一个特定的IPC
(进程间通信)消息时创建新窗口:
- 如果需要创建多个窗口,可以在适当的事件触发时调用
- 多窗口创建
javascript
const { ipcMain } = require('electron');
ipcMain.on('open - new - window', (event, arg) => {
const newWin = createWindow();
// 可以在这里对新窗口进行进一步的操作,如设置位置等
newWin.setPosition(100, 100);
});
- 这里,当主进程接收到名为
open - new - window
的IPC
消息时,就会调用createWindow
函数创建一个新的窗口,并将其位置设置为横坐标100像素、纵坐标100像素的位置。
-
主进程与渲染进程交互实例
- 从渲染进程发送消息到主进程
- 在渲染进程(
index.html
及其关联的JavaScript文件)中,首先需要引入ipcRenderer
模块。假设index.html
中有一个按钮,点击这个按钮就向主进程发送消息:
- 在渲染进程(
- 从渲染进程发送消息到主进程
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF - 8">
</head>
<body>
<button id="sendMessageButton">Send Message to Main</button>
<script>
const { ipcRenderer } = require('electron');
const sendMessageButton = document.getElementById('sendMessageButton');
sendMessageButton.addEventListener('click', () => {
ipcRenderer.send('message - from - renderer', 'This is a test message');
});
</script>
</body>
</html>
-
在上述代码中,
ipcRenderer.send()
方法用于发送消息。第一个参数message - from - renderer
是一个自定义的通道名称,用于在主进程中识别消息来源;第二个参数This is a test message
是要发送的实际消息内容。- 在主进程中接收并处理消息
- 在主进程(
main.js
)中,需要使用ipcMain
模块来接收消息。代码如下:
- 在主进程(
- 在主进程中接收并处理消息
javascript
const { ipcMain } = require('electron');
ipcMain.on('message - from - renderer', (event, arg) => {
console.log('Received message from renderer:', arg);
// 可以根据接收到的消息进行各种操作,如修改窗口属性等
const currentWindow = BrowserWindow.getFocusedWindow();
if (currentWindow) {
currentWindow.setTitle(arg);
}
});
-
这里,
ipcMain.on()
方法监听名为message - from - renderer
的通道消息。当接收到消息时,会在控制台打印消息内容,并获取当前获得焦点的窗口(如果有),将其标题设置为接收到的消息内容。- 从主进程发送消息到渲染进程
- 在主进程中发送消息到渲染进程,首先要获取窗口对应的
WebContents
对象。例如,在主进程中有一个定时器,每隔一段时间就向渲染进程发送消息:
- 在主进程中发送消息到渲染进程,首先要获取窗口对应的
- 从主进程发送消息到渲染进程
javascript
const { app, BrowserWindow } = require('electron');
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
});
win.loadFile('index.html');
// 定时器,每隔5秒发送一次消息
setInterval(() => {
const webContents = win.webContents;
webContents.send('message - from - main', 'This is a message from the main process');
}, 5000);
return win;
}
-
这里,通过
win.webContents.send()
方法发送消息。message - from - main
是通道名称,This is a message from the main process
是消息内容。- 在渲染进程中接收并处理消息
- 在渲染进程中,需要使用
ipcRenderer
来接收主进程发送的消息。代码如下:
- 在渲染进程中,需要使用
- 在渲染进程中接收并处理消息
javascript
const { ipcRenderer } = require('electron');
ipcRenderer.on('message - from - main', (event, arg) => {
console.log('Received message from main:', arg);
// 可以根据消息进行更新页面内容等操作
const messageElement = document.createElement('p');
messageElement.textContent = arg;
document.body.appendChild(messageElement);
});
- 当渲染进程接收到名为
message - from - main
的消息时,会在控制台打印消息内容,并在页面中创建一个新的<p>
标签,将消息内容添加到标签中,然后将标签添加到页面的body
元素中,从而在页面上显示主进程发送的消息。
技术细节
- 在HTML中直接引用Electron(不推荐用于正式开发)
- 在开发初期的快速原型阶段或者非常小型的应用场景下,可能会直接在HTML文件(渲染进程)中引用Electron相关模块来进行简单的功能测试。例如,在
index.html
文件中通过<script>
标签直接引入Electron的ipcRenderer
模块来实现主进程和渲染进程之间的简单通信。 - 示例代码如下:
- 在开发初期的快速原型阶段或者非常小型的应用场景下,可能会直接在HTML文件(渲染进程)中引用Electron相关模块来进行简单的功能测试。例如,在
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF - 8">
</head>
<body>
<button id="sendMessageButton">Send Message to Main</button>
<script>
// 直接引入Electron的ipcRenderer模块
const { ipcRenderer } = require('electron');
const sendMessageButton = document.getElementById('sendMessageButton');
sendMessageButton.addEventListener('click', () => {
ipcRenderer.send('message - from - renderer',
'This is a test message');
});
</script>
</body>
</html>
- 这种方式虽然简单直接,但存在一些问题。比如,它没有很好地遵循模块加载的最佳实践,并且如果在复杂的项目中,可能会导致代码难以维护和管理。另外,在一些严格的安全策略下,这种直接引用可能会受到限制。
- 在正式开发中的更好实践
- 预加载脚本(Preload Scripts)
- 在正式开发中,通常会使用预加载脚本。预加载脚本是一个在渲染进程加载之前运行的脚本,它运行在一个特殊的环境中,可以访问Electron的主进程和渲染进程的一些功能。
- 首先,创建一个预加载脚本文件(例如
preload.js
),在这个文件中可以进行一些初始化操作,比如将主进程的功能封装并暴露给渲染进程。示例代码如下:
- 预加载脚本(Preload Scripts)
javascript
const { contextBridge, ipcRenderer } = require('electron');
// 暴露一个名为sendMessageToMain的函数给渲染进程
contextBridge.exposeInheritedObject('electronAPI', {
sendMessageToMain: (message) => {
ipcRenderer.send('message - from - renderer', message);
}
});
- 然后,在创建
BrowserWindow
时,指定预加载脚本。在main.js
文件中:
javascript
const { app, BrowserWindow } = require('electron');
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
});
win.loadFile('index.html');
return win;
}
- 在
index.html
文件中,就可以通过window.electronAPI.sendMessageToMain
来调用这个函数发送消息到主进程,这样的方式更加安全和易于维护。 - 模块打包和构建工具
- 在正式开发中,还会使用模块打包和构建工具,如Webpack或Rollup。这些工具可以帮助管理和打包JavaScript代码,包括Electron相关的代码。
- 例如,使用Webpack时,可以将Electron相关的代码作为一个模块进行管理,通过配置
webpack.config.js
文件来处理不同的入口文件(包括主进程的main.js
和渲染进程的index.html
相关的JavaScript文件)。可以将Electron模块的引用和其他依赖一起打包,并且可以进行代码优化、压缩等操作,提高应用的性能和可维护性。
- 代码分离和分层架构
- 正式开发通常会采用代码分离和分层架构。将Electron相关的功能代码(如主进程的生命周期管理、窗口创建和进程间通信等)与具体的业务逻辑和页面展示代码(在渲染进程中)分开。
- 例如,在一个大型的Electron应用中,可能会有一个专门的
electron-services
文件夹,里面存放主进程相关的服务代码,如窗口管理服务、文件系统服务(利用Electron的fs
模块)等。在渲染进程中,通过前面提到的预加载脚本或者其他通信机制来调用这些服务,从而实现清晰的架构和更好的代码复用。
electron 中需要注意的安全问题
-
Electron安全性概述
- Electron应用的安全性是一个重要的考虑因素,因为它结合了Web技术(HTML、CSS、JavaScript)和本地系统访问权限(通过Node.js)。与传统的纯Web应用相比,Electron应用有更多潜在的安全风险,因为它们可以访问本地文件系统、操作系统功能等。不过,Electron本身也提供了一些机制来帮助开发者构建安全的应用。
-
正式开发中需要注意的安全问题及应对措施
- 进程间通信(IPC)安全
- 问题:主进程和渲染进程之间的通信通道如果没有正确保护,可能会被恶意利用。例如,攻击者可能会尝试在渲染进程中发送恶意指令到主进程,从而获取敏感信息或者执行有害操作。
- 应对措施 :
- 对IPC消息进行验证和过滤。在主进程接收渲染进程消息时,检查消息的来源和内容是否合法。例如,只接受来自已知渲染进程且符合预定义格式的消息。
- 使用
contextBridge
来安全地暴露主进程的API给渲染进程。通过contextBridge.exposeInheritedObject
,可以精确控制暴露给渲染进程的功能,避免过度暴露可能导致安全风险的主进程功能。以下是一个示例:
- 进程间通信(IPC)安全
javascript
// 在预加载脚本(preload.js)中
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInheritedObject('electronAPI', {
// 只暴露一个安全的函数用于发送消息到主进程
sendMessageToMain: (message) => {
ipcRenderer.send('message - from - renderer', message);
}
});
- 渲染进程中的脚本安全
- 问题:渲染进程中的JavaScript代码可能会受到跨站脚本攻击(XSS)。如果应用加载了不可信的外部内容或者用户输入没有得到正确处理,就可能导致恶意脚本在渲染进程中执行。
- 应对措施 :
- 对用户输入进行严格的过滤和转义。在将用户输入插入到HTML页面(如用于显示用户评论、表单提交内容等场景)之前,使用合适的函数进行转义,防止恶意脚本注入。例如,在JavaScript中使用
DOMParser
来安全地解析和处理HTML内容。 - 避免使用
eval()
函数和Function()
构造函数,因为它们可以执行任意字符串作为JavaScript代码,这是XSS攻击的一个常见入口点。如果必须使用,要确保输入内容是完全可信的。 - 限制对外部资源的加载。如果应用不需要加载外部脚本或者样式表,应该避免加载。如果需要加载,要确保来源是可信的,并使用内容安全策略(CSP)来限制脚本的执行范围。例如,可以在HTML页面的
<meta>
标签中设置CSP,如下:
- 对用户输入进行严格的过滤和转义。在将用户输入插入到HTML页面(如用于显示用户评论、表单提交内容等场景)之前,使用合适的函数进行转义,防止恶意脚本注入。例如,在JavaScript中使用
html
<meta http-equiv="Content-Security-Policy" content="default-src'self'; script
-src'self'">
- 本地文件系统访问安全
- 问题:由于Electron应用可以通过Node.js访问本地文件系统,这可能会导致用户文件的泄露或者被恶意篡改。如果应用没有正确地限制文件访问权限,攻击者可能会利用这个漏洞获取敏感文件(如用户的文档、配置文件等)。
- 应对措施 :
- 最小化文件访问权限。只在必要时才使用Node.js的
fs
模块访问本地文件系统,并且只访问应用需要的特定文件和目录。例如,如果应用只需要读取一个特定的配置文件,可以明确指定该文件的路径,而不是允许访问整个文件系统。 - 对用户操作进行权限验证。当用户尝试通过应用执行文件操作(如保存、删除文件等)时,要验证用户是否有相应的权限。这可以通过操作系统提供的用户认证机制或者应用内部的权限管理系统来实现。
- 加密敏感文件。如果应用存储了敏感的用户数据(如密码、密钥等),在存储到本地文件系统之前,应该使用适当的加密算法进行加密。当需要访问这些文件时,再进行解密。例如,可以使用
crypto
模块(Node.js内置模块)来进行文件加密和解密操作。
- 最小化文件访问权限。只在必要时才使用Node.js的
- 网络访问安全
- 问题:Electron应用在进行网络访问时,可能会受到中间人攻击、恶意网络请求等安全威胁。例如,应用可能会发送用户的敏感信息(如登录凭证)没有经过加密的网络请求。
- 应对措施 :
- 使用安全的网络协议。对于涉及用户敏感信息的网络通信,如登录、支付等操作,应该使用HTTPS协议而不是HTTP。可以通过检查服务器证书的有效性来确保通信的安全性。
- 对网络请求进行限制。只允许应用向已知的、可信的服务器发送请求。可以通过配置网络请求的白名单或者使用代理服务器来限制请求的目标。
- 防止跨域攻击。如果应用与多个不同域的服务器进行交互,要注意防范跨域攻击。可以通过设置合适的跨域策略(如服务器端设置
Access-Control-Allow-Origin
头)和在客户端正确处理跨域请求来避免安全问题。