Vivo Internet Front-end Team-Yang Kun
1. Background
In the team, we need to use desktop technology due to business development, such as offline availability and invoking desktop system capabilities. What is desktop development? In one sentence: software development with Windows, macOS and Linux as operating systems . In this regard, we have done a detailed technical research. The development methods of the desktop side mainly include Native, QT, Flutter, NW, Electron, and Tarui. Their respective advantages and disadvantages are shown in the table below:
Our final desktop technology choice is Electron, a development framework that can use web technologies to develop cross-platform desktop applications.
Its technical composition is as follows:
Electron = Chromium + Node.js + Native API
The technical capabilities are shown in the figure below:
The overall structure is shown in the following figure:
Electron is a multi-process architecture with the following characteristics:
- Consists of one main process and N rendering processes
- The main process assumes the leading role and is used to complete various cross-platform and native interactions
- There can be multiple rendering processes, developed using Web technology, and 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 in the following figure:
It provides the IPC communication module, ipcMain of the main process and ipcRenderer of the rendering process.
As can be seen from the electron source code, ipcMain and ipcRenderer are both EventEmitter objects. The source code is shown in the following figure:
Seeing the source code implementation, do you think IPC is not difficult to understand? Knowing its essence, can do it with ease.
Seeing this, we review 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 the huge and complex detailed functions. It also causes the development framework based on chromium, such as electron and nwjs, to start with more than 100 packages. M.
In summary, electron has the advantages of cross-end, web-based, and super ecological, and is one of the excellent solutions for desktop development. The following 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
The reasons are as follows:
- For developers
- Superset of Javascript - seamless support for all es2020+ features, low learning cost
- The code that compiles the generated JavaScript remains very readable
- Maintainability is significantly enhanced
- Full OOP support - extends, interface, private, protect, public, etc.
- type is document
- Type constraints, less unit test coverage
- more secure code
- for tools
- better refactoring capabilities
- Static analysis automatic package import
- code error checking
- code jump
- Completion of code hints
- 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 build tools for electron applications.
Here is the introduction and difference between electron-builder and electron-forge, as shown in the following figure:
The biggest difference between the two is the degree of freedom. There is basically no difference between the two in terms of ability. Judging from the ranking in the official organization, electron-forge is intentionally preferred.
2.3 Web solution Vue3 + Vite
We use Vue3 and use Vite as a construction tool. For specific advantages, you can check the introduction on the official website. This combination is the current mainstream Web development solution.
2.4 monorepo scheme pnpm + turbo
The current monorepo ecology is flourishing, and the correct practice method should be the integration method, that is, taking the strengths of each family.
For example, pnpm is good at dependency management, and turbo is good at building task orchestration. Then in the monorepo technology selection, I chose pnpm and turbo.
The reasons for pnpm are as follows:
- The best package management tool at present, pnpm absorbs the essence of mainstream tools such as npm, yarn, lerna, and removes the dross.
- Ecology, community active and strong
- The best design and practice of monorepo can be completed in combination with workspace
- Excellent performance in the management of multi-project package dependencies, code style, code quality, component library reuse, etc.
- Excellent performance in the development, debugging and maintenance of frameworks and libraries
Compared with the vue official website, I added workspace when using pnpm.
The reasons for turbo are as follows:
- It is a high-performance build system with features such as incremental builds, cloud caching, parallel execution, zero runtime overhead, task pipelines, and reduced subsets.
- It has very good task scheduling ability, which can make up for the shortcomings of pnpm in task scheduling
2.5 Database lowdb
There are many options for electron application database such as lowdb , sqlite3 , electron-store , pouchdb , dedb , rxdb , dexie , ImmortalDB , etc. One feature of these databases is that they are serverless.
There are three main factors to consider in the selection of electronic application database technology:
- Ecology (number of users, maintenance frequency, version stability)
- ability
- performance
- Others (matching with user technology)
We conducted relevant research through the following channels
- github issues, commits, forks, stars
- sourcegraph keyword search results
- npm package downloads, version releases
- Official website and blog
Four optimal choices are given, namely lowdb , sqlite3 , nedb , electron-store , for the following reasons:
- lowdb: Excellent performance in ecology, capability and performance, json format storage structure, supports api operations such as lodash, ramda, etc., which is conducive to backup and invocation
- sqlite3: Excellent performance in ecology, capability and performance, the first choice for Nodejs relational database
- nedb: It has excellent performance in terms of capability and performance. The disadvantage is that it is basically not maintained, but the foundation is still there, especially the operation is a subset of MongoDB, which is an excellent choice for users who are familiar with MongoDB.
- electron-store: Excellent ecological performance, lightweight persistence solution, easy to use
The database selection we use is the lowdb scheme.
PS: mentioning pouchdb, if you need to synchronize local data to a remote database, you can use pouchdb, which and couchdb can easily complete synchronization.
2.6 Script tool zx
In the process of software development, some processes and operations are completed through scripts, which can effectively improve development efficiency and happiness.
There are two excellent choices for relying on node runtime: shelljs and zx. The reasons for choosing zx are as follows:
- Comes with fetch, chalk and other common libraries, easy to use
- Multiple subprocesses are convenient and fast, execute remote scripts, parse md, xml file scripts, support ts, and have rich and powerful functions
- Produced by Google, with a large factory background, the ecology is very active
So far, the technical selection has been introduced. Next, I will introduce the common functions of electron applications.
3. Construction
This part mainly introduces the following 5 points:
- App icon generation
- binary build
- Build on Demand
- performance optimization
- Cross-platform compatible
3.1 Application Icon Generation
There are the following methods for generating icons of different sizes:
Windows
- Software generated: icofx3
- Web page generation: https://tool.520101.com/diannao/ico/(opens new window)
MacOS
- Software generated: icofx3
- Web page generation: https://tool.520101.com/diannao/ico/(opens new window)
- Command line build: build with sips and iconutil
3.2 Binary build
The content of this chapter is based on electron-forge, but the principle is the same.
When developing desktop applications, there are scenarios where third-party binary programs, such as ffmpeg, are used. There are two caveats to keep in mind when building binaries:
(1) Binary programs cannot be packaged into asar. The following settings can be made in the build configuration file (forge.config.js):
const os = require('os')
const platform = os.platform()
const config = {
packagerConfig: {
// 可以将 ffmpeg 目录打包到 asar 目录外面
extraResource: [`./src/main/ffmpeg/`]
}
}
(2) The development and production environments, the method of obtaining the binary program path is different, and the following code can be used to obtain it dynamically:
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 二进制程序路径
const ffmpegPath = path.join(basePath, `${platform}`, `ffmpeg${isWin ? '.exe' : ''}`)
3.3 Build on Demand
How do you build on-demand for cross-platform binaries?
For example, ffmpeg is used in desktop applications, it needs to have download binaries for windows, mac and linux. When packaging, if you do not build on-demand, all three binary files will be typed into the build, which will increase the size of the application a lot.
You can perform the following configuration in the forge.config.js configuration file to complete the on-demand construction. The code is as follows:
const platform = os.platform()
const config = {
packagerConfig: {
extraResource: [`./src/main/ffmpeg/${platform}`]
},
}
By using the platform variable to type the binary of the corresponding system into the build, the on-demand construction of the binary file can be completed.
3.4 Performance optimization
It is mainly to optimize the build speed and build volume, and the build speed is not easy to optimize. This article focuses on building volume optimization. Here is an example of the mac system. After the electron application is packaged, view the content of the application package, 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 contents:
It can be seen that the files in asar are the project code after our construction. From the figure, we can see that there is a node_modules directory. This is because in the electron construction mechanism, all dependencies of dependencies will be automatically typed into asar.
Therefore, combined with the above analysis, our optimization measures have the following 4 points:
- Put all the dependencies required for the web side construction into devDependencies, and only put the dependencies needed on the electron side into dependencies
- Remove non-production-related code and files from the build
- On-demand builds of binaries for cross-platform use, such as ffmpeg (described above)
- Clean up node_modules
Here is the fourth point, how to clean up node_modules?
If it is a dependency of yarn installation, we can use the following command in the root directory to simplify:
yarn autoclean -I
yarn autoclean -F
If it is a dependency installed by pnpm, point 4 should not work. I used yarn to install dependencies in the project, and after executing the above command, I found that the package size was reduced by 6M, although not much, but it was okay.
At this point, the construction function is introduced.
4. Update
This chapter is mainly divided into the following two aspects:
- Full update
- Incremental update
The above two updates will be introduced in sequence below.
4.1 Full update
Software updates, by downloading the latest package or zip file, require replacing all files.
The overall design flow chart is as follows:
According to the flow chart to achieve, we need to do the following things:
- Develop a server interface to return the latest version information of the application
- The rendering process uses tools such as axios to request the interface to obtain the latest version information
- Encapsulate the update logic, which is used to comprehensively compare the version information returned by the interface to determine whether to update
- Pass the update information to the main process through ipc communication
- The main process is fully updated through electron-updater
- Push the update information to the rendering process via ipc
- The rendering process displays the update information to the user. If the update is successful, a pop-up window will pop up to tell the user to restart the application and complete the software update.
4.2 Incremental update
By pulling the latest rendering layer package file, overwriting the previous rendering layer code, and completing the software update, this solution only needs to replace the rendering layer code without replacing all files.
According to the flow chart to achieve, we need to do the following things
- The rendering process periodically notifies the main process to detect updates
- The main process detects updates
- If you need to update, pull the latest package online
- Delete the old version package, copy the latest online package, and complete the incremental update
- Notify the rendering process, prompting the user to restart the application to complete the update
Both full update and incremental update have their own advantages. In most cases, incremental update is used to improve the user update experience, while full update is used as a bottom-up update solution.
At this point, the update function is introduced.
Five, performance optimization
Divided into the following 3 aspects:
- Build optimization
- Optimize at startup
- runtime optimization
Build optimization has been introduced in detail in the above content, and will not be introduced here. The following will introduce startup-time optimization and runtime optimization.
5.1 Optimization at startup
- Compile code with v8-compile-cache cache
- Core functions are loaded first, and non-core functions are loaded dynamically
- Use multi-process, multi-thread technology
- Packaging with asar: it will speed up 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 it?
Because electorn uses the V8 engine to run js, when V8 runs js, it needs to parse and compile first, and then execute the code. Among them, the parsing and compilation process consumes a lot of time and often leads to performance bottlenecks. The V8 cache function can cache the compiled bytecode, saving the time for 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 check this link document https://www.npmjs.com/package/v8-compile-cache(opens new window)
5.1.2 Priority loading of core functions, dynamic loading of 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 rendering process
- Lightweight 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 in the following figure:
The above figure basically contains the core key points and content of performance optimization. You can use this as a reference for performance optimization.
5.2.2 Lightweight the main process
The core solution is to hand over the time-consuming and computationally intensive functions to the newly opened node process for processing.
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 () => {
// 其他初始化代码...
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 fork to the newly opened node process for processing.
At this point, the performance optimization is introduced.
6. Quality Assurance
The whole process of quality assurance measures is shown in the following figure:
This chapter mainly introduces the following three aspects:
- automated test
- Crash monitoring
- crash governance
The above will be introduced in sequence below.
6.1 Automated testing
What is automated testing?
The above picture is a complete step of doing automated testing, you can see the picture to understand.
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:
Under normal circumstances, as software engineers, we can do certain unit tests. And from my current experience, if you are writing a business project, you will basically not write test-related code. Automated testing is mainly used to write libraries, frameworks, components, etc. that need to be provided to others as a separate entity.
Electron's testing tools recommend vitest and spectron. For specific usage, please refer to the official website documentation, there are no special skills.
6.2 Crash Monitoring
For GUI software, especially desktop software, the crash rate is very important, so it is necessary to monitor the crash.
The principle of crash monitoring is shown in the following figure:
Crash Monitoring Tips
- After the rendering process crashes, prompt the user to reload
- Unified initialization of crash monitoring through preload
- The main process and the rendering process simulate a crash through process.crash()
- Collect and analyze crash logs
After the crash monitoring is done, if a crash occurs, how to manage the crash?
6.3 Crash Management
Difficulties in crash 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:
- Timely upgrade electron
- User operation log and system information
- Recurrence and localization issues are more important than governance
- Leave the problem to the community to solve, the community responds quickly
- Good at analyzing and managing memory problems with devtool
7. Safety
As the saying goes, security is greater than the sky, and ensuring the security of electron applications is also an important thing. This chapter divides security into the following five aspects:
- Source code leak
- asar
- source code protection
- application security
- Coding Security
The above will be introduced in sequence below.
7.1 Source code leak
At present, electron is not doing well in source code security. The official only uses asar to do a very useless source code protection. How useless is it?
You only need to download the asar tool, and then decompress the asar file to get the source code inside, as shown in the following figure:
You can see the source code of the Yuque application through the operation in the figure. What is the asar mentioned above?
7.2 asar
asar is a tar-like archive format that combines multiple files into a single file. Electron can read arbitrary file contents from it without unpacking the entire file.
The principle of asar technology:
You can directly look at the electron source code, which is all ts code, easy to read, the source code is shown in the following figure:
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
Avoid source code leakage. According to the source code security from low to high, it can be divided into the following degrees
- asar
- Code obfuscation
- WebAssembly
- Language bindings
Among them, Language bindings is the highest source code security measure. In fact, C++ or Rust code is used to write electron application code. After compiling C++ or Rust code into binary code, the difficulty of deciphering will become higher. Here I talk about how to use Rust to write electron application code.
Solution: use napi-rs as a tool to write, as shown in the following figure:
We use pnpm-workspace to manage Rust code and 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 napi decoration code as follows:
use napi_derive::napi;
#[napi]
fn sum(a: f64, b: f64) -> f64 {
a + b
}
The above code is compiled into binary code that can be called by node through napi-cli.
After compiling, use the above code in electron as follows:
import { sum as rsSum } from '@rebebuca/native'
// 输出 7
console.log(rsSum(2, 5))
For the use of napi-rs, please read the official documentation, the address is: https://napi.rs/(opens new window)
At this point, the description of language bindings is complete. In this way, we can complete the source code protection of important functions.
7.4 Application Security
A well-known security problem is cloning attack. The mainstream solution to this problem is to bind user authentication information and application device fingerprints. The overall process is shown in the following figure:
- Application device fingerprint generation: It can be implemented with the napi-rs scheme 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
- window open security options
- Limit link jumps
The above specific details will not be introduced, and you can search for the above solutions by yourself. In addition, there is an official recommended best security practice, you can take a look when you have time, the address is as follows: https://www.electronjs.org/docs/latest/tutorial/security(opens new window)
At this point, the security part is introduced.
8. Summary
This article introduces our research on desktop technology, determining technology selection, and the practical experience summed up in the development process of 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. There are inevitably deficiencies and mistakes in the article. Readers are welcome to communicate in the comment area.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。