Electron(01)

发布于:2025-06-20 ⋅ 阅读:(11) ⋅ 点赞:(0)

Electron

Electron是什么

electron可以使用前端技术开发桌面应用,跨平台性,开发一套应用,可以打包到三个平台。

electron结合Chromium(谷歌内核)和 Node.js 和Native Api

当使用 Electron 时,很重要的一点是要理解 Electron 不是一个 Web 浏览器。 它允许您使用熟悉的 Web 技术构建功能丰富的桌面应用程序

官网:简介 | Electron


Electron广泛应用

使用Electron框架开发的知名软件。

Visual Studio Code、GithubDesktop、Postman、DockerDesktop...


Electron进程模型

主进程(main.js)。运行在node环境

一个应用只会有一个主进程,负责应用的生命周期、展示原生窗口、执行特殊操作和管理渲染进程。

渲染进程(render.js ),运行在浏览器环境

一个应用可以有多个渲染进程,渲染器进程负责展示图形内容。


快速上手

环境安装

node环境(v20.13.1)、npm环境(10.5.2)

测试安装是否成功,按下【win+R】键,输入cmd,打开cmd窗口

输入:node -v // 显示node.js版本

​ npm -v // 显示npm版本

npm config set registry https://registry.npmmirror.com/
 npm i electron -D

main.js

main 文件是 Electron 应用的入口。 这个文件控制 主程序 (main process),它运行在 Node.js 环境里,负责控制您应用的生命周期、显示原生界面、执行特殊操作并管理渲染器进程 (renderer processes)

const { app, BrowserWindow, ipcMain, Notification, Menu } = require('electron/main')
const path = require('node:path')
const fs = require('fs')

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

function readFile() {
    const res = fs.readFileSync('D:/hello.txt').toString();
    return res
}
const createWindow = () => {
    const win = new BrowserWindow({
        width: 1000,
        height: 800,
        icon: path.join(__dirname, 'favicon.ico'),
        title: '简单网页',
        webPreferences: {
            preload: path.join(__dirname, 'preload.js')
        }
    })
    ipcMain.on('file-save', writeFile)
    ipcMain.handle('file-read', readFile)

    //自定义菜单项
    let menuTemp = [
        {
            label: '文件',
            submenu: [
                {
                    label: '打开文件',
                    click() {
                        console.log('打开一个具体的文件')
                    }
                },
                { label: '打开文件夹' },
                {
                    label: '关于',
                    role: 'about'
                }
            ]
        },
        { label: '编辑' }
    ]

    //生成自定义菜单
    let menu = Menu.buildFromTemplate(menuTemp)
    Menu.setApplicationMenu(menu)

    win.loadFile('index.html')
    // 创建并显示通知
    const notification = new Notification({
        title: '主进程通知',
        body: '恭喜你,学会了求雨之术,风来~雨来~'
    }).show();

    // 确保在窗口创建后调用 openDevTools
    //win.webContents.on('did-finish-load', () => {
      //  win.webContents.openDevTools();
    //});

    // 定时发送时间给渲染进程(每1秒)
    setInterval(() => {
        if (win && !win.isDestroyed()) {
            win.webContents.send('main-time', new Date().toLocaleTimeString());
        }
    }, 1000);
}
app.whenReady().then(createWindow)

index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';" />
</head>

<body>
    <div id="time">当前时间:加载中...</div>
    <div class="hint">注意:输入内容,可以保存到d:/hello.txt,点击读取,可以读取该文件内容</div>
    <input id="input" type="text">
    <button id="btn2">向D盘输入hello.txt</button>
    <br>
    <br>
    <hr>
    <button id="btn3">读取D盘hello.txt</button>
    <script type="text/javascript" src="./render.js"></script>
</body>

</html>

render.js

const timeElement = document.getElementById('time');
const btn2 = document.getElementById('btn2');
const btn3 = document.getElementById('btn3');
const input = document.getElementById('input');

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

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

// 监听主进程发送的时间消息
myAPI.onMainTime((time) => {
    timeElement.textContent = `当前时间:${time}`;
});

preload.js

preload.js执行时机:预加载脚本在渲染器加载网页之前注入

预加载脚本preload.js(中间人),它在渲染进程运行(浏览器环境),人家也能访问一部分的node api。

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

