Best Practices for Electron Application Development

Best Practices for Electron Application Development

1. Background

In the team, we need to use desktop technology due to business development, such as offline availability and calling desktop system capabilities. What is desktop development? In one sentence, it is: software development based on Windows, macOS and Linux operating systems. We have conducted a detailed technical survey on this. The main desktop development methods are Native, QT, Flutter, NW, Electron, and Tarui. Their respective advantages and disadvantages are shown in the following table:

Our final desktop technology choice is Electron, which is a development framework that can use Web technology to develop cross-platform desktop applications.

Its technical components are as follows:

Electron = Chromium + Node.js + Native API

The technical capabilities are shown in the figure below:

The overall architecture is shown in the figure below:

Electron is a multi-process architecture with the following features:

  • Consists of a main process and N rendering processes
  • The main process plays a leading role and is used to complete various cross-platform and native interactions.
  • There can be multiple rendering processes, developed using Web technology, rendering pages through the browser kernel
  • The main process and the rendering process complete various functions through inter-process communication

Here is the principle of Electron inter-process communication technology:

Electron uses IPC (interprocess communication) to communicate between processes, as shown below:

It provides IPC communication modules, ipcMain for the main process and ipcRenderer for the rendering process.

From the electron source code, we can see that ipcMain and ipcRenderer are both EventEmitter objects. The source code is shown in the figure below:

After seeing the source code implementation, do you think IPC is not difficult to understand? Only by knowing its essence can you handle it with ease.

Seeing this, we look back at the technical table above and see that the Electron application package is large in size. What is the root cause of the large size?

In fact, this is related to the framework design of Chromium. It has no macro control for many functions, which makes it difficult to remove large and complex detailed functions. It also causes the packages based on Chromium, such as electron and nwjs, to start at more than 100M.

In summary, electron has the advantages of being cross-terminal, web-based, and having a strong ecosystem, and is one of the excellent solutions for desktop development. The following article will introduce the practical experience of electron application development, including application technology selection and common functions.

2. Application Technology Selection

2.1 Programming language Typescript

Here are the reasons:

  • For developers
  1. Superset of Javascript - seamlessly supports all es2020+ features with low learning cost
  2. The compiled JavaScript code remains very readable
  3. Maintainability is significantly improved
  4. Complete OOP support - extends, interface, private, protect, public, etc.
  5. Types as Documents
  6. Type constraints, less unit test coverage
  7. Safer code
  • Targeting tools
  1. Better refactoring capabilities
  2. Static analysis automatic package guide
  3. Code error checking
  4. Code Jump
  5. Code prompt completion
  • Community

A large number of community type definition files improve development efficiency

2.2 Build Tool Electron-Forge

Reason: Simple and powerful, one of the best building tools for electron applications.

Here is the introduction and difference between electron-builder and electron-forge, as shown in the figure below:

The biggest difference between the two is the degree of freedom. There is basically no difference in capabilities between the two. Judging from the ranking in the official organization, electron-forge is intentionally recommended first.

2.3 Web Solution Vue3 + Vite

We use Vue3 and Vite as the construction tool. For specific advantages, you can check the official website introduction. This combination is the current mainstream Web development solution.

2.4 monorepo solution pnpm + turbo

The current monorepo ecosystem is flourishing, and the correct practice method should be the integration method, that is, taking the strengths of each. This is also the current trend. Various open source monorepo tools have reached a tacit understanding and focused on their own strengths.

For example, pnpm is good at dependency management, and turbo is good at building task orchestration. So in the selection of monorepo technology, I chose pnpm and turbo.

pnpm reasons are as follows:

  • As the best package management tool currently, pnpm absorbs the essence of mainstream tools such as npm, yarn, and lerna, and removes their dross.
  • The ecosystem and community are active and strong
  • Combining workspaces can achieve monorepo best practices
  • Excellent performance in managing package dependencies, code style, code quality, component library reuse, etc. for multiple projects
  • Excellent performance in framework and library development, debugging, and maintenance

