10 分钟带你入坑 electron

在找工作的伙伴,可以看看这里:双越老师联合几位博主(包括我)搞了一个前端面试网站 面试派 ------ 常见面试题 + 大厂面试流程 + 面试技巧。做一个真正专业的前端面试网站,旨在解决前端面试资料碎片化、老旧化、非专业化等一系列问题,网站开源免费且持续更新题库

前言

electron 本质上由 Chromiumnode.js 二者构成,我们会用它来写桌面端应用,写法上还是写 node.js,其实就是 jsnode 赋予了 js 能够和操作系统打交道的能力,写法上还是 htmljscss 三把斧,所以我们在 electron 中写 vuereact 也是可以的,因为框架最终还是静态文件。

目前像 vscodegithub 桌面端,postmanWhatsApp 桌面端,Skype微信 桌面端,新版 QQ飞书 桌面端,Figma 等知名商业软件都是 electron 应用,这些应用利用了 electron 的跨平台特性,使得同一套代码能在 windowsmacOSLinux 上运行,不过 electron 饱受诟病的是它的内存占用以及应用体积很大。

其实 vscode 你是可以点击 帮助 ,然后进入 开发者模式 ,你会发现打开了一个 F12 窗口,很神奇,这么看 vscode 就像是浏览器一样。

electrontauri 该如何抉择

electrontauri 是目前最流行的前端开发桌面端框架,先看下各自优缺点。

electron 优缺点

优点

  • 无需考虑兼容性。
  • 学习成本低,可能你看完本文就能上手了。
  • 社区生态支持完善。

缺点

  • 体积大。
  • 仅支持 windowsmacLinux,不支持 安卓ios
  • 开发语言仅支持 jshtmlcss,当然也可以使用 vuereact 等前端框架,不支持其他语言。

electron 的体积很大,应用会自己内嵌一个 ChromiumNode.js,内嵌后就不像 tauri 一样考虑不同浏览器下的 api 差异性。

tauri 优缺点

优点

  • 体积小。
  • 可以使用不同的编程语言开发,不仅仅支持 jshtmlcss,还支持 kotlinswiftrustC#(.net) 更多语言。
  • 支持 windowsmaclinux 还支持 安卓ios

缺点

  • 新兴框架生态不够完善。
  • linux 上兼容性不好。
  • 学习成本高,需要有 rust 功底。

tauri 体积比较小,这是有代价的,tauriwindows 用的是 原生 **webview**,在 macOS 用的是 webkit,不同浏览器上样式以及 api 兼容性会有所不同,比如 requestIdleCallbackSafari 上就不支持。本质上,tauri 就是调用宿主系统的浏览器,问题就在于宿主浏览器五花八门,很容易出问题。

总结

要是看完你还是在选择上犹豫,那我建议你还是无脑选择 electron,大厂都在用,跟着他们的脚步准没错。其实小型应用用 tauri 挺好,比如你单纯想实现一个剪贴板软件小工具,用 electron 打底也要 250+ M,用 tauri 可能就 30+ M,大型应用还是建议用 electron

常用模块

app

app 模块控制应用程序的生命周期

app.whenReady().then

应用初始化完成后执行特定操作,基本等效 app.on('ready', callback)

app.quit()

尝试关闭所有窗口并退出应用程序,此方法会先触发 before-quit 事件,若所有窗口成功关闭,则触发 will-quit 事件,随后应用程序终止

app.exit()

立即终止应用程序,并可指定退出代码,与 app.quit 不同,此方法不会触发 before-quitwill-quit

app.relaunch()

在当前实例退出时重新启动应用程序,可指定新的启动参数或可执行路径

BrowserWindow

BrowserWindow 模块可用于创建和控制浏览器窗口

new BrowserWindow()

创建一个新的浏览器窗口实例,参数可用于设置窗口的宽度,高度,是否显示菜单栏等属性

win.loadURL()

在窗口中加载指定的 url

win.show()

显示窗口

win.hide()

隐藏窗口

win.webContents()

获取窗口的 webContents 对象,用于与页面内容交互

ipcMain 和 ipcRenderer

这两个模块实现了主进程和渲染进程之间的通信机制

ipcMain 模块在主进程中使用,用于监听和处理从渲染进程发送过来的消息

ipcRenderer 模块在渲染进程中使用,用于发送消息到主进程或接受主进程的回复

shell

shell 模块提供与桌面集成相关的功能,允许应用程序使用系统默认的方式管理文件和 URL,该模块可以在主进程和非沙盒的渲染进程中使用

shell.showItemInFolder()

在文件管理器中显示指定的文件,并尽可能选中该文件

shell.openPath()

以桌面默认方式打开给定的文件,返回一个 promise,如果操作失败,promise 将解析为包含错误信息的字符串,否则解析为一个空字符串

