How to use Electron to quickly develop a desktop application

foreword

It has always been heard that it is electronvery 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 installInstall project-related npm dependencies.
  • Open the terminal in this project directory yarn add electronand please type to install electron in this project.
  • Install some dependent tools wait-onandcross-env

wait-onis 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 use wait-onto 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-envis a npmpackage 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 command set NODE_ENV=productionwhile on Unix/Linux/Mac you need to use export NODE_ENV=productionthe 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 科学进行上网again WIFI切换为移动流量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
.
insert image description here

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.jsor index.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 the webContentsobject's loadURLmethod 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.jsa 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:

  1. 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.
  2. 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.
  3. 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
    insert image description here

2. The new main.jssample 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

  1. 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
  1. 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

insert image description here
insert image description here

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.

insert image description here

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.
insert image description here

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.jsin the change publicPathis './'

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 JavaScriptit 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
insert image description here

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.protocolchanged file: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:

Guess you like

Origin blog.csdn.net/huan132456765/article/details/131047964