Compared to the vue official website, when using pnpm, I added workspace.

Turbo reasons are as follows:

  • It is a high-performance build system with features such as incremental builds, cloud cache, parallel execution, zero runtime overhead, task pipelines, streamlined subsets, etc.
  • It has excellent task scheduling capabilities, which can make up for the shortcomings of pnpm in task scheduling.

2.5 Database lowdb

There are many choices for electron application databases, such as lowdb, sqlite3, electron-store, pouchdb, dedb, rxdb, dexie, ImmortalDB, etc. These databases have one feature, that is, serverless.

The main considerations for electron application database technology selection are as follows:

  • Ecosystem (number of users, maintenance frequency, version stability)
  • ability
  • performance
  • Others (matching with user technology)

We conducted relevant research through the following channels

  • github issues, commit, fork, star
  • Sourcegraph keyword search results
  • npm package downloads and version releases
  • Official website and blog

Four optimal choices are given, namely lowdb, sqlite3, nedb, and electron-store, for the following reasons:

  • Lowdb: Excellent in ecology, capability, and performance. It has a JSON storage structure and supports API operations such as lodash and ramda, which is convenient for backup and calling.
  • sqlite3: Excellent in ecology, capabilities, and performance, the first choice for Nodejs relational database
  • nedb: It has excellent capabilities and performance. Its disadvantage is that it is basically not maintained, but the foundation is still there. In particular, its operations are a subset of MongoDB. It is an excellent choice for users who are familiar with MongoDB.
  • electron-store: excellent ecological performance, lightweight persistence solution, simple and easy to use

The database we use is the lowdb solution.

PS: Let me mention pouchdb. If you need to synchronize local data to a remote database, you can use pouchdb, which can easily complete synchronization with couchdb.

2.6 Script tool zx

During the software development process, completing some processes and operations through scripts can effectively improve development efficiency and happiness.

There are two excellent choices for node runtime: shelljs and zx. The reasons for choosing zx are as follows:

  1. Comes with common libraries such as fetch and chalk, which is convenient and fast to use
  2. Multiple subprocesses are convenient and fast, execute remote scripts, parse md, xml file scripts, support ts, rich and powerful functions
  3. Produced by Google, with a large company background and a very active ecosystem

So far, the technical selection has been introduced. Next, I will introduce the common functions of electron applications.

3. Construction

This section mainly introduces the following 5 points:

  • Application Icon Generation
  • Binary build
  • Build on demand
  • Performance Optimization
  • Cross-platform compatibility

3.1 Application Icon Generation

There are the following methods to generate icons of different sizes:

Windows

  • Software generation: icofx3

Web page generation:

https://tool.520101.com/diannao/ico/(opens new window)

MacOS

  • Software generation: icofx3

Web page generation:

https://tool.520101.com/diannao/ico/(opens new window)

  • Command line build: build with sips and iconutil

3.2 Binary file building

The content of this chapter is based on electron-forge, but the principle is the same.

When developing desktop applications, there are scenarios where you need to use third-party binary programs, such as ffmpeg. When building binary programs, pay attention to the following two points:

(1) Binary programs cannot be packaged into asar. You can set the following in the build configuration file (forge.config.js):

 const os = require ( 'os' )
const platform = os . platform ()
const config = {
packagerConfig : {

// You can package the ffmpeg directory outside the asar directory

extraResource : [ `./src/main/ffmpeg/` ]

}

}

(2) The methods for obtaining binary program paths are different in development and production environments. You can use the following code to dynamically obtain the path:

 import { app } from 'electron'
import os from 'os'
import path from 'path'
const platform = os . platform ()

const dir = app . getAppPath ()

let basePath = ''

if ( app . isPackaged ) basePath = path . join ( process . resourcesPath )

else basePath = path . join ( dir , 'ffmpeg' )

const isWin = platform === 'win32'

// ffmpeg binary program path

const ffmpegPath = path . join ( basePath , `${ platform } ` , `ffmpeg${ isWin ? '.exe' : '' } ` )

3.3 Build on Demand

How to build cross-platform binaries on demand?

For example, ffmpeg is used in desktop applications, which requires downloadable binaries for Windows, Mac, and Linux. When packaging, if on-demand building is not done, all three binary files will be included in the build, which will increase the size of the application a lot.

You can configure the forge.config.js configuration file as follows to complete the on-demand build. The code is as follows:

 const platform = os . platform ()
const config = {
packagerConfig : {
extraResource : [ `./src/main/ffmpeg/${ platform } ` ]

},

}

By using the platform variable to add the binary of the corresponding system to the build, you can complete the on-demand build of the binary file.

3.4 Performance Optimization

The main thing is to optimize the build speed and build volume. The build speed is not easy to optimize. This article focuses on the build volume optimization. Here we take the Mac system as an example. After the electron application is packaged, check the application package content, as shown in the following figure:

You can see that there is an app.asar file. After decompressing this file with asar, you can see the following content:

It can be seen that the files in asar are the project codes after we build. From the picture, we can see that there is a node_modules directory. This is because in the electron build mechanism, all dependencies of dependencies will be automatically packaged into asar.

Therefore, based on the above analysis, our optimization measures are as follows:

  1. Put all the dependencies required for web-side building into devDependencies, and only put the dependencies required for electron-side into dependencies
  2. Remove non-production-related code and files from the build
  3. Build cross-platform binary files, such as ffmpeg, on demand (build on demand has been introduced above)
  4. Clean and streamline node_modules

Here is the fourth point: how to clean and streamline node_modules?

If it is a dependency installed by yarn, we can use the following command in the root directory to streamline it:

 yarn autoclean - I
yarn autoclean - F

If the dependency is installed by pnpm, point 4 should not work. I used yarn to install the dependency in my project, and then after executing the above command, I found that the package size was reduced by 6M, which is not much, but it is still okay. ​

This concludes the introduction to the build function.

4. Update

This chapter is mainly divided into the following two aspects:

  • Full update
  • Incremental Updates

The following will introduce the above two updates in turn

4.1 Full Update

To update the software, you need to replace all files by downloading the latest package or zip file.

The overall design flow chart is as follows:

To implement it according to the flowchart, we need to do the following:

  1. Develop a server-side interface to return the latest version of the application
  2. The rendering process uses tools such as axios to request interfaces and obtain the latest version information
  3. Encapsulates update logic to perform a comprehensive comparison of the version information returned by the interface to determine whether to update
  4. Pass update information to the main process via ipc communication
  5. The main process is fully updated through electron-updater
  6. Push update information to the rendering process via ipc
  7. The rendering process displays update information to the user. If the update is successful, a pop-up window will pop up to tell the user to restart the app to complete the software update.

4.2 Incremental Update

The software update is completed by pulling the latest rendering layer package file and overwriting the previous rendering layer code. This solution only needs to replace the rendering layer code, without replacing all files.

To implement it according to the flowchart, we need to do the following things

  1. The rendering process periodically notifies the main process to detect updates
  2. Main process detection update
  3. If an update is needed, pull the latest package online
  4. Delete the old version package, copy the latest online package, and complete the incremental update
  5. Notify the rendering process and prompt the user to restart the app to complete the update

Full updates and incremental updates each have their own advantages. In most cases, incremental updates are used to improve the user update experience, while full updates are used as a backup update solution.

This concludes the introduction to the update function.

5. Performance Optimization

It is divided into the following three aspects:

  1. Build optimization
  2. Startup Optimization
  3. Runtime optimization

Build optimization has been introduced in detail above, so it will not be introduced here. The following will introduce startup optimization and runtime optimization.