shell.openExternal()

使用默认的浏览器打开指定的 url

shell.trashItem()

将指定的文件或目录移动至回收站,返回一个 promise,如果操作失败,promise 将解析为包含错误信息的字符串,否则解析为一个空字符串

dialog

dialog 模块提供了与用户进行文件和消息交互的原生系统对话框

dialog.showOpenDialog()

显示打开文件对话框

dialog.showSaveDialog()

显示保存文件对话框

dialog.showMessageBox()

显示消息框

clipboard

clipboard 模块用于与系统剪贴板交互,允许读写文件,图像等内容

clipboard.readText()

读取剪贴板中文本内容,可选参数指定要读取的剪贴板类型,比如 selection

clipboard.writeText()

将文本写入剪贴板

clipboard.readImage()

读取剪贴板中的图像内容,返回 NativeImage 对象

clipboard.writeImage()

将图像写入剪贴板

clipboard.clear ()

清空剪贴板内容

session

session 模块管理 electron 应用的会话设置,包括缓存,cookie,代理设置等

session.defaultSession

获取默认会话对象

session.fromPartition()

根据分区名称获取会话对象

session.cookies

访问和操作会话的 cookie

session.clearCache()

清除会话缓存

session.setProxy()

设置代理配置

Menu 模块用于创建应用程序的菜单,MenuItem 模块用于创建菜单选项

设置应用程序的菜单

从模板数组中构建菜单

net

net 模块提供了一组 API,用于发起 HTTPS 请求,类似于 Node.js 的 http 模块,但是提供了更高级的功能

net.request()

创建 ClientRequest 实例用于发起 HTTP(S) 请求,options 可以是 URL 字符串或配置对象

ClientRequest 对象的方法

  • request.write() 向请求主体写入数据
  • request.end() 结束请求,发送剩余的数据
  • request.abort() 终止请求

protocol

protocol 模块允许开发者拦截和自定义协议的行为,例如 http(s) ,甚至可以实现自定义协议

protocol.registerSchemesAsPrivileged()

注册自定义协议为特权协议,已启用 CORS,安全等

protocol.registerFileProtocol()

注册一个协议以处理文件请求

protocol.registerHttpProtocol()

注册一个协议以处理 HTTP 请求

protocol.unregisterProtocol()

注销已注册的协议

contextBridge

contextBridge 模块用于在渲染进程的隔离上下文中创建一个安全的,双向的,同步的桥梁,使预加载脚本可以安全地将功能暴露给网页

contextBridge.exposeInMainWorld(apiKey, api)

将预加载脚本中的 api 暴露给浏览器

apiKeywindow 对象上挂在 api 的键名

api 要暴露的 api 对象

进程间通信 IPC

进程之间的通信简称就是 ipceletron 的进程分为两种,主进程和渲染进程,eletron 项目就是一个 主进程,渲染进程可以有多个,一个主进程控制多个渲染进程,一个窗口就是一个渲染进程

electron 项目一定会有个 main.js,这个 文件 就是来控制 主进程的

一般渲染进程的就是 render.js ,我们只需要在 html 文件中引入 render.js 即可

主进程的环境就是 node ,我们写不了浏览器的 api,渲染进程就是 浏览器环境,同样的,这里也用不了 node 的模块。比如 window 是浏览器的 api,主进程拿不到这个 api__dirnamenodeapi,渲染进程就拿不到了。这么看,二者就是独立的,但是二者必须要通信,比如我现在希望 窗口可以展示我的 electron 版本,这个版本数据必须是从主进程才能拿到,实际上需要二者通信的案例比比皆是。

预加载脚本 preload.js

主进程和渲染进程的通信需要一个中间人----预加载脚本 preload.js,通常这个文件和main.js 一样,放在根目录下面

preload.js 本质上还是在浏览器环境,所以你这里的 log 只能在窗口的控制台才能看到,preload.js 特殊点,它可以访问一部分 nodeapi,如图

预加载脚本可以在浏览器环境中,能够使用一部分 node api 也是蛮合理的, 想要让 preload.js 生效,我们需要给 主进程的 BrowserWindow 添加一个配置项

js 复制代码
function createWindow () {
    const win = new BrowserWindow({
        // ***
        webPreference: {
            preload: path.resolve(__dirname, './preload.js') // 记得引入 path 模块
        }
    })
}

主进程,预加载脚本,渲染进程的执行顺序一定是 从左往右

预加载脚本是可以将部分 node api 暴露出来给到 渲染进程中使用,比如下面

js 复制代码
const { contextBridge } = require('electron')
contextBridge.exposeInMainWorld('myAPI', {
    version: process.versions
})

我们就可以在 render.js 中拿到 这个 myAPImyAPI 下面就是 version 这个 key,其实这个暴露到了 window 上面

