foreword
It has always been heard that it is electron
very convenient to quickly package web applications into desktop applications, and use the API provided by electron to call some advanced functions of the native desktop API. So this time, by demonstrating whether the Huanxin Web SDK can be used normally and stably on the desktop generated by electron, I decided to package the official new webim-vue3-demo to the desktop, and record the verification process, problems encountered and solutions .
Prerequisite skills
- Possess good emotional self-management, and can not punch the keyboard when encountering difficult problems.
- Possess a relatively proficient water group ability, and when encountering problems, he can actively throw out his own problems to the uneven group friends in the technical group.
- [Important] Possess a relatively proficient ability to use search engines.
- Being able to read this article means that you have fully possessed the above abilities.
Test process record
The first step, preparation work
- Clone the vue3 Demo project to the local Huanxin vue3-demo source code address
- Open this project in an editor and execute
yarn install
Install project-related npm dependencies. - Open the terminal in this project directory
yarn add electron
and please type to install electron in this project. - Install some dependent tools
wait-on
andcross-env
wait-on
is a Node.js package that can be used to wait for a number of specified resources (such as HTTP resources, TCP ports, or files) to become available. It is usually used to wait for the application's dependencies to be ready before starting the application. For example, you can usewait-on
to wait for database connections, message queues, and other services to be ready before starting your application. This ensures that your application does not crash before attempting to use these resources.
cross-env
is anpm
package whose role is to set environment variables on different platforms. In different operating systems, the way to set environment variables is different. For example, setting environment variables in Windows uses the commandset NODE_ENV=production
while on Unix/Linux/Mac you need to useexport NODE_ENV=production
the command.
At this time, it may enter a long waiting period. First, the package itself is relatively large. Second, I believe everyone understands that it is caused by network reasons, and it is possible to experience several installation failures
TIMOUT
. At this time, you need to be calm and patient, and改变镜像地址
try科学进行上网
againWIFI切换为移动流量
a few times. I believe that you will always succeed.
[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-2xyqXdwF-1685947556715)(https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/69f46811946344499047553af9abccd9~tplv-k3u 1fbpfcp-watermark.image?)] If the following output is displayed, the installation should be successful
.
The second step is to add electron files to the project directory
When adding electron files to the project, we need to expand part of our knowledge to understand why this directory was created and the role of adding files in this directory
main.js
. Of course, you can skip it if you don’t think it’s necessary.
The concept of main process and rendering process
In Electron, the main process and the rendering process are two different concepts. The main process is the heart of an Electron application. It runs in a Node.js instance and manages the application's lifecycle, window creation and destruction, interaction with the underlying operating system, and more. The main process can also communicate with the rendering process through the IPC (Inter-Process Communication) mechanism.
The rendering process is the process where the UI interface of the application resides. Each Electron window has its own renderer process. A renderer process is an instance of the Chromium rendering engine running in a Web API-only environment. The rendering process is responsible for rendering HTML, CSS and JavaScript, processing input events from users, and communicating with the main process through the IPC mechanism.
Since the rendering process can only access the Web API and cannot directly access the Node.js API, if you need to use the Node.js API in the rendering process, you need to send a request to the main process through the IPC mechanism, and the main process will execute it and return the result to the rendering process.
Where should the main process and the rendering process be written?
In an Electron application, the main process is usually written in a JavaScript file named
main.js
orindex.js
, which is the application's entry point. The rendering process is usually written in the HTML file and the JavaScript file it imports. In an Electron window, you can call thewebContents
object'sloadURL
method to load an HTML file, which contains the code and resources needed for the rendering process. The JavaScript code in the HTML file will run in the corresponding rendering process, and some APIs and Web APIs provided by Electron can be used to perform operations related to the user interface. It should be noted that in Electron, since the main process and the rendering process are different Node.js instances, they cannot directly share variables or call functions
. If you want to achieve communication between the main process and the rendering process, you must use the IPC mechanism provided by Electron to communicate between processes by sending messages.
The role of preload.js in some electron file directories
In Electron,
preload.js
a file is an optional JavaScript file used to load some additional scripts or modules before the render process is created, thereby extending the capabilities of the render process. The preload.js file is usually stored in the same directory as the main process code.
The actual application of preload.js mainly has the following aspects:
- Managed Node.js API: Node.js modules can be introduced into preload.js and exposed to the window object, so that the Node.js API can also be used in the rendering process, avoiding the security risks brought by calling the Node.js API directly in the rendering process.
- Extended Web API: Some custom functions or objects can also be defined in preload.js, and then injected into the window object, so that they can be used directly in the rendering process without additional import operations.
- Perform some initialization operations: the code in the preload.js file will run once in the context of each rendering process, and some initialization operations can be performed here, such as adding some necessary DOM elements to the page, registering event handlers for the page, and so on.
It should be noted that the code in the preload.js file runs in the context of the rendering process, so if preload.js contains some malicious code, it is likely to compromise the security of the entire rendering process. Therefore, when writing the preload.js file, be very careful and only include those modules and objects that you trust.
1. Add electron file
- At this point the project directory
2. The new main.js
sample code under electron is as follows:
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. Create a new one under electron preload.js
, the sample code is as follows:
This file is optional
//允许vue项目使用 ipcRenderer 接口, 演示项目中没有使用此功能
const {
contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('ipcRender', ipcRenderer);
4. Modify package.json
, the current sample code is as follows:
- Modify
"main"配置
, pointing it to"main": "electron/main.js"
- Add a startup for electron
"scripts"
,"electron:dev": "wait-on tcp:3000 && cross-env NODE_ENV=development electron ./"
The current project configuration looks like this
{
"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"
}
}
The third step, start up locally to verify
- Start and run the original vue project
mainWindow.loadURL(' http://localhost:9001/')
Start the project here to port number 9001, which can correspond to the above electron/main.js , that is, electron will load this service address when it runs.
yarn run dev
- Open a new terminal to execute, enter the following command to start electron
Execute the following command
yarn run electron:dev
You can see that an electron page is automatically opened
And it has been tested and verified that there is no problem logging in.
Step 4: Try to package and verify whether the packaged installation package is available.
1. Installationelectron-builder
This tool is an electron packaging tool library
electron-builder official document
Terminal executes the following command to install electron-builder
yarn add electron-builder --dev
2. package.json configures packaging script commands and sets packaging personalized configuration items
The reference configuration is as follows
For specific configuration items, please refer to the official website documentation. Some of the following configurations are also sent by CV, and there is no specific in-depth study.
{
"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": {
}
}
}
3. Start building
- Firstly
build original vue project
yarn run build
- again like that
build electron project
yarn run electron:build
There may be a long wait, but don't panic, it may have a lot to do with the Internet, so you need to wait patiently.
After the packaging is successful, you can see that there is an output folder generated. After opening, you can choose to double-click to open the software to verify whether the application can be opened normally.
If you open the page normally, it proves that there is no problem. If you encounter problems, there will be some problems I encountered below, which can be used as a reference.
Summary of Distressing Problems
Question 1. After packaging, the page is blank, and a similar error (Failed to load resource: net::ERR_FILE_NOT_FOUND) appears
Brief description of the problem: It is found that only the electron application after packaging has a blank page after startup, and it is normal in the case of dev.
One of the solutions:
After investigation, the configuration vue.config.js
in the change publicPath
is './'
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',
});
},
});
The reason is that the packaged application electron will start looking for resources from relative paths, so after this configuration, all resources can start looking for resources from relative paths.
默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上,例如 `https://www.my-app.com/`。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 `https://www.my-app.com/my-app/`,则设置 `publicPath` 为 `/my-app/`。
这个值也可以被设置为空字符串 (`''`) 或是相对路径 (`'./'`),这样所有的资源都会被链接为相对路径,这样打出来的包可以被部署在任意路径,也可以用在类似 Cordova hybrid 应用的文件系统中。
The second solution:
After a while of operation, I found that it was still blank, and I opened the console to see that the page can load resource files normally, but index.html returns this type of error: , after searching, I found that
We're sorry but XXX doesn't work properly without JavaScript
it can be solved by modifying the routing mode, and it is indeed effective after testing.
The reference article is: https://www.imgeek.net/article/825363952
Modified code example:
const router = createRouter({
//改为#则可以直接变更路由模式
history: createWebHistory('#'),
routes,
});
Question two,
Brief description of the problem: After the page is displayed normally, the following error is reported when the login is called
Solution: It was found that when axios was initiated to request the ring letter to replace the connection token interface, the protocol was obtained through the protocol. Then the protocol after packaging is then the request initiated at this time will be
window.location.protocol
changedfile:
to a request initiated by the file protocol. Then modify the logic here. If it is determined that the file protocol is used, the http protocol will be used to initiate the request by default. The sample code is as follows:
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;
References
Special thanks to the two fellow Taoists whose articles are very useful and can be used as a reference: