electron

electron

周四 5月 08 2025
2036 字 · 12 分钟

electron是基于node.js开发的桌面端应用开发工具

SHELL
npm init

init初始化命令会提示您在项目初始化配置中设置一些值 为本教程的目的,有几条规则需要遵循:

  • entry point 应为 main.js.
  • author 与 description 可为任意值,但对于应用打包是必填项。

你的 package.json 文件应该像这样:

PLAINTEXT
{  
	"name": "my-electron-app",
	"version": "1.0.0",  
	"description": "Hello World!", 
	"main": "main.js",  
	"author": "Jane Doe", 
	"license": "MIT"
}

然后安装electron包

SHELL
npm install electron --save-dev

—save-dev作用: npm 会将该包添加到 devDependencies 部分的 package.json 文件中。这意味着该依赖包仅在开发环境中需要,而在生产环境中不需要,可以有效隔离环境。 使用 —save(或省略该标志),npm 会将依赖包添加到 dependencies 部分,这意味着该包在生产环境中也是必需的。 npm 用不了也可以用其他的 pnpmcnpm(这个确实可以使用)

PLAINTEXT
"scripts": {
  "start": "electron --no-sandbox"
},

对于wsl中,因为是root用户,需要使用 --no-sandbox 来解决,作用是关闭沙盒,会影响安全性 这样做可能会引入安全风险,因为禁用沙箱会降低隔离性。 其他情况下不需要 --no-sandbox

语法

PLAINTEXT
const {app, BrowserWindow } = require('electron')

function createWindow() { 
  const win = new BrowserWindow({
    width:800,
    height:600,
    autoHideMenuBar: true,
    alwaysOnTop:true,
    x:0,
    y:0
  })

  win.loadFile('./a.html')
}

app.on('ready', ()=>{
  createWindow()
})
解析
PLAINTEXT
const { app, BrowserWindow } = require('electron')

注册一个app和BrowserWindow, app是项目的入口,BrowserWindow创建一个窗口。

PLAINTEXT
app.on('ready', ()=>{
	createWindow()
})

这里在app,状态为ready时,触发回调函数,这里为箭头函数,函数里调用了createWindow()函数

PLAINTEXT
function createWindow() { 
  const win = new BrowserWindow({
    width:800,
    height:600,
    autoHideMenuBar: true,
    alwaysOnTop:true,
    x:0,
    y:0
  })

  win.loadFile('./a.html')
}

自定义createWindow()函数,创建一个BrowserWindow的实例化,并设置配置项。width:宽度,height:高度,x,y:窗口出现的位置,autoHideMenuBar:自动隐藏菜单栏,alwaysOnTop:始终页面图层置顶 更多设置在Electron-BrowserWindow

win.loadFile(’./a.html’)这里加载一个本地页面 win.loadURL(”)可以加载一个网页

![[Pasted image 20241223230357.png]]

PLAINTEXT
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;">

