Electron 基础
设置国内镜像
将 Electron 设置国内镜像后,可以加快文件的下载速度,下面是 electron 的国内镜像设置
npm config set electron_mirror=https://npm.taobao.org/mirrors/electron/
npm config set electron_builder_binaries_mirror=https://npm.taobao.org/mirrors/electron-builder-binaries/上面的镜像不行就试试下面的
npm config set electron_mirror https://mirrors.huaweicloud.com/electron/
npm config set electron_builder_binaries_mirror https://mirrors.huaweicloud.com/electron-builder-binaries/创建应用
首先安装 node.js,因为 Electron 将 Node.js 嵌入到其二进制文件中,你应用运行时的 Node.js 版本与你系统中运行的 Node.js 版本无关
npm init
npm i -D electron创建的 package.json 内容如下
{
"name": "camera",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"electron": "^25.5.0"
}
}修改 package.json 文件中的 main 与 scripts 配置段
- name 字段会做为 app.name 的默认值
- productName 字段会做为 app.name 的默认值,优先级高于 name
- main 主进程脚本
- dev 运行electron项目命令
{
"name": "camera",
"productName": "camera",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "electron ."
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"electron": "^25.5.0"
}
}模版文件 index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<h1>hello world!</h1>
</body>
</html>主进程 main.js
const { app, BrowserWindow } = require("electron")
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
})
win.loadFile("index.html")
}
// 应用准备好后创建窗口
app.whenReady().then(() => {
createWindow()
app.on("activate", () => {
if (BrowserWindow.getAllViews().length === 0) {
createWindow()
}
})
})
// 非苹果系统当关闭所有窗口时退出应用
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit()
}
})自动重启 nodemon
有时需要当文件修改后,自动重起项目,这时需要安装 nodemon 模块
npm i -g nodemon修改 package.json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "nodemon --exec electron ."
},然后添加 nodemon.json 配置文件
{
"ignore": ["node_modules", "dist"],
"colours": true,
"verbose": true,
"watch": ["*.*"],
"ext": "html,js"
}主进程设置窗口的位置,并且让窗口置顶,这样不会遮挡 vscode 编辑器
const { app, BrowserWindow } = require("electron")
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 400,
x: 1400,
y: 100,
alwaysOnTop: true,
})
win.loadFile("index.html")
}
// 应用准备好后创建窗口
app.whenReady().then(() => {
createWindow()
app.on("activate", () => {
if (BrowserWindow.getAllViews().length === 0) {
createWindow()
}
})
})
// 非苹果系统当关闭所有窗口时退出应用
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit()
}
})安全策略
因为 Electron 项目可以执行 javascript 代码,也可以访问用户电脑的文件系统,所以访问任何不受信任的内容都可能带来安全隐患
当没有配置安装策略时,Electron 会报出警告,你可以在渲染进程的开发者工具中查看到

内容安全策略(CSP) 是应对跨站脚本攻击和数据注入攻击的又一层保护措施。 我们建议任何载入到 Electron 的站点都要开启
<meta http-equiv="Content-Security-Policy" content="default-src 'self' *.trusted.com; script-src '*.baidu.com'" />基于框架
平时项目开发我们都会使用到 vue、react 等框架开发渲染进程的代码,所以要配置 electron 与常用框架的整合。下面我们先自己配置个electron+vite+vue3 的脚手架,然后在介绍成熟的开源脚手架
npm create vite
npm i -D electron
# 因为我们要跑两个进程所以要安装 concurrently,它可以控制依次启动多个进程
npm add -D concurrently然后修改 package.json
"main": "electron/main.cjs"定义 electron 主进程脚本"dev": "concurrently "nodemon --exec electron . " "vite""定义依次启动 vue 与 electron 脚本
{
"name": "test",
"private": true,
"version": "0.0.0",
"type": "module",
"main": "electron/main.cjs",
"scripts": {
"dev": "concurrently \"nodemon --exec electron . \" \"vite\"",
"build": "vue-tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.2.45"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.0.0",
"concurrently": "^7.6.0",
"electron": "^22.0.3",
"typescript": "^4.9.3",
"vite": "^4.0.0",
"vue-tsc": "^1.0.11"
}
}然后定义 electron/main.cjs 主进程脚本
const { ipcMain, BrowserWindow, app } = require('electron')
const path = require('path')
const createWindow = () => {
const win = new BrowserWindow({
width: 600,
height: 600,
alwaysOnTop: true,
})
win.webContents.openDevTools()
win.loadURL('http://localhost:5173')
return win
}
app.whenReady().then(() => {
createWindow()
app.on('window-all-closed', () => {
if (process.platform != 'darwin') app.quit
})
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length == 0) {
createWindow()
}
})
})成熟的脚手架
日常开发可以使用 electron-vite 或 electron-vite-vue、electron-react-boilerplate 等脚手架快速创建项目,脚手架已经为我们完成了基本的配置,并支持使用 Vue 与 React 等技术开发 Electron 项目
下面使用 electron-vite 脚手架创建项目
pnpm create @quick-start/electron
pnpm install
pnpm run dev

