IM跨平台技术学习(十一):环信基于Electron打包Web IM桌面端的技术实践

简介: 这次借着论证 Web IM端 SDK 是否可以在 Electron 生成的桌面端正常稳定使用,我决定把官方新推出的 webim-vue3-demo,打包到桌面端,并记录了这次验证的过程以及所遇到的问题和解决方法。

本文由环信技术黄飞鹏分享,原题“实战|如何利用 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({

   width: 980,

   height: 680,

   fullscreen: true,

   skipTaskbar: true,

   webPreferences: {

     nodeIntegration: true,

     preload: path.join(__dirname, 'preload.js'),

   },

 });

 

 if (NODE_ENV === 'development') {

   mainWindow.loadURL('http://localhost:9001/');

   mainWindow.webContents.openDevTools();

 } else {

   mainWindow.loadURL(`file://${path.join(__dirname, '../dist/index.html')}`);

 }

}

 

// 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": {

   "dev": "vue-cli-service serve",

   "build": "vue-cli-service build",

   "lint": "vue-cli-service lint",

   "electron:dev": "wait-on tcp:9001 && cross-env NODE_ENV=development  electron ./"

 },

 "dependencies": {

   "@vueuse/core": "^8.4.2",

   "agora-rtc-sdk-ng": "^4.14.0",

   "axios": "^0.27.2",

   "benz-amr-recorder": "^1.1.3",

   "core-js": "^3.8.3",

   "easemob-websdk": "^4.1.6",

   "element-plus": "^2.2.5",

   "nprogress": "^0.2.0",

   "pinyin-pro": "^3.10.2",

   "vue": "^3.2.13",

   "vue-router": "^4.0.3",

   "vuex": "^4.0.0"

 },

 "devDependencies": {

   "@babel/core": "^7.12.16",

   "@babel/eslint-parser": "^7.12.16",

   "@vue/cli-plugin-babel": "~5.0.0",

   "@vue/cli-plugin-eslint": "~5.0.0",

   "@vue/cli-plugin-router": "~5.0.0",

   "@vue/cli-plugin-vuex": "~5.0.0",

   "@vue/cli-service": "~5.0.0",

   "cross-env": "^7.0.3",

   "electron": "^24.3.1",

   "eslint": "^7.32.0",

   "eslint-plugin-vue": "^8.0.3",

   "sass": "^1.51.0",

   "sass-loader": "^12.6.0",

   "wait-on": "^7.0.1"

 }

}

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": {

   "dev": "vue-cli-service serve",

   "build": "vue-cli-service build",

   "lint": "vue-cli-service lint",

   "electron:dev": "wait-on tcp:9001 && cross-env NODE_ENV=development  electron ./",

   "electron:build": "rimraf dist &&  vue-cli-service build &&  electron-builder",

   "electron:build2": "electron-builder"

 },

 "dependencies": {

   "@vueuse/core": "^8.4.2",

   "agora-rtc-sdk-ng": "^4.14.0",

   "axios": "^0.27.2",

   "benz-amr-recorder": "^1.1.3",

   "core-js": "^3.8.3",

   "easemob-websdk": "^4.1.6",

   "element-plus": "^2.2.5",

   "nprogress": "^0.2.0",

   "pinyin-pro": "^3.10.2",

   "vue": "^3.2.13",

   "vue-router": "^4.0.3",

   "vuex": "^4.0.0"

 },

 "devDependencies": {

   "@babel/core": "^7.12.16",

   "@babel/eslint-parser": "^7.12.16",

   "@vue/cli-plugin-babel": "~5.0.0",

   "@vue/cli-plugin-eslint": "~5.0.0",

   "@vue/cli-plugin-router": "~5.0.0",

   "@vue/cli-plugin-vuex": "~5.0.0",

   "@vue/cli-service": "~5.0.0",

   "cross-env": "^7.0.3",

   "electron": "^24.3.1",

   "electron-builder": "^23.6.0",

   "eslint": "^7.32.0",

   "eslint-plugin-vue": "^8.0.3",

   "sass": "^1.51.0",

   "sass-loader": "^12.6.0",

   "wait-on": "^7.0.1"

 },

 "build": {

   "productName": "webim-electron",

   "appId": "com.lvais",

   "copyright": "2023@easemob",

   "directories": {

     "output": "output"

   },

   "extraResources": [

     {

       "from": "./src/assets",

       "to": "./assets"

     }

   ],

   "files": ["dist/**/*", "electron/**/*"],

   "mac": {

     "artifactName": "${productName}_${version}.${ext}",

     "target": ["dmg"]

   },

   "win": {

     "target": [

       {

         "target": "nsis",

         "arch": ["x64"]

       }

     ],

     "artifactName": "${productName}_${version}.${ext}"

   },

   "nsis": {

     "oneClick": false,

     "allowElevation": true,

     "allowToChangeInstallationDirectory": true,

     "createDesktopShortcut": true

   },

   "linux": {}

 }

}

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: {

   host: 'localhost',

   port: 9001,

   // https:true

 },

 publicPath: './',

 chainWebpack: (config) => {

   //最小化代码

   config.optimization.minimize(true);

   //分割代码

   config.optimization.splitChunks({

     chunks: 'all',

   });

 },

});