渲染向主进程通信(单)ipcRenderer.send, ipcMain.on

这里举一个🌰,我希望在窗口中的 input 框中写入内容到操作系统,这里肯定是需要渲染进程向主进程通信的,主进程代表 node,可以与操作系统打交道

先把 render.js 实现好

js 复制代码
const btn = document.getElementById('btn')
const input = document.getElementById('input')

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

需要 preload.js 引入一个 模块 ipcRenderer

js 复制代码
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('myAPI', {
    saveFile: () => {
        ipcRenderer.send('file-save', data) // 参数一:事件名,参数二:callback
    }
})

主进程需要订阅来自 preload.js 发布的事件

js 复制代码
const { app, BrowserWindow, ipcMain } = require('electron')

function createWindow () {
    const win = new BrowserWindow({
        // ***
    })
    ipcMain.on('file-save', writeFile)
}

function writeFile (_, data) { // 第一个参数为 event,不需要就用 _ 占位
    // 调用 fs 模块写入文件
}

主进程的事件 callback,默认有两个参数,一个 事件,一个传进来的参数

渲染向主进程通信(双)ipcRenderer.invoke, ipcMain.handle

这里是双向通信,为了体现双向,这里依旧是上面的🌰,我现在希望在窗口可以读取出操作系统的文件内容

preload.js

js 复制代码
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('myAPI', {
    async readFile () {
        let res = await ipcRenderer.invoke('file-read') // 第二个参数就是传入的参数
    }
})

main.js

js 复制代码
const { app, BrowserWindow, ipcMain } = require('electron')

function createWindow () {
    const win = new BrowserWindow({
        // ***
    })
    ipcMain.handle('file-read', readFile)
}

function readFile () {
    // 调用 js 模块读取文件内容
    return fs.readFileSync('/****').toString() // 正常读取是 buffer,需要转成 字符串
}

这里双向就是因为 ipcRenderer.invoke 的事件发给了 ipcMain.handle 订阅后,handle 会将返回值给到 ipcRenderer.invoke,给个结果一定是个 promise,所以需要用 await 接受,否则会是 pending 态的 promise,当然你也可以把 promise 写到 render.js

主进程向渲染进程通信(单)webContents.send, ipcRenderer.on

这个其实 就是 渲染向主进程通信(单)的反写

main.js

js 复制代码
function createWindow () {
    win.webContents.send('message', 'Hello World')
}

preload.js

js 复制代码
contextBridge.exposeInMainWorld('myAPI',{
    getMessage: (data) => {
        return ipcRenderer.on('message', data)
    }
})

渲染进程之间通信

渲染进程之间是不能直接通信的,所以需要借助 主进程 作为中间人来进行通信

election-store

electron 的存储方案总的来看就是下面三种

  • web 浏览器 api

    electron 使用了 Chromium 来渲染,因此可以使用 cookielocalStoragesessionStorageindexDB 等来实现本地存储,这个方案适用于存储少量数据或临时数据

  • 文件系统操作

    利用 node.js 的文件系统模块

  • 数据库技术

    electron 支持多种数据库技术,比如 SQLiteLevelDB,以实现更高效的大量结构化数据

electron-store 是一个较为特殊的方式,他是一个专门用于 electron 应用设计的轻量级数据持久化库,使用简单,依赖少,他属于 electron 但又基于node 的文件读取,它会将信息存到一个 json 中,而且提供了类似 浏览器 api 方式的读写方式

js 复制代码
const { app, BrowserWindow } = require('electron')
const Store = require('electron-store')

const store = new Store()

function createWindow () {
    const win = new BrowserWindow({
        width: 800,
        height: 600,
        webPreference: {
            preload: path.join(__dirname, 'preload.js')
        }
    })
    
    win.loadFile('index.html')
}

app.whenReady('unicorn', '独角兽')
console.log(store.get('unicorn')) // 输出:独角兽

store.set('foo.bar', true)
console.log(store.get('foo')) // 输出 { bar: true 

由上代码可以看出,electron-store 的使用还是非常无脑的,无需你写 sql 语句,你还能存入信息,写法上就像是 浏览器 的缓存

electron-store 既然本质上还是 node.js 的读写,所以它是不能使用在 渲染进程的,否则程序会白屏

存入的 json 文件,默认情况下是在 app.getPath('userData') 目录下的 config.json 文件中

windows macos 差异性

通常来讲,一个应用会有很多个窗口,在 windows 下,我们的行为通常是关闭最后一个 窗口 就会直接关闭应用,而 macos 下,关闭最后一个 窗口,应用的进程仍然保留,对此,我们这里的差异性我们也需要实现

js 复制代码
app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') app.quit()
})

app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
})