配置调试
微软有一个仓库 vscode-recipes 提供了vscode的 launch.json 常用开发语言的配置
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Electron: Main",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron",
"runtimeArgs": [
"--remote-debugging-port=9223",
"."
],
"windows": {
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd"
}
},
{
"name": "Electron: Renderer",
"type": "chrome",
"request": "attach",
"port": 9223,
"webRoot": "${workspaceFolder}",
"timeout": 30000
}
],
"compounds": [
{
"name": "Electron: All",
"configurations": [
"Electron: Main",
"Electron: Renderer"
]
}
]
}主进程调试
下面介绍主进程的调试。其实做的工作很少,在主进程代码中设置断点,然后运行调试就可以了


渲染进程调试
因为渲染进程是由主进程打开的,所以在进行渲染进程调试时,需要先启动主进程调试 Electron:Main
分别启动
首先启动主进程的调试,然后再启动渲染进程 Electron:Renderer
同时启动
因为渲染进程是由主进程打开的,所以在进行渲染进程调试时,需要先启动主进程 debug

窗口管理
每一个窗口可以简单理解成一个chrome浏览器标签,需要在在 electron 主文件electron/main/index.ts中定义
- 窗口可以定义尺寸
- 窗口可以加载本地文件或一个链接
下面我们创建窗口并加载 百度 网站链接
function createWindow() {
...
const hdWin = new BrowserWindow({
title: 'xiaoyekeji',
width: 390,
height: 844,
})
hdWin.loadURL('https://www.baidu.com')
...
}常用的方法
下面介绍窗口实例常用的方法
| 方法 | 说明 |
|---|---|
| win.loadFile() | 加载文件 |
| win.loadURL() | 加载链接 |
| win.webContents.openDevTools() | 打开开发者工具 |
| win.setContentBounds() | 控制窗口尺寸与位置 |
| win.center() | 将窗口移动到屏幕中心 |
常用属性
| 属性 | 说明 |
|---|---|
| title | 标题,也可以修改 html 模板的 title 标签,模板的 title 标签优先级高 |
| icon | window 系统窗口图标 |
| frame | 是否显示边框 |
| transparent | 窗口是否透明 |
| x | x 坐标 |
| y | y 坐标 |
| width | 宽度 |
| height | 高度 |
| movable | 是否可以移动窗口 |
| minHeight | 最小高度,不能缩放小于此高度 |
| minWidth | 最大高度,不能缩放小于此高度 |
| resizable | 是否允许缩放窗口 |
| alwaysOnTop | 窗口是否置顶 |
| autoHideMenuBar | 是否自动隐藏窗口菜单栏。 一旦设置,菜单栏将只在用户单击 Alt 键时显示 |
| fullscreen | 是否全屏幕 |
属性举例
ready-to-show
如果应用过于复杂,在加载本地资源时出现白屏,这时可以监测窗口的 ready-to-show 事件
- 设置 show 属性为 false,让窗口不显示
- 可以通过设置 backgroundColor 属性指定应用背景颜色,使界面不显示突兀
- ready-to-show 事件检测,当渲染进程绘制完成时,显示窗口
...
const createWindow = () => {
const win = new BrowserWindow({
width: 600,
height: 500,
show: false,
backgroundColor: 'red',
})
win.loadFile(path.resolve(__dirname, 'index.html'))
win.once('ready-to-show', () => {
win.show()
})
}
...窗口定位
下面将窗口定位到屏幕右侧顶部,需要使用到 electron 库的 screen 对象
const { app, BrowserWindow, shell, ipcMain, screen } = require('electron')
function createWindow() {
win = new BrowserWindow({
title: 'Main window',
x: screen.getPrimaryDisplay().workAreaSize.width - 414,
y: 0,
width: 414,
height: 736
})
...
}窗口居中
我们有多种方式实现窗口居中,首先使用 win.center() 方法操作
...
mainWindow.webContents.openDevTools()
mainWindow.loadFile(path.resolve(__dirname, 'index.html'))
mainWindow.center()
...也可以通过 screen 模块获取屏幕尺寸,经过计算后设置窗口居中
- screen.getPrimaryDisplay().workAreaSize 获取窗口尺寸
- mainWindow.setContentBounds() 设置窗口尺寸与坐标,每二个参数用于定义是否使用过渡动画
...
const mainWindow = new BrowserWindow({
width: 300,
height: 300,
x: 1500,
y: 100,
alwaysOnTop: true,
webPreferences: {
preload: path.resolve(__dirname, 'preload.js'),
},
})
mainWindow.webContents.openDevTools()
mainWindow.loadFile(path.resolve(__dirname, 'index.html'))
setTimeout(() => {
mainWindow.setContentBounds(
{
width: 300,
height: 300,
x: screen.getPrimaryDisplay().workAreaSize.width / 2 - 150,
y: 100,
},
true,
)
}, 1000)
...动态修改窗口大小
main.js
const { app, ipcMain, BrowserWindow, screen } = require('electron')
const { createWindow } = require('./window')
app.whenReady().then(() => {
createWindow()
})
ipcMain.on('setPostion', (event, options) => {
//获取窗口
const win = BrowserWindow.fromWebContents(event.sender)
//根据屏幕尺寸获取窗口的x坐标,使其居中显示
const primaryDisplay = screen.getPrimaryDisplay()
const { width, height } = primaryDisplay.workAreaSize
const x = width / 2 - options.width / 2
//设置窗口坐标
win.setContentBounds({ ...options, x, y: 100 }, true)
})window.js
const { BrowserWindow } = require('electron')
const path = require('path')
const createWindow = () => {
const mainWindow = new BrowserWindow({
width: 300,
height: 300,
x: 1500,
y: 100,
alwaysOnTop: true,
webPreferences: {
preload: path.resolve(__dirname, 'preload.js'),
},
})
mainWindow.webContents.openDevTools()
mainWindow.loadFile(path.resolve(__dirname, 'index.html'))
return mainWindow
}
module.exports = {
createWindow,
}preload.js
预加载脚本用于IPC通信
const { ipcRenderer, contextBridge } = require('electron')
contextBridge.exposeInMainWorld('api', {
changeWindowPos: (options) => {
ipcRenderer.send('setPostion', options)
},
})renderer.js
渲染进程用于接收按钮事件,然后通过 preload.js 调用 main.js 的事件,改变窗口大小
window.addEventListener('DOMContentLoaded', () => {
const btn = document.querySelector('button')
btn.addEventListener('click', () => {
window.api.changeWindowPos({
width: Number(document.querySelector('[name="width"]').value),
height: Number(document.querySelector('[name="height"]').value),
})
})
})index.html
模板文件定义改变窗口的表单
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'" />
<title>改变窗口</title>
</head>
<body>
宽度:<input type="text" name="width" value="500" /> <br />
调试:<input type="text" name="height" value="500" />
<button>改变位置</button>
<script src="renderer.js"></script>
</body>
</html>BrowserView
BrowserView 被用来让 BrowserWindow 嵌入更多的 web 内容。 它就像一个子窗口,除了它的位置是相对于父窗口
下面演示使用 BrowserView 在主窗口中嵌入网页
const win = new BrowserWindow({
width: 1024,
height: 500,
frame: false,
webPreferences: {
preload: path.resolve(__dirname, 'preload.js')
},
})
win.webContents.openDevTools()
win.loadFile(path.resolve(__dirname, 'index.html'))
const view = new BrowserView()
win.setBrowserView(view)
view.setBounds({
x: 0,
y: 0,
width: win.getBounds().width,
height: 300,
})
view.webContents.loadURL('https://www.baidu.com')Shell
electron的 shell 模块是使用操作系统的默认应用程序打开文件或 url
可以在 Main, Renderer (只能在非沙盒下使用) 进程中使用
下面演示使用 shell 模块,用操作系统的默认浏览器打开网页链接。
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'" />
<meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'" />
<title></title>
</head>
<body>
<a href="https://www.baidu.com" target="__blank">百度</a>
<script src="renderer.js"></script>
</body>
</html>main.js
const { app, shell } = require('electron')
const { BrowserWindow } = require('electron/main')
const path = require('path')
const createWindow = () => {
const win = new BrowserWindow({
width: 500,
height: 500,
})
win.loadFile(path.resolve(__dirname, 'index.html'))
//捕获 a 标签的打开事件,使用系统浏览器打开,并阻止新窗口打开
win.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
//action:deny 拒绝 electron 新建窗口打开
//action:allow 允许 electron 新建窗口打开
return { action: 'deny' }
})
}
app.whenReady().then(() => {
createWindow()
})隔离进程
上下文隔离
上下文隔离是从安全角度考量的,即不允许 webcontent 网页使用 electron 内部组件与 node 等权限 。 默认情况下 electron 是开启上下文隔离的。
因为使用 Electron 开发的桌面应用,是比较特殊的。他是使用网页开发的,所以会引用第三方的应用,如果不进行隔离,这些应用是有机会调用 node.js api 的,如果应用是恶意的,就会对用户电脑带来案全隐患。
使用上下文隔离对团队开发好处也是明显的,可以让熟悉 vue、react 的前端工程师专门编写前端页面逻辑,让熟悉 nodejs 与 electron 的开发者负责 node.js 程序编写
关闭上下文隔离后,网页脚本可以使用 electron 与 node api 等部分高级 api
下面是在 main.js 中禁用上下文隔离的方法
...
webPreferences: {
preload: path.resolve(__dirname, 'preload.js'),
contextIsolation: false,
nodeIntegration: true,
},
...禁用上下文隔离后 preload.js 与 renderer.js 没有隔离机制,在 preload.js 中定义的变量可以在 renderer.js 中直接使用
在 preload.js 定义全局变量
window.hd = 'abc'现在可以在 renderer.js 网页脚本中访问了
console.log(window.hd)exposeInMainWorld
禁用上下文隔离后contextIsolation: false,在 preload.js 中则不需要使用 contextBridge.exposeInMainWorld 向 renderer.js 中提供接口了
main.js
主进程定义 IPC 事件
const { app, shell } = require('electron')
const { BrowserWindow, ipcMain } = require('electron/main')
const path = require('path')
const createWindow = () => {
const win = new BrowserWindow({
width: 500,
height: 500,
webPreferences: {
preload: path.resolve(__dirname, 'preload.js'),
contextIsolation: false
},
})
win.webContents.openDevTools()
win.loadFile(path.resolve(__dirname, 'index.html'))
}
app.whenReady().then(() => {
createWindow()
//定义IPC事件处理程序
ipcMain.handle('show', () => {
return 'houdunren.com'
})
})preload.js
直接定义接口
const { ipcRenderer } = require('electron')
const { contextBridge } = require('electron/renderer')
window.api = {
show: () => ipcRenderer.invoke('show'),
}renderer.js
渲染脚本使用
window.api.show().then((res) => {
console.log(res)
})nodeIntegration
可以通过修改 main.js 中的 nodeIntegration 配置,来开启 node 支持,这时就可以在 preload.js 或 renderer.js 中使用 fs 等高级模块
...
const win = new BrowserWindow({
width: 500,
height: 500,
webPreferences: {
preload: path.resolve(__dirname, 'preload.js'),
nodeIntegration: true,
},
})
...preload.js 默认只能使用有限的 node.js api,不能使用 fs 等高级模块,但开启 nodeIntegration 后,就可以使用了
const { readFileSync } = require('fs')
const res = readFileSync('package.json', {
encoding: 'utf-8',
})
console.log(res)如果想在 renderer.js 中使用 node.js 高级模块也是可以的,需要在 main.js 文件中关闭上下文隔离 contextIsolation: false 和开启 node 支持 nodeIntegration: true
...
const win = new BrowserWindow({
width: 300,
height: 300,
x: 1500,
y: 100,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: false,
nodeIntegration: true,
},
})
...进程沙盒
当 Electron 中的渲染进程被沙盒化时,它们的行为与常规 Chrome 渲染器一样。 一个沙盒化的渲染器不会有 Node.js 环境
在沙盒中,渲染进程只能通过进程间通讯 (inter-process communication, IPC) 委派任务给主进程的方式,来执行需权限的任务 (例如:文件系统交互,对系统进行更改或生成子进程)
如果我们想在 preload.js 中使用 node.js 与 electron 高级应用,如 shell、fs 等,可以通过关闭沙盒完成。当然通过开启 nodeIntegration 也可以实现该功能,但这会让 renderer.js 也可以使用 node.js 高级 api,这是不安全的
所以,开启沙盒,可以赋予 preload.js 高级权限,但不影响 renderer.js
- electron 默认是开启沙盒模式的
- nodeIntegration:true 时会自动关闭沙盒
- sandbox: false 时 preload.js 可以使用 nodejs、electron 的高级 api,如 fs 模块
下面演示在关闭沙盒后,可以在 preload.js 中使用 electron 的shell模块
main.js 主进程中禁用沙盒模式
const { app, shell } = require('electron')
const { BrowserWindow } = require('electron/main')
const path = require('path')
const createWindow = () => {
const win = new BrowserWindow({
width: 500,
height: 500,
webPreferences: {
preload: path.resolve(__dirname, 'preload.js'),
//关闭沙盒模式
sandbox: false,
},
})
win.webContents.openDevTools()
win.loadFile(path.resolve(__dirname, 'index.html'))
}
app.whenReady().then(() => {
createWindow()
})关闭沙盒模式后 preload.js 中可以使用 shell 模块打开链接了
const { shell } = require('electron')
shell.openExternal('https://www.baidu.com')设置了 nodeIntegration:true 也会关闭沙盒