IM 跨平台技术学习 (十一):环信基于 Electron 打包 Web IM 桌面端的技术实践
本文由环信技术黄飞鹏分享,原题“实战|如何利用 Electron 快速开发一个桌面端应用”,本文进行了排版和内容优化等。
1、引言
早就听说利用 Electron 可以非常便捷的将网页端快速打包成桌面应用,并且利用 Electron 提供的 API 调用可以使用原生桌面 API 一些高级功能。于是这次借着论证 Web IM 端 SDK 是否可以在 Electron 生成的桌面端正常稳定使用,我决定把官方新推出的 webim-vue3-demo,打包到桌面端,并记录了这次验证的过程以及所遇到的问题和解决方法。
技术交流:
移动端 IM 开发入门文章:《新手入门一篇就够:从零开发移动端 IM》
开源 IM 框架源码:https://github.com/JackJiang2011/MobileIMSDK(备用地址点此)
(本文已同步发布于:http://www.52im.net/thread-4666-1-1.html)
2、系列文章本文是系列文章中的第 11 篇,本系列总目录如下:
《IM 跨平台技术学习(一):快速了解新一代跨平台桌面技术——Electron》
《IM 跨平台技术学习(二):Electron 初体验(快速开始、跨进程通信、打包、踩坑等)》
《IM 跨平台技术学习(三):vivo 的 Electron 技术栈选型、全方位实践总结》
《IM 跨平台技术学习(四):蘑菇街基于 Electron 开发 IM 客户端的技术实践》
《IM 跨平台技术学习(五):融云基于 Electron 的 IM 跨平台 SDK 改造实践总结》
《IM 跨平台技术学习(六):网易云信基于 Electron 的 IM 消息全文检索技术实践》
《IM 跨平台技术学习(七):得物基于 Electron 开发客服 IM 桌面端的技术实践》
《IM 跨平台技术学习(八):新 QQ 桌面版为何选择 Electron 作为跨端框架》
《IM 跨平台技术学习(九):全面解密新 QQ 桌面版的 Electron 内存占用优化》
《IM 跨平台技术学习(十):快速选型跨平台框架 Electron、Flutter、Tauri、React Native 等》
《IM 跨平台技术学习(十一):环信基于 Electron 打包 WebIM 桌面端的技术实践》(* 本文)
3、前置技能
1)拥有良好的情绪自我管理,能够在遇到棘手问题时不一拳给到键盘;
2)拥有较为熟练的水群能力,能够在遇到问题时,主动向技术群内参差不齐的群友们抛出自己的问题;3)重要的是,要拥有较为熟练的搜索引擎使用能力;
4)能够看到这篇文章,那说明以上能力你已完全具备。
PS:不开玩笑的说,开始 Electron 的踩坑之前,肯定还是要对 Electron 的方方面面有所了解才能磨刀不误砍柴工,建议从《快速了解新一代跨平台桌面技术——Electron》、《Electron 初体验(快速开始、跨进程通信、打包、踩坑等)》这两篇开始。
4、第 1 步:准备工作
1)克隆 vue3 Demo 项目到本地(vue3-demo 的源码地址);
2)在编辑器内打开此项目并执行 yarn install 安装项目相关 npm 依赖;
3)在此项目目录下打开终端请敲下 yarn add electron,从而在该项目中安装 electron;
4)安装一些依赖工具 wait-on 以及 cross-env。
PS:如果访问 vue3 Demo 的 Github 仓库太慢,可以直接下载以下附件:
webim-vue-demo(demo-vue3).zip (1.05 MB , 下载次数: 0 , 售价: 1 金币)
wait-on:它是一个 Node.js 包,它可以用于等待多个指定的资源(如 HTTP 资源、TCP 端口或文件)变得可用。它通常用于等待应用程序的依赖项准备好后再启动应用程序。例如,您可以使用 wait-on 等待数据库连接、消息队列和其他服务就绪后再启动您的应用程序。这样可以确保您的应用程序在尝试使用这些资源之前不会崩溃。
cross-env:是一个 npm 包,它的作用是在不同平台上设置环境变量。在不同操作系统中,设置环境变量的方式是不同的。例如,在 Windows 中使用命令 set NODE_ENV=production 设置环境变量,而在 Unix/Linux/Mac 上则需要使用 export NODE_ENV=production 命令。
此时可能会进入到漫长的等待阶段(第一、这个包本身就比较大,第二、相信大家都懂由于网络原因导致),并且有可能进行会经历几次 TIMOUT 安装失败。此时就需要心平气和,且有耐心的进行改变镜像地址、科学进行上网,WIFI 切换为移动流量多去重试几次,相信道友你总会成功过的。
有如下输出则应该为安装成功:
5、第 2 步:项目目录增加 Electron 文件
在项目增加 Electron 文件时我们需要扩展一部分知识从而了解为什么创建创建这个目录,并在该目录下增加 main.js 文件的作用。当然如果觉得不需要可以直接略过。
5.1 主进程与渲染进程的概念
在 Electron 中,主进程和渲染进程是两个不同的概念。主进程是 Electron 应用程序的核心,它运行在一个 Node.js 实例中,并管理应用程序的生命周期、窗口创建和销毁、与底层操作系统进行交互等。主进程还可以通过 IPC(进程间通信)机制与渲染进程进行通信。
渲染进程则是应用程序的 UI 界面所在的进程。每个 Electron 窗口都有其自己的渲染进程。渲染进程是一个 Chromium 渲染引擎实例,它运行在一个仅包含 Web API 的环境中。渲染进程负责渲染 HTML、CSS 和 JavaScript,并处理来自用户的输入事件,同时通过 IPC 机制与主进程进行通信。
由于渲染进程只能访问 Web API 而不能直接访问 Node.js API,因此如果需要在渲染进程中使用 Node.js API,就需要通过 IPC 机制向主进程发出请求,由主进程代为执行并将结果返回给渲染进程。
PS:关于 Electron 的进程知识,可以详读《Electron 初体验(快速开始、跨进程通信、打包、踩坑等)》一文的“5、进程详解”一节。
5.2 主进程与渲染进程分别应该写在哪?
在 Electron 应用程序中,主进程通常写在名为 main.js 或者 index.js 的 JavaScript 文件中,这个文件是应用程序的入口点。
而渲染进程则通常写在 HTML 文件和其引入的 JavaScript 文件中。在一个 Electron 窗口中,可以通过调用 webContents 对象的 loadURL 方法来加载一个 HTML 文件,其中包含了渲染进程所需的代码和资源。该 HTML 文件中的 JavaScript 代码将运行在对应的渲染进程中,可以通过 Electron 提供的一些 API 和 Web API 来进行与用户界面相关的操作。
需要注意的是,在 Electron 中,由于主进程和渲染进程是不同的 Node.js 实例,因此它们之间并不能直接共享变量或者调用函数。如果想要实现主进程和渲染进程之间的通信,必须使用 Electron 提供的 IPC 机制,通过发送消息的方式来进行进程间通信。
5.3 有些 Electron 文件目录下 preload.js 的作用
在 Electron 中,preload.js 文件是一个可选的 JavaScript 文件,用于在渲染进程创建之前加载一些额外的脚本或者模块,从而扩展渲染进程的能力。preload.js 文件通常存放在与主进程代码相同的目录下。
preload.js 的实际运用主要有以下几个方面。
1)托管 Node.js API:preload.js 中可以引入 Node.js 模块,并将其暴露到 window 对象中,从而使得在渲染进程中也能够使用 Node.js API,避免了直接在渲染进程中调用 Node.js API 带来的安全风险;
2)扩展 Web API:preload.js 中还可以定义一些自定义的函数或者对象,然后将它们注入到 window 对象中,这样在渲染进程中就可以直接使用它们了,而无需再进行额外的导入操作;
3)进行一些初始化操作:preload.js 文件中的代码会在每个渲染进程的上下文中都运行一遍,在这里可以进行一些初始化操作,比如为页面添加一些必要的 DOM 元素、为页面注册事件处理程序等。
需要注意的是:preload.js 文件中的代码运行在渲染进程的上下文中,因此如果 preload.js 中包含一些恶意代码,那么它很可能会危及整个渲染进程的安全性。因此,在编写 preload.js 文件时,一定要格外小心,并且仅引入那些你信任的模块和对象。
1) 添加 Electron 文件(此时项目目录):
2) Electron 下新建 main.js 示例代码如下:
const { app, BrowserWindow } = require('electron');
const path = require('path');
const NODE_ENV = process.env.NODE_ENV;
app.commandLine.appendSwitch('allow-file-access-from-files');
function createWindow() {
// Create the browser window.
const mainWindow = new BrowserWindow({
});
if (NODE_ENV === 'development') {
} else {
}
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
createWindow();
});
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit();
});
3)Electron 下新建 preload.js,示例代码如下(此文件为可选文件):
//允许 vue 项目使用 ipcRenderer 接口, 演示项目中没有使用此功能
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('ipcRender', ipcRenderer);
4)修改 package.json:
当前示例代码如下:
1)修改"main"配置,将其指向为"main": "electron/main.js";2)增加一个针对 electron 启动的"scripts","electron:dev": "wait-on tcp:3000 && cross-env NODE_ENV=development electron ./"。当前项目配置如下所示:
{
"name": "webim-vue3-demo",
"version": "0.1.0",
"private": true,
"main": "electron/main.js",
"scripts": {
},
"dependencies": {
},
"devDependencies": {
}
}
6、第 3 步:本地启动起来验证一下
6.1 启动运行原 vue 项目
这里启动项目至端口号 9001,跟上面 electron/main.jsmainWindow.loadURL(' http://localhost:9001/')是可以对应上的,也就是 Electron 运行起来将会加载此服务地址。
yarn run dev
6.2 新开终端,启动 Electron
新开一个终端执行,输入下方命令启动 Electron。
执行下面命令:
yarn run electron:dev
并且经过测试验证登录没有什么问题。
7、第 4 步:尝试打包并验证打包出来的安装包是否可用
7.1 安装 electron-builder
该工具为 Electron 打包工具库,点击打开 electron-builder 官方文档。
终端执行下面命令安装 electron-builder:
yarn add electron-builder --dev
7.2 配置打包脚本命令及个性化配置项
package.json 配置打包脚本命令以及设置打包个性化配置项。
具体配置项作用请参考官网文档,下面有些配置也是 CV 大发过来的,没有具体深入研究。
{
"name": "webim-vue3-demo",
"version": "0.1.0",
"private": true,
"main": "electron/main.js",
"scripts": {
},
"dependencies": {
},
"devDependencies": {
},
"build": {
}
}
7.3 开始 build
先这样——build 原始 vue 项目:
yarn run build
再那样——build Electron 项目:
yarn run electron:build
可能会进入漫长的等待,但是不要慌,可能与网络关系比较大,需要耐心等待。
打包成功之后可以看到有一个 output 文件夹的生成,打开之后可以选择双击打开软件验证看下是否可以正常开启应用。
8、 痛苦踩坑 1:打包后页面空白等
8.1 概述
打包后页面空白并出现类似“Failed to load resource: net::ERR_FILE_NOT_FOUND”的报错。
问题简述:发现只有在打包之后的 Electron 应用,启动后存在页面空白,dev 情况下正常。
8.2 解决手段 1
经排查,更改 vue.config.js 中 publicPath 的配置为‘./’。
const { defineConfig } = require('@vue/cli-service');
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave: false,
devServer: {
},
publicPath: './',
chainWebpack: (config) => {
},
});
原因是:打包后的应用 Electron 会从相对路径开始找资源,所以经过此配置可以所有资源则开始从相对路径寻找。
默认情况下:Vue CLI 会假设你的应用是被部署在一个域名的根路径上,例如 https://www.my-app.com/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。
例如:如果你的应用被部署在 https://www.my-app.com/my-app/,则设置 publicPath 为 /my-app/。这个值也可以被设置为空字符串 ('') 或是相对路径 ('./'),这样所有的资源都会被链接为相对路
8.3 解决手段 2
经过一顿操作之后发现仍然还是空白,并且打开控制台看到页面可以正常加载资源文件,但是 index.html 返回此类错误:“We're sorry but XXX doesn't work properly without JavaScript”,经过查找发现可以通过修改路由模式来解决,经过测试确实有效。
参考文章为:《vue3 项目打包时 We're sorry but XXX doesn't work properly without JavaScript》。
修改后的代码示例:
const router = createRouter({
//改为 #则可以直接变更路由模式
history: createWebHistory('#'),
routes,
});
9、痛苦踩坑 2:页面展示正常后,调用登录报错
问题简述:页面展示正常后,调用登录发现出现如下图所示的报错。
解决方式:经发现原来是发起 axios 请求环信置换连接 token 接口的时候,协议的获取是通过 window.location.protocol 来获取的,那么打包之后的协议为 file:那么这时发起的请求就会变更为以 file 协议发起的请求,那么修改这里的逻辑,判断如果为 file 协议则默认走 http 协议发起请求。
示例代码如下:
import axios from 'axios';
const defaultBaseUrl = '//a1.easemob.com';
console.log('window.location.protocol', window.location.protocol);
// create an axios instance
const service = axios.create({
withCredentials: false,
// baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
baseURL: `${
}${defaultBaseUrl}`,
// withCredentials: true, // send cookies when cross-domain requests
timeout: 30000, // request timeout
headers: { 'Content-Type': 'application/json' },
});
// request interceptor
service.interceptors.request.use(
(config) => {
},
(error) => {
}
);
// response interceptor
service.interceptors.response.use(
/**
If you want to get http information such as headers or status
Please return response => response
*/
/**
Determine the request status by custom code
Here is just an example
You can also judge the status by HTTP Status Code
*/
(response) => {
},
(error) => {
}
);
export default service;
10、参考资料
[1] Electron 官方文档
[2] 快速了解新一代跨平台桌面技术——Electron
[3] Electron 初体验(快速开始、跨进程通信、打包、踩坑等)
[4] Electron + Vue3 + TS + Vite 桌面应用项目搭建教程
[5] Electron + Vue3 +Ant Design Vue 桌面应用从项目搭建到打包发布
[6] vivo 的 Electron 技术栈选型、全方位实践总结
[7] 一文读懂前端技术演进:盘点 Web 前端 20 年的技术变迁史
[8] 详解 Web 端通信方式的演进:从 Ajax、JSONP 到 SSE、Websocket
[9] WebSocket 从入门到精通,半小时就够!
(本文已同步发布于:http://www.52im.net/thread-4666-1-1.html)
评论