安全策略,引入外部资源必须是同源的,引入的样式表必须是同源的或是行内样式,引入的图片必须是同源的或是使用data:URI来嵌入图像。 [[js_基础1#使用data URI实现插入图片内容]]

系统差别的细节化操作

JS
app.on('window-all-close', ()={
	if (process.platform !== 'darwin') app.quit()
})

这里判断app的状态,当所有页面都关闭时,触发回调函数。如果平台不是苹果,卸载(关闭)app

JS
app.on('activate', ()=>{
	if (BroserWindow.getAllWindows().length === 0) createwindow()
})

(苹果系统) 当应用被激活时,如果目前没有窗口,就创建一个窗口。

配置自动重启

SHELL
cnpm i nodemon -D

下载nodemon,配置自动重启项

PLAINTEXT
{
    "ignore": [
        "node_modules",
        "dist"
    ],
    "restartable": "r",
    "watch": ["*.*"],
    "ext": "html,js,css"
}

这里解释一下各个配置项。 ignore,忽略node_modules和dist文件夹。 watch,监视 ext,指明文件类型为html,js,css restartable,使用字母r可以重启

这里只有main.js为主进程,其他js文件均为渲染进程 在主加载进程与渲染进程中,还有一个预加载脚本,协调两个进程脚本

预加载脚本

预加载脚本需要在main.js里面修改。

PLAINTEXT
const { app, BrowserWindow } = require('electron')
const path = require('path')
function createWindow() {
  const win = new BrowserWindow({
    width:800,
    height:600,
    autoHideMenuBar: true,
    alwaysOnTop:true,
    webPreferences:{
      preload:path.resolve(__dirname,'./preload.js')
    }
  })
  win.loadFile('pages/index.html')
}
PLAINTEXT
const path = require('path')

    webPreferences:{
      preload:path.resolve(__dirname,'./preload.js')
    }

这里使用了相对路径,找到了当前文件夹下的preload.js文件。并作为预加载脚本,在启动渲染进程时运行。

部分解决:使用预加载脚本

PLAINTEXT
const { contextBridge } = require('electron')

contextBridge.exposeInMainWorld('myAPI', {
    version:process.version
})
PLAINTEXT
const btn1 = document.getElementById('btn1')

btn1.onclick = ()=>{
    alert(myAPI.version)
}

解释一下这里的两个脚本。preload.js即预加载脚本。通过 const { contextBridge } = require('electron') 来引入contextBridge。 然后通过 contextBridge.exposeInMainWorld 方法来实现部分通信的传递。例如这里可以通过定义 myAPI 来向render.js传递内部的信息。这里process.version是调出了node的版本信息。而在render.js中,只需要通过定义好的 myAPI 中的 version 即可调用出对应的信息。

问题:这里只能传递一部分的信息,无法完整做到进程通信。

进程通信

进程通信主要指主进程与渲染进程之间的通信关系。而这种通信主要通过预加载脚本进行实现,在预加载脚本中,

渲染向主进程单向通信

这里举一个例子

PLAINTEXT
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('myAPI', {
    version:process.version,
    saveFile:(data)=>{
        ipcRenderer.send('file-save', data)
    }
})

这里是预加载脚本,需要介绍的是 ipcRenderer ,这个是负责通信传递的,在上文提到的 contextBridge 通信方法下,自定义新的方法——saveFile,接收一个data参数,交给 ipcRenderer 的send方法,进行传递, send 方法接收两个参数,信道与数据,信道在后面的 ipcMain 中还要使用

PLAINTEXT
const btn1 = document.getElementById('btn1')
const input = document.getElementById('input')
const btn2 = document.getElementById('btn2')

btn2.onclick = ()=>{
    myAPI.saveFile(input.value)
}

在render.js即渲染进程中,只需要像之前那样,调用 contextBridge 中定义好的方法,通过定义的 myAPIsaveFile 方法传递参数

PLAINTEXT
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
const fs = require('fs')

function writeFile(_, data) {
   fs.writeFileSync('./hello.txt', data)
}

function createWindow() {
  const win = new BrowserWindow({
    width:800,
    height:600,
    autoHideMenuBar: true,
    webPreferences:{
      preload:path.resolve(__dirname,'./preload.js')
    }
  })
  
  ipcMain.on('file-save', writeFile)
  win.loadFile('pages/index.html')
}

在main.js即主进程中,需要介绍的是 ipcMain ,这是对应 ipcRender 用来接收数据的,ipcMain.on('file-save', writeFile) ,这里 ipcMain.on 接收两个参数,一个是上面提到的信道,另一个是回调函数。接着,是回调函数的部分,这里的回调函数接收两个参数,event和data。前者是js基本功部分暂时不考虑,后者是我们需要传递的data数据。 这样一个单项传递就可以结束了。

渲染进程向主进程双向通信

这里拓展上面的例子,从文件写入后进行读取并返回

PLAINTEXT
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
const fs = require('fs')

function writeFile(_, data) {
   fs.writeFileSync('./hello.txt', data)
}

function readFile() {
  return fs.readFileSync('./hello.txt').toString()
}

function createWindow() {
  const win = new BrowserWindow({
    width:800,
    height:600,
    autoHideMenuBar: true,
    webPreferences:{
      preload:path.resolve(__dirname,'./preload.js')
    }
  })

  ipcMain.on('file-save', writeFile)
  ipcMain.handle('file-read',readFile)
  win.loadFile('pages/index.html')
}

这是mian.js即主进程。因为对应预加载脚本中使用的是 ipcRendererinvoke 方法。所以这里需要对应使用 ipcMain.handle 来进行处理。然后是这个回调函数,这里不仅需要取出对应的内容,还需要将这个数据传递给渲染进程。所以这里使用 return 返回内容。而对应这个返回的内容,将在下面讲解

PLAINTEXT
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('myAPI', {
    version:process.version,
    saveFile:(data)=>{
        ipcRenderer.send('file-save', data)
    },
    readFile(){
        return ipcRenderer.invoke('file-read')
    }
})

这是预加载脚本,需要注意的是,这里的 readFile 方法中使用的是 invoke 同时,这里的这个方法会返回一个data数据,即上文提到的从文件中取出的内容。而且,这里 invoke方法 返回的数据是一个 Promise 数据,需要使用异步操作来进行处理才能拿到最后的数据。为了文件整洁,将异步操作留在渲染进程处理

PLAINTEXT
const btn1 = document.getElementById('btn1')
const input = document.getElementById('input')
const btn2 = document.getElementById('btn2')
const btn3 = document.getElementById('btn3')

btn1.onclick = ()=>{
    alert(myAPI.version)
}

btn2.onclick = ()=>{
    myAPI.saveFile(input.value)
}

btn3.onclick = async ()=>{
    let data = await myAPI.readFile()
    alert(data)
}

渲染进程中只有简单的操作,使用异步操作读取到对应的数据即可。

主进程到渲染进程

可以不要使用,使用上面的双向即可。 原理是从主进程使用 send 方法,然后在渲染进程中 .on 来接收

渲染进程向渲染进程

不能直接传递,需要主进程作为中间人进行传递。渲->主->渲

打包

PLAINTEXT
  "scripts": {
    "start": "nodemon --exec electron --no-sandbox .",
    "build": "electron-build"
  },
  "build": {
    "appId": "com.yang", //程序唯一标识符

    "win": { //windows配置
      "icon": "", //图标
      "target": [
        {
          "target": "nsis", //使用nsis作为安装程序格式,生成的是exe安装程序(非msi)
          "arch": ["x64"] // 生成64位安装包
        }
      ]
    },
    "nsis": {
      "oneclick": false, //设置位false,是安装程序显示安装向导界面,而不是一键安装
      "perMachine": true, //允许每一台机器安装一次,而不是每一个用户安装一次
      "allowToChangeInstallationDirectory": true // 允许用户选择安装位置
    }
  },

scripts部分修改,build部分添加,在执行时删掉注释。


Thanks for reading!

electron

周四 5月 08 2025
2036 字 · 12 分钟