5.1 Startup Optimization

  • Cache compiled code using v8-compile-cache
  • Prioritize loading core functions, and dynamically load non-core functions
  • Use multi-process and multi-threading technology
  • Use asar packaging: will speed up the startup
  • Add visual transition: loading + skeleton screen

5.1.1 Use v8-compile-cache to cache compiled code

Use V8 to cache data. Why do this?

Because electorn uses the V8 engine to run js, when V8 runs js, it needs to parse and compile before executing the code. The parsing and compiling process consumes a lot of time and often leads to performance bottlenecks. The V8 cache function can cache the compiled bytecode, saving the time of the next parsing and compilation.

Mainly use v8-compile-cache to cache compiled code. The method is very simple: add a line where you need to cache

 require ( 'v8-compile-cache' )

For other usage methods, please refer to this link document

https://www.npmjs.com/package/v8-compile-cache(opens new window)

5.1.2 Prioritize core functions and dynamically load non-core functions

The pseudo code is as follows:

 export function share () {
const kun = require ( 'kun' )
kun ()
}

5.2 Runtime Optimization

  • Web performance optimization for the renderer process
  • Lightweighting of the main process

5.2.1 Web performance optimization for rendering process

Use a mind map to fully explain how to optimize Web performance, as shown below:

The above picture basically contains the core key points and contents of performance optimization. You can use it as a reference to perform performance optimization.

5.2.2 Lightweighting the main process

The core solution is to delegate the time-consuming and computationally intensive functions to the newly opened node process for execution.

The pseudo code is as follows:

 const { fork } = require ( 'child_process' )
let { app } = require ( 'electron' )

function createProcess ( socketName ) {
process = fork ( `xxxx/server.js` , [
'--subprocess' ,
app.getVersion () ,
socketName

])
}

const initApp = async () => {
// Other initialization code...
let socket = await findSocket ()
createProcess ( socket )
}

app . on ( 'ready' , initApp )

Through the above code, the time-consuming and computationally intensive functions are placed in server.js, and then forked to the newly opened node process for processing.

This concludes the introduction to performance optimization.

VI. Quality Assurance

The full process of quality assurance measures is shown in the figure below:

This chapter mainly introduces the following three aspects:

  1. Automated testing
  2. Crash monitoring
  3. Collapse Governance

The above contents will be introduced one by one below.

6.1 Automated Testing

What is Automation Testing?

The above picture shows a complete step of automated testing. You can understand it by looking at the picture.

Automated testing is mainly divided into unit testing, integration testing, and end-to-end testing. The relationship between the three is shown in the following figure:

Generally speaking, as software engineers, we only need to do some unit testing. And from my current experience, if you are writing a business project, you basically won’t write test-related code. Automated testing is mainly used to write libraries, frameworks, components, etc. that need to be provided to others as individual units.

The recommended testing tools for electron are vitest and spectron. Please refer to the official website for specific usage. There are no special techniques.

6.2 Crash Monitoring

For GUI software, especially desktop software, the crash rate is very important, so crashes need to be monitored.

The crash monitoring principle is shown in the following figure:

Crash Monitoring Tips

  • After the rendering process crashes, prompt the user to reload
  • Unified initialization of crash monitoring via preload
  • The main process and rendering process simulate crashes through process.crash()
  • Collect and analyze crash logs

After crash monitoring is done, if a crash occurs, how to manage the crash?

6.3 Crash Management

Difficulties in collapse management:

  • Difficulty locating the error stack: Native error stack, no operation context
  • High debugging threshold: C++, IIdb/GDB
  • Complex operating environment: machine model, system, other software

Crash management tips:

  • Update electron in time
  • User operation logs and system information
  • Reproducing and locating problems is more important than governance
  • Leave the problem to the community, the community responds quickly
  • Good at using devtool to analyze and manage memory issues

VII. Safety

As the saying goes, safety is paramount. Ensuring the safety of electron applications is also an important matter. This chapter divides safety into the following five aspects:

  1. Source code leak
  2. asar
  3. Source code protection
  4. Application Security
  5. Coding security

