Life is a process of solving problems

preface

I haven’t written an article for a long time. I have been very busy during this period of time, and I have finally finished it. Here is a copy to record the problems encountered in the process of exploration.

Background: Project requirements say: I want a desktop client application, preferably cross-platform.

Technology selection: Electorn will be the first to use Web technology to build applications, open source, cross-platform excellent features. Do I have a choice? No, I didn’t.

Knowledge base: HTML, CSS, JS, VUE

start

Open the official website of Electorn and see:

Our most common development tool, vscode, was developed with Electorn.

Then scroll down to see the properties:

It looks like a cow.

From the homepage of the official website, we know what we should know. Next, we will open the quick start page of the document and follow the tutorial step by step to build our first demo.

We need to focus on the flow model section and understand that in each Electron application there is a single main process running in the Node.js environment that manages multiple renderers (if you are creating multiple Windows), helper processes, etc. Main process and rendering process through the IPC pipeline communication, several specific writing methods, Baidu a lot. A preloaded script contains the code that is executed in the renderer process before the web page content is loaded. The usual way to do this is to pass data to the renderer process through the preloaded script, which is mounted under the Window object.

Well, that’s all you need to know; it’s just a matter of checking the API over and over again during specific development.

This time, we developed two applications for sharing Electorn. The first application is relatively simple. Let’s talk about the first one first.

The first application

Considering the simplicity of the application, there are only three pages. Therefore, I don’t need vUE single file components, Webpack packaging, routing vue-Router, Axios, etc. Even if there is a ready-made project like Electorn Vue that can be more convenient for secondary development, I won’t use it. I don’t think such a small project should add complexity with engineering, that’s what I think. It feels good to write about native development once in a while.

I’ve replaced vue-Router with show and hide. Vue-router’s internal implementation is nothing more than loading corresponding components based on urls. Since the rendering process of Electorn ran in Chromium, I directly used fetch to initiate the request part. You’re going to use the ElementUI framework for the UI part, right? Don’t be lazy. Write by hand.

In this first application, the first pitfall discovered was that the send method for ipcRenderer objects in the renderer process did not exist, causing the renderer process to fail to communicate with the main process. The solution given is to pass data in a preloaded script:

// preload.js
const { contextBridge, ipcRenderer } = require('electron')

// Pass parameters to the renderer process
contextBridge.exposeInMainWorld('ipc', {
  "ipcRenderer": {
    send: (channel, data) = > {
      ipcRenderer.send(channel, data)
    },
    on: (channel, callback) = > {
      ipcRenderer.on(channel, callback)
    }
  }
})
Copy the code

The second pit is about packaging. In the official documentation – Quick Start – Packaging and Distributing your application section, the Electron Forge packaging kit doesn’t work very well and it turns out the Electorn builder smells good.

This is the first application, there is not too much stuff, the main point, the second application, the difficulty of all of a sudden.

Second application

The second application, according to the requirements, will integrate many things and have complex functions, so I considered convenient scaffolding like Electorn vue at the very beginning. However, the technology is developing, and I found that the existing Electorn Vue scaffold only has the vue2. X version. I want to do this project with VUE3 + TS + Vite to promote technological innovation.

I took the vue3.x project template that I built according to the article that I helped you build a standard vue3.x project engineering environment from 0, and integrated electorn into it. Note that this is only a Web project, only the Electorn dependency package was added, about the local boot mode, packaging, etc., need to be further modified.

The VuE3. X project was transformed into electorn engineering project

This part is the most complex, involving the packaging configuration, I am still learning NodeJS, so I do not have a very deep understanding of packaging, explore, I will learn this part of packaging, engineering is very important.

Fortunately, the project template electron- vue-Vite is already available on Github, so I’ll refer to the author for the packaging part. Of course, I did a lot of thinking before I discovered this warehouse, and here are some of my thoughts, or what you can think of as an interpretation of the project electron vue-Vite.

Think about: The Electorn project requires an entry file such as main.js running in the main process to control the application, and at least one entry file such as index.html running in the render process to render the application interface. Finally, a preloaded file such as preload.js can be provided as needed to pass some data before the renderer process loads.

Now the files generated in the dist folder after our vue3.x project is packaged are the files that run in the render process. We still lack main.js and preload.js. Now to modify the project, first modify the SRC directory, we modify the project directory to the following three parts:

Move the SRC directory to the Render directory. The Render directory looks something like this:

Modify the vit.config.ts configuration to change the vite package root directory to SRC /render.

The main and preload folders can also be rollup separately, considering that there may be many smaller file modules below. Rollup is a better way to package JS libraries than WebPack, so rollup is a better way to package them.

Now create a new directory script to write the rollup package script. The directory might look like this:

We then go to package.json to configure the relevant packaging command, which looks something like this:

"build:render": "vite build"."build:preload": "node -r ts-node/register script/build-preload --env=production"."build:main": "node -r ts-node/register script/build-main --env=production"."build": "rimraf dist && npm run build:render && npm run build:preload && npm run build:main"
Copy the code

For NPM Scripts, read the NPM Scripts Usage Guide.

Now we run the NPM run build command and the packed directory looks like this:

You’ve got the three parts you need to start thinking.

After the final integration of The Electorn builder package, I also added the following command for easy packaging:

"win32": "npm run build && electron-builder --win --ia32"."win64": "npm run build && electron-builder --win --x64"."mac": "npm run build && electron-builder --mac"."linux": "npm run build && electron-builder --linux"
Copy the code

Note that the && symbol indicates subsequent execution.

As far as we’re concerned, we’ve built the packaging stuff, but that’s just what we need in production. How can we get The Electorn application to start and heat reload easily in a local development environment?

Consider: you can start the Web project first, then package the mian and preload folders and execute them, so the NPM script command in the development environment will be configured as follows:

"dev": "concurrently -n=R,P,M -c=green,yellow,blue \"npm run dev:render\" \"npm run dev:preload\" \"npm run dev:main\""."dev:render": "vite"."dev:preload": "node -r ts-node/register script/build-preload --env=development --watch"."dev:main": "node -r ts-node/register script/build-main --env=development --watch"
Copy the code

Dev :render dev:preload dev:main dev:render dev:preload So in the package part of the script (build-main.ts) there is a waitOn function that polls for vite startup status. The purpose is to wait until vite has started, that is, after the local Web server is up, to rollup files in the main folder. Finally, execute the packaged main.js with child_process.spawn(), and you can start the electorn + vue3.x + vite + ts application locally.

The project has been well set up and can better complete the packaging of local development and production environment. So I started to develop the project, and here is a record of the problems I encountered during the development, and maybe you will also encounter this aspect of the problem.

The problem record

1. Use RabbitMQ

JavaScript Get Started on RabbitMQ’s website. Producers, consumers, switches, routing, RPCS and so on are all in the documentation, so it’s good to go.

Following the official tutorial, I have been unsuccessful in setting up RabbitMQ connections.

First question, my RabbitMQ server is authenticated with an SSL certificate, add the certificate to amqplib official website -SSL

The second problem is that even if I have configured the SSL certificate, there will be problems with the connection and I need to add it

checkServerIdentity: () = > {
  return null
}
Copy the code

Complete connection code, in the form of Promise, with additional receive and send message examples:

const amqp = require('amqplib')
const constains = require('constants')

const url = {
  protocol: 'amqps'.hostname: 'xxx.xxx.xxx.xxx'.port: 'xxxx'.username: 'xxxxxxx'.password: 'xxxxxxxxxxxxxxxx'
}

const opts = {
  cert: fs.readFileSync('clientcert.pem'), 
  key: fs.readFileSync('clientkey.pem'), 
  passphrase: 'MySecretPassword'.ca: [fs.readFileSync('cacert.pem')].secureOptions: constains.SSL_OP_NO_TLSv1_1, // SSL protocol version
  checkServerIdentity: () = > {
    return null}}function reconnect() {
  console.log('Connection to server has been disconnected')
  return connectRabbitMq()
}

// Connect RabbitMQ examples
function connectRabbitMq() {
  return amqp
  .connect(url, opts)
  .then(connect= > {
    connect.on('error', reconnect) // Error reconnection
    console.log('Connection to server ok')
    return connect.createChannel()
  })
  .then(async (channel) => {
    await channel.assertExchange('exchange'.'topic', {
      durable: true.// Message persistence
      autoDelete: false
    })
    return channel
  })
  .catch(reconnect)
}

// Receive a message example
function receiveMessage() {
  connectRabbitMq()
  .then(async (channel) => {
       // Declare a queue
      await channel.assertQueue('receiveQueue', {
        durable: true.autoDelete: true
      })
       Do not send a new message to a worker until the previous message has been processed and confirmed
      await channel.prefetch(1)
       // bind the queue
      await channel.bindQueue('receiveQueue'.'exchange'.'receiveRoutingKey')
       // Consume messages
      channel.consume(
        'receiveQueue'.async (msg) => {
          console.log('======= receive =======')
          console.log(msg.content.toString())
          await dealMsg(msg) // Process the message
          channel.ack(msg) / / reply
        },
        {
          noAck: false
        }
      )
    })
    .catch(console.warn)
}

// Send a message example
function sendMessage(msg) {
  connectMQ()
    .then((channel) = > {
      channel.publish('exchange'.'sendRoutingKey', Buffer.from(JSON.stringify(msg)))
    })
    .catch(console.warn)
}
Copy the code

2. NodeJS downloads the file and displays the download progress

I first used Axios to download the file, and found that the download progress of AXIos can only be obtained in the browser environment, but not in the Node environment. You can see this on Request Config:

Axios stopped thinking about it and I ended up using two more packages, as shown in the following example:

const fs = require('fs')
const fetch = require('node-fetch')
const progressStream = require('progress-stream')

/ * * *@param  {*}* fileURL: string File download address * fileSavePath: string file save address * callback Optional, default empty function, can be used for some operations during file download */
function downLoadFile(
  fileURL,
  fileSavePath,
  callback = function () {}
) {
  const fileStream = fs
    .createWriteStream(fileSavePath)
    .on('error'.() = > {
      console.log('Downloading error')
    })

  fetch(fileURL, {
    method: 'GET'.headers: { 'Content-Type': 'application/octet-stream' }
  })
    .then(res= > {
      let fsize = res.headers.get('content-length')
      // Create progress
      let str = progressStream({
        length: fsize,
        time: 100 /* ms */
      })
      // Download progress
      str.on('progress'.function (progressData) {
        let progress = Math.round(progressData.percentage)

        callback(process) // We can do further processing after we get the progress
      })
      // Save the file
      res.body.pipe(str).pipe(fileStream)
    })
    .catch(console.warn)
}
Copy the code

For more information about breakpoint continuations, see NodeJS for examples of downloading files using Node-fetch and showing download progress

3. Package the Electorn builder and change the default installation path of the program

This functionality needs to be implemented by writing an NSIS script. First modify the NSIS configuration item in package.json and add the include option as follows:

"nsis": {
  "oneClick": true."allowElevation": true."installerIcon": "dist/favicon.ico"."uninstallerIcon": "dist/favicon.ico"."installerHeaderIcon": "dist/favicon.ico"."createDesktopShortcut": false."createStartMenuShortcut": true."shortcutName": "rabbit"."deleteAppDataOnUninstall": true."include": "./installer.nsh"
}
Copy the code

For NSH scripts, you can go to the Electorn builder website here.

The installer. NSH script is as follows:

! macro preInit SetRegView64
    WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\Program Files (x86)\rabbit"
    WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\Program Files (x86)\rabbit"
  SetRegView 32
    WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\Program Files (x86)\rabbit"
    WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\Program Files (x86)\rabbit"! macroendCopy the code

4. Copy files during package rollup

There is an icon file in the root of my project, favicon.ico, which I need to move to the dist directory when ROLLup is packed so that it matches the address configuration of the icon item in my NSIS configuration item above.

Not stupid enough to use the fs built-in module, I first went to see how webpack works in the Vue-CLI project and found the copy-webpack-plugin package. Rollup-plugin-copy: rollup-plugin-copy: rollup-plugin-copy: rollup-plugin-copy: rollup-plugin-copy

import copy from 'rollup-plugin-copy'

const RollupOptions = {
    plugins: [
      copy({
        // Copy favicon.ico to the specified directory
        targets: [{src: 'favicon.ico'.dest: 'dist'}]}]}Copy the code

You can see more examples on the NPM website: rollup-plugin-copy

5. Register the Windows service

First of all, we conclude that it is not advisable to register the Electorn application as a Windows service.

Two reasons:

  1. throughelectorn-builderPackaged upexeExecutable programs that simply do not meet the specifications of the service.

Before I knew this, I tried two things.

The first is to use the NSIS Simple Service Plugin. After I wrote the NSH script according to the document, I packaged the program, and then ran the program. I found that the Service was registered, but it could not be started.

SC [Servername] command Servicename [Optionname= Optionvalues] SC [Servername] command Servicename [Optionname= Optionvalues]

Node-windows is not a NodeJS script, but a NodeJS script. My application is packaged as an exe executable, so it depends on the situation.

  1. Windows serviceOperating system core mentality, that isWindows serviceWorks in the operating system kernel. Programs that work in the operating system kernel do not have graphical interfaces.

The most telling case was when I registered my application as a Windows service using the NSSM tool (NSSM is a tool for registering Nodejs projects as Windows services) and looked at the process and found that the application was running, but the tray icon of the application didn’t show up anyway. Registering the Electorn app for Windows is not an option at all.

My final solution is to split the project and package the parts that need to be registered as Windows services separately as NodeJS projects. Here I use the PKG package for packaging. It is important to note that if you do not configure Babel in NodeJS projects, please do not use ESModule, otherwise the packaging will fail. The other part can be presented as a graphical interface. If you need to communicate between the two parts, you can use Websocket. Ws is a good package.

The last

The project is still in continuous iteration, I will record more problems in the future here, welcome everyone to discuss.

reference

Electorn website

Build a standard Vue3. X project engineering environment hand in hand from 0

electron-vue-vite

NPM Scripts Usage Guide

RabbitMQ 官网 JavaScript Get Started

Amqplib website – SSL

Axios website Request Config

NodeJS uses Node-fetch to download files and shows examples of download progress

Electorn – builder’s official website

rollup-plugin-copy

NSIS Simple Service Plugin

nssm

pkg

ws