原因是:打包后的应用 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: `${

   window.location.protocol === 'file:' ? 'https:' : window.location.protocol

 }${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) => {

   // do something before request is sent

   return config;

 },

 (error) => {

   // do something with request error

   console.log('request error', error); // for debug

   return Promise.reject(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) => {

   const res = response.data;

   const code = response.status;

   // if the custom code is not 20000, it is judged as an error.

   if (code >= 400) {

     return Promise.reject(new Error(res.desc || 'Error'));

   } else {

     return res;

   }

 },

 (error) => {

   if (error.response) {

     const res = error.response.data; // for debug

     if (error.response.status === 401 && res.code !== '001') {

       console.log('>>>>>无权限');

     }

     if (error.response.status === 403) {

       res.desc = '您没有权限进行查询和操作!';

     }

     return Promise.reject(res.desc || error);

   }

   return Promise.reject(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

目录
相关文章
|
21天前
|
Ubuntu Linux vr&ar
IM跨平台技术学习(十二):万字长文详解QQ Linux端实时音视频背后的跨平台实践
本文详细记录了新版QQ音视频通话在 Linux 平台适配开发过程中的技术方案与实现细节,希望能帮助大家理解在 Linux 平台从 0 到 1 实现音视频通话能力的过程。
49 2
|
1月前
|
数据库 Python
Python实践:从零开始构建你的第一个Web应用
使用Python和轻量级Web框架Flask,你可以轻松创建Web应用。先确保安装了Python,然后通过`pip install Flask`安装Flask。在`app.py`中编写基本的"Hello, World!"应用,定义路由`@app.route('/')`并运行`python app.py`启动服务器。扩展应用,可添加新路由显示当前时间,展示Flask处理动态内容的能力。开始你的Web开发之旅吧!【6月更文挑战第13天】
68 2
|
1天前
|
监控 安全 应用服务中间件
如何搭建高效的Web服务器:技术指南与实践
【7月更文挑战第24天】搭建一个高效的Web服务器需要综合考虑多个方面,包括选择合适的操作系统、安装合适的Web服务器软件、进行配置优化、加强安全防护以及实施性能监控。通过不断地优化和调整,可以确保Web服务器在高负载下仍能保持稳定和高效的运行,为用户提供优质的访问体验。
|
8天前
|
JavaScript 前端开发 网络协议
从理论到实践:全面剖析Python Web应用中的WebSocket实时通信机制
【7月更文挑战第17天】WebSocket在实时Web应用中扮演重要角色,提供全双工通信,减少延迟。本文详述了Python中使用`websockets`库创建服务器的步骤,展示了一个简单的echo服务器示例,监听8765端口,接收并回显客户端消息。客户端通过JavaScript与服务器交互,实现双向通信。了解WebSocket的握手、传输和关闭阶段,有助于开发者有效利用WebSocket提升应用性能。随着实时需求增长,掌握WebSocket技术至关重要。
29 6
|
6天前
|
JSON 中间件 数据处理
实践出真知:通过项目学习Python Web框架的路由与中间件设计
【7月更文挑战第19天】探索Python Web开发,掌握Flask或Django的关键在于理解路由和中间件。路由连接URL与功能,如Flask中@app.route()定义请求响应路径。中间件在请求处理前后执行,提供扩展功能,如日志、认证。通过实践项目,不仅学习理论,还能提升构建高效Web应用的能力。示例代码展示路由定义及模拟中间件行为,强调动手实践的重要性。
|
18天前
|
前端开发 JavaScript API
现代前端开发中的Web组件化设计与实践
在现代前端开发中,Web组件化已经成为了一个关键的设计思想和实践方法。本文探讨了Web组件化的概念、优势以及如何在实际项目中进行设计和应用。通过分析实例和最佳实践,展示了如何利用组件化开发提升前端开发效率和代码可维护性,同时也解决了在大型项目中常见的代码重用和团队协作问题。
|
21天前
|
人工智能 开发框架 Devops
.NET技术概览:** 本文探讨了.NET的核心特性,包括多语言支持、Common Language Runtime、丰富的类库和跨平台能力,强调其在企业级、Web、移动及游戏开发中的应用。
【7月更文挑战第4天】.NET技术概览:** 本文探讨了.NET的核心特性,包括多语言支持、Common Language Runtime、丰富的类库和跨平台能力,强调其在企业级、Web、移动及游戏开发中的应用。此外,讨论了.NET如何通过性能优化、DevOps集成、AI与ML支持以及开源策略应对未来挑战,为开发者提供强大工具,共创软件开发新篇章。
21 3
|
22天前
|
存储 JSON 安全
深入理解与实践:Token的使用及其在Web应用安全中的重要性
【7月更文挑战第3天】在现代Web应用程序中,Token作为一种关键的安全机制,扮演着维护用户会话安全、验证用户身份的重要角色。本文将深入探讨Token的基本概念、类型、工作原理,并通过实际代码示例展示如何在Web应用中实现Token的生成、验证及应用,以确保数据传输的安全性和用户认证的有效性。
58 1
|
28天前
|
边缘计算 JSON 网络协议
移动端IM开发者必读(三):爱奇艺移动端跨国弱网通信的优化实践
本次分享的文章内容,基于爱奇艺面向全球用户推出的国际版,在海外跨国网络环境复杂的前提下,针对性地做了一系列弱网优化实践,取得了不错的效果,在此总结分享我们的一些做法和优化思路,希望对你有所帮助。
30 1
|
1月前
|
前端开发 JavaScript 开发工具
Web前端开发学习资料:深度探索与开发实践
Web前端开发学习资料:深度探索与开发实践
25 3