第一个部分就是当所有窗口关闭时,并且操作系统不是 darwin,这个 darwin 就是 macos,所以就是 windows, 就直接退出 应用

第二个部分就是当应用被激活时,若没有了窗口,那么就会去创建窗口

打包方式

eletron 的打包有四种工具,但是最推荐的也是比较繁琐的工具就是 electron-builder,最推荐是因为相比其他方式打包出的体积小一点,繁琐是因为它可以进行很多自定义的设置

打包是需要我们对 package.json 进行配置的

json 复制代码
{
    "scripts": {
        "start": "electron .",
        "build": "electron-build" // 打包指令,生成安装包
    },
    "build": {
        "appId": "", // 程序的唯一标识符
        "win": { // 代表打包到 windows 平台
            "icon": "", // 应用图标
            "target": [
                {
                    "target": "nsis", // 使用 NSIS 作为安装程序格式,其实就是 exe 文件,微软还有中 msi 安装格式
                    "arch": ["x64"] // 生成 64 位安装包
                }
            ]
        },
        "nsis": {
            "oneClick": false, // 设置为 false 使安装程序显示安装向导界面,而不是一键安装
            "perMachine": true, // 允许每台机器安装一次,而不是每个用户都安装
            "allowToChangeInstallationDirectory": true // 允许用户在安装过程中选择安装目录
        }
    }
}

json 文件不支持 注释,这里仅仅给大家看含义

打包的产物会放到 dist 文件夹目录下面,你可能会发现没写几行 jseletron 打包成应用也有 70+ M 的安装包,这其实就包含了 node 环境和 Chromium 环境,当你安装完毕发现大小又变大了,可能会有 250+ M

electron-vite

我们一般不会去真的去写 htmljscss,一般都是用 vuereact,其中 electron-vite 就是让我们用 vue/react + electron 的形式去开发

electron-vite 如何支持 VueReact

  • 渲染进程支持

    electron-vite 为渲染进程提供了对 vue/react 等现代前端框架的支持

  • 模块热替换(HMR)

    在开发过程中,electron-vite 为渲染进程提供了快速的模块热替换功能,这样修改 vue, react 组价时,无需重启应用即可实时看到更改效果

帮助开发

nodemon

我们修改 js 文件时,若发现需要手动重启才能生效,可以试着用 nodemon 这个插件

nodemon.json

若修改的是 html 文件,我们希望可以立即生效,那么就需要配置 nodemon.json 文件

json 复制代码
{
    "ignore": [
        "node_modules",
        "dist"
    ],
    "restartable": "r", // 项目运行阶段在控制台输入 r 也可以拥有重启的效果
    "watch": ["*.*"], // 监视所有文件
    "ext": "html,js,css" // 包括 html, js, css 文件
}

最后

其实学习 electron 最重要的就是学习它的 进程间通信(IPC),这部分搞明白了,其余内容需要用到是直接参考 electron 官网看 api 就行,其实这个东西本质就是发布订阅模式,所以还是非常好理解的,这也就是大家为何喜欢用 electron 去开发应用,主要就是因为学习成本低以及生态完善,应用体积大就是因为每个软件本质上都包含了完整的 Chromium 浏览器引擎 和 Node.js 运行时,这也无可厚非

文章中若出现错误内容还请各位大佬见谅并指正。如果有任何问题或建议,欢迎指出,另外,有不懂之处欢迎在评论区留言。如果觉得文章对你的学习有所帮助,还请 关注、点赞、收藏 一键三连,感谢支持!欢迎关注我的公众号: Dolphin_Fung

相关推荐
uhakadotcom11 分钟前
Sentry:你的应用程序的守护者
前端·面试·github
勘察加熊人16 分钟前
fastapi +angular迷宫求解可跨域
前端·fastapi·angular.js
敲敲敲敲暴你脑袋17 分钟前
高德地图自定义canvas实现聚类分析
javascript·数据可视化·canvas
一个处女座的程序猿O(∩_∩)O20 分钟前
Vue 计算属性与 Data 属性同名问题深度解析
前端·javascript·vue.js
随风九天1 小时前
使用 Nginx 进行前端灰度发布的策略与实践
运维·前端·nginx·前端灰度发布
黄Java1 小时前
SVG中linearGradient的id冲突的显隐问题深度解析
前端·svg
蜗牛快跑1232 小时前
通过尤大“围绕Vite的前端统一框架”分享,看未来前端发展趋势
前端
skywalk81632 小时前
Mac下安装Zed以及Zed对MCP(模型上下文协议)的支持
服务器·前端·macos
陈龙龙的陈龙龙2 小时前
macOS 安装 Homebrew、nvm 及安装切换 node 版本
前端·macos·bash
asphyxia2 小时前
老龄化项目问题解决
前端