contextBridge.exposeInMainWorld('myAPI', {
    saveFile: (data) => {
        ipcRenderer.send('file-save', data)
    },
    readFile: () => {
        return ipcRenderer.invoke('file-read')
    },
    // 监听主进程发送的时间消息
    onMainTime: (callback) => {
        ipcRenderer.on('main-time', (event, time) => callback(time))
    }
})

ipc进程通信

remote模块

Electron 官方已经明确表示,remote 模块是一个遗留的 API,不建议在新项目中使用。 他们鼓励开发者迁移到 Context Bridge。

remote 模块安全风险: 正如之前提到的,remote 模块允许渲染进程直接访问主进程的几乎所有对象,这带来了严重的安全风险。 恶意代码可能会利用 remote 模块来执行任意代码,甚至控制整个应用程序。

替代方案: Context Bridge (结合 ipcRenderer 和 ipcMain) 提供了更安全、更可控的进程间通信方式。


[渲染进程]调用[主进程] 无返回

需求:比如在页面上向d盘的文件写内容

主进程 ipcMain.on,写在app.whenReady().then()中

ipcMain.on('file-save', writeFile)

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

ipcRender.send 通过预加载脚本暴露了一个单行的 saveFile函数。

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

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

})

最后在渲染器进程,也就是HTML页面就可以使用暴露出来的saveFile方法了

const btn2 = document.getElementById('btn2');
const input = document.getElementById('input');

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

[渲染进程]调用[主进程] 有返回

双向 IPC 的一个常见应用是从渲染器进程码调用代主进程模块并等待结果,更适合请求-响应模式

需求:在页面上读取d盘的文件内容,点击按钮弹出d盘hello.txt文件内容

主进程 ipcMain.handle,写在app.whenReady().then()中

ipcMain.handle('file-read', readFile)

function readFile() {
    const res = fs.readFileSync('D:/hello.txt').toString();
    return res
}

ipcRender.invoke 通过预加载脚本暴露了一个单行的 readFile函数。

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

contextBridge.exposeInMainWorld('myAPI', {
    readFile: () => {
       return ipcRenderer.invoke('file-read')
    }

})

最后在渲染器进程,也就是HTML页面就可以使用暴露出来的readFile方法了

const btn3 = document.getElementById('btn3');
btn3.onclick = async () => {
    let data = await myAPI.readFile()
    alert(data)
}

[主进程]调用[渲染进程]

需求:页面显示当前时间,每1秒更新一次,来自主进程定时推送

主进程:webContents.send  发送消息

    // 定时发送时间给渲染进程(每1秒)
    setInterval(() => {
        if (win && !win.isDestroyed()) {
            win.webContents.send('main-time', new Date().toLocaleTimeString());
        }
    }, 1000);

ipcRenderer.on 预加载脚本监听主进程消息,暴露出onMainTime函数给渲染进程

callback本质是渲染进程向预加载脚本“注册”的一个“数据接收器”,让预加载脚本能把主进程的数据“交给”渲染进程处理。

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

contextBridge.exposeInMainWorld('myAPI', {
    // 监听主进程发送的时间消息
    onMainTime: (callback) => {
        ipcRenderer.on('main-time', (event, time) => callback(time))
    }
})

渲染进程

渲染进程调用onMainTime时传递了一个callback

const timeElement = document.getElementById('time');

//监听主进程发送的时间消息
myAPI.onMainTime((time) => {
    timeElement.textContent = `当前时间:${time}`;
});

打包应用

electron-builder

需要梯子

安装打包工具

npm install electron-builder -D

打包应用 electron-builder(做很多自定义的东西)

愉快执行:npm run build,安装包在dist目录,74MB,安装后包含node和chromiun

npm run build

electron-forge

安装打包工具

npm install --save-dev @electron-forge/cli
npx electron-forge import

执行打包

npm run make

执行成功后,默认输出目录为out/make,里面会生成平台对应的分发文件,例如:

  • Windowsout/make/squirrel.windows/x64/my-app-1.0.0 Setup.exe(安装包)、my-app-1.0.0-full.nupkg(更新包)。
  • macOSout/make/dmg/my-app-1.0.0.dmg(磁盘镜像)、out/make/zip/my-app-1.0.0.zip(压缩包)。
  • Linuxout/make/deb/x64/my-app_1.0.0_amd64.deb(Debian包)、out/make/rpm/x64/my-app-1.0.0-1.x86_64.rpm(RPM包)。

网站公告

今日签到

点亮在社区的每一天
去签到