im跨平台技术学习(十一):环信基于electron打包web im桌面端的技术实践 -凯发k8网页登录

我的最新工程mobileimsdk:http://git.oschina.net/jackjiang/mobileimsdk
posts - 441, comments - 13, trackbacks - 0, articles - 0

本文由环信技术黄飞鹏分享,原题“实战|如何利用 electron 快速开发一个桌面端应用”,本文进行了排版和内容优化等。

早就听说利用electron可以非常便捷的将网页端快速打包成桌面应用,并且利用 electron 提供的 api 调用可以使用原生桌面 api 一些高级功能。于是这次借着论证 web im端 sdk 是否可以在 electron 生成的桌面端正常稳定使用,我决定把官方新推出的 ,打包到桌面端,并记录了这次验证的过程以及所遇到的问题和解决方法。

 
技术交流:

- 移动端im开发入门文章:《》

- 开源im框架源码:()

本文是系列文章中的第11篇,本系列总目录如下:

《》

《》

《》

《》

《》

《》

《》

《》

《》

《》

《》(* 本文

  • 1)拥有良好的情绪自我管理,能够在遇到棘手问题时不一拳给到键盘;
  • 2)拥有较为熟练的水群能力,能够在遇到问题时,主动向技术群内参差不齐的群友们抛出自己的问题;
  • 3)重要的是,要拥有较为熟练的搜索引擎使用能力;
  • 4)能够看到这篇文章,那说明以上能力你已完全具备。

ps:不开玩笑的说,开始electron的踩坑之前,肯定还是要对electron的方方面面有所了解才能磨刀不误砍柴工,建议从《》、《》这两篇开始。

  • 1)克隆 vue3 demo 项目到本地();
  • 2)在编辑器内打开此项目并执行yarn install安装项目相关 npm 依赖;
  • 3)在此项目目录下打开终端请敲下yarn add electron,从而在该项目中安装 electron;
  • 4)安装一些依赖工具wait-on以及cross-env。

ps:如果访问vue3 demo的github仓库太慢,可以直接下载以下附件:

  (1.05 mb , 下载次数: 0 , 售价: 1 金币)

:它是一个 node.js 包,它可以用于等待多个指定的资源(如 http 资源、tcp 端口或文件)变得可用。它通常用于等待应用程序的依赖项准备好后再启动应用程序。例如,您可以使用 wait-on 等待数据库连接、消息队列和其他服务就绪后再启动您的应用程序。这样可以确保您的应用程序在尝试使用这些资源之前不会崩溃。

:是一个 npm 包,它的作用是在不同平台上设置环境变量。在不同操作系统中,设置环境变量的方式是不同的。例如,在 windows 中使用命令 set node_env=production 设置环境变量,而在 unix/linux/mac 上则需要使用 export node_env=production 命令。

此时可能会进入到漫长的等待阶段(第一、这个包本身就比较大,第二、相信大家都懂由于网络原因导致),并且有可能进行会经历几次timout安装失败。此时就需要心平气和,且有耐心的进行改变镜像地址、科学进行上网,wifi切换为移动流量多去重试几次,相信道友你总会成功过的。

有如下输出则应该为安装成功:

在项目增加 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的进程知识,可以详读《》一文的“”一节。

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.load;

    mainwindow.webcontents.opendevtools();

  } else {

    mainwindow.load}`);

  }

}

 

// 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.1启动运行原 vue 项目

这里启动项目至端口号 9001,跟上面 electron/main.jsmainwindow.load是可以对应上的,也就是 electron 运行起来将会加载此服务地址。

yarn run dev

6.2新开终端,启动electron

新开一个终端执行,输入下方命令启动 electron。

执行下面命令:

yarn run electron:dev

 

并且经过测试验证登录没有什么问题。

7.1安装electron-builder

该工具为 electron 打包工具库,点击打开。

终端执行下面命令安装 electron-builder:

yarn add electron-builder --dev

7.2配置打包脚本命令及个性化配置项

package.json 配置打包脚本命令以及设置打包个性化配置项。

具体配置项作用请参考凯发k8网页登录官网文档,下面有些配置也是 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概述

打包后页面空白并出现类似“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 会假设你的应用是被部署在一个域名的根路径上,例如 。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。

例如:如果你的应用被部署在 ,则设置 publicpath为 /my-app/。这个值也可以被设置为空字符串 ('') 或是相对路径 ('./'),这样所有的资源都会被链接为相对路

8.3解决手段2

经过一顿操作之后发现仍然还是空白,并且打开控制台看到页面可以正常加载资源文件,但是 index.html 返回此类错误:“we're sorry but xxx doesn't work properly without javascript”,经过查找发现可以通过修改路由模式来解决,经过测试确实有效。

参考文章为:《》。

修改后的代码示例:

const router = createrouter({

  //改为#则可以直接变更路由模式

  history: createwebhistory('#'),

  routes,

});

问题简述:页面展示正常后,调用登录发现出现如下图所示的报错。

 

解决方式:经发现原来是发起 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;

[1] 

[2] 

[3] 

[4] 

[5] 

[6] 

[7] 

[8] 

[9] 



作者: (点击作者姓名进入github)
出处:
交流:欢迎加入即时通讯开发交流群
讨论:
jack jiang同时是和的作者,可前往下载交流。
本博文 欢迎转载,转载请注明出处(也可前往 找到我)。


只有注册用户后才能发表评论。


网站导航:
              
 
jack jiang的 mail: jb2011@163.com, 联系qq: 413980957, 微信: hellojackjiang
网站地图