The above contents will be introduced one by one below.

7.1 Source code leakage

Currently, electron does not do well in source code security. The official only uses asar to do some useless source code protection. How useless is it?

You just need to download the asar tool and then decompress the asar file to get the source code inside, as shown below:

You can see the source code of the Yuque application by following the operation shown in the picture. What is the asar mentioned above?

7.2 asar

asar is a tar-like archive format that combines multiple files into one file. Electron can read any file content from it without decompressing the entire file.

ASAR technical principle:

You can directly look at the electron source code, which is all ts code, easy to read, the source code is shown in the figure below:

As can be seen from the figure, the core implementation of asar is to rewrite the fs module of nodejs.

7.3 Source Code Protection

To avoid source code leakage, source code security can be divided into the following levels from low to high.

  1. asar
  2. Code Obfuscation
  3. WebAssembly
  4. Language bindings

Among them, language bindings are the highest source code security measure. In fact, using C++ or Rust code to write electron application code will make it more difficult to decipher after compiling C++ or Rust code into binary code. Here I will talk about how to use Rust to write electron application code.

Solution: Use napi-rs as a tool to write, as shown below:

We use pnpm-workspace to manage Rust code and use napi-rs. For example, we write a sum function. The rs code is as follows:

 fn sum ( a : f64 , b : f64 ) - > f64 {
a + b
}

At this point we add the napi decoration code, as shown below:

 use napi_derive :: napi ;

#[ napi ]
fn sum ( a : f64 , b : f64 ) - > f64 {
a + b
}

Compile the above code into binary code that can be called by node through napi-cli.

After compilation, use the above code in electron as follows:

 import { sum as rsSum } from '@rebebuca/native'
// Output 7
console . log ( rsSum ( 2 , 5 ))

For more information about napi-rs, please read the official documentation at: https://napi.rs/(opens new window)

So far, the description of language bindings is complete. In this way, we can protect the source code of important functions.

7.4 Application Security

A well-known security issue is cloning attack. The mainstream solution to this problem is to bind user authentication information and application device fingerprint. The overall process is shown in the following figure:

  • Application device fingerprint generation: This can be achieved using the napi-rs solution described above
  • User authentication information and device fingerprint binding: Use the server to implement

7.5 Coding Security

The main measures are as follows:

  • Common web security, such as anti-xss, csrf
  • Set up the node executable environment
  • Open security options in the form
  • Limit link redirection

The above specific details will not be introduced here, you can search for the above solutions by yourself. In addition, there is an official recommended best security practice, you can take a look at it when you have time, the address is as follows:

https://www.electronjs.org/docs/latest/tutorial/security(opens new window)

This concludes the introduction to security.

8. Conclusion

This article introduces our research on desktop technology, the technology selection, and the practical experience summarized in the process of developing with electron, such as construction, performance optimization, quality assurance, security, etc. I hope it will be helpful to readers in the process of developing desktop applications. The article is bound to have shortcomings and errors, and readers are welcome to communicate in the comment area.

<<:  How big is the difference between iOS 15.4.1 and iOS 15.6? Is it worth upgrading?

>>:  Android development board serial communication - in-depth analysis and detailed use

Recommend

Dingxiangyuan's ingenious mechanism for free coffee cup growth campaign

DXY is China's leading medical connector and ...

7 directions for optimizing medical bidding plans!

The SEM bidding promotion team concluded that a g...

130 App Slogans, each one is a great insight!

A good slogan, in addition to being closely relat...

Several strategies for good news marketing

In the increasingly competitive Internet, it is c...

Projects die in the cold winter, but businesses survive

[[153510]] Three days ago was my first anniversar...

How to find user growth points? Build a Mini Program User Growth Model

1. If a business is not growing, it is dying I ha...

Google: 10 rules you must know to build a great mobile app

The differences between iOS and Android are the r...