preface

Some time ago, I developed a cross-platform (currently Mac and Windows) free and open source map bed upload application called PicGo using Electron Vue. During the development process, there were many problems, not only from the application business logic itself, but also from Electron itself. I learned a lot in the process of developing this application. Since I also started to learn electron from 0, so many experiences should also be able to give some inspiration and instructions to students who want to learn electron development for the first time. Therefore, write a Electron development of actual combat experience, with the most close to the actual project development Angle to elaborate. I hope it helps.

It is expected to be launched from several series of articles or aspects:

  1. Electron – vue primer
  2. Simple development of the Main and Renderer processes
  3. JSON database lowDB is introduced based on Lodash
  4. Some cross-platform compatibility measures
  5. Release and update through CI
  6. . (Think of writing again)

instructions

PicGo is developed using electron vue, so if you know vue, it will be faster to learn along. If you have a technology stack like React or Angular, you may not learn much from the Render side of the build, but you should be able to do so on the Electron side.

If the previous article did not read friends can follow from the previous article.

The need for persistent storage of data

It’s not like some of the demos that a lot of people write, where you just ask the API and you just show the web page. After all, electron is a desktop-level application, and if you still think in terms of pure Web development, then you lose the meaning of using electron.

Persistent data storage is actually familiar to the back end. This usually refers to the process of storing data in memory on disk in a different storage model and then reading it into memory when needed. The storage model is usually the familiar database. When it comes to databases, many people think of MySQL, Mongodb, SQLite, etc. Most of these databases are in server-client mode and need to start the Server — which is usually what we install. But you rarely see someone install a desktop software at the same time, ask someone to configure a database.

Because some of the data we have to save locally so we can read it the next time we use it. For Electron, since installing MySQL and Mongodb is not an elegant solution, it would be a good thing for both us and our users to find other ways to store data locally without the user having to worry about how to store it.

Pure JavaScript database choice

Since it’s on the JS stack, I found some databases that are pure JavaScript implementations. After preliminary screening, I found the following two:

  1. Nedb 7800STAR (2018-02-12)
  2. Lowdb 7269STAR (2018-02-12)

To compare

Among them, at present, NEDB is more widely used, the number of STAR is more (as of February 12, 2018), and there are many articles about the combination of NEDB and ELECTRON. However, NeDB has not been maintained for nearly two years, and does not support Promises natively and uses asynchronous callbacks (although promises can be implemented through third-party plug-ins).

Lowdb is a JSON storage structure based on LoDash development, with the support of LoDash, it is very easy to use. The advantage is that it is in constant maintenance and has a number of useful plug-ins. And the key is synchronous operation, using the chain call writing method, it feels like jQuery. I also like the fact that data stored in JSON is easy to call and easy to back up.

To sum up, PicGo uses lowDB.

Initialization of lowDB

Since electron places Node’s FS module into both main and Renderer, we can easily use FS operations on both ends. And lowDB is essentially through FS to read and write JSON files, just meet our requirements. So according to the official documentation, let’s first initialize it.

In order to operatefsMore convenient, might as well install onefs-extra.

Create a datastore.js file:

import Datastore from 'lowdb'
import FileSync from 'lowdb/adapters/FileSync'
import path from 'path'
import fs from 'fs-extra'
import { app } from 'electron'

const STORE_PATH = app.getPath('userData') // Get the user directory for electron

const adapter = new FileSync(path.join(STORE_PATH, '/data.json')) // Initialize the json file name and storage path that lowdb reads and writes

const db = Datastore(adapter) // lowdb takes over the file

export default db // Expose

Copy the code

We can then introduce this in the main and renderer processes:

import db from '.. /datastore' // Depends on where your datastore.js is located
Copy the code

Hit the pit

If it were just the basics above, this article would be too simple. The trampling of the electron into lowDB has just begun.

1. Renderer processes use the remote module

The first problem is obvious from the initialization above. The app module is unique to the main process, and the Renderer process should use the remote.app module. So the above code will report an error in the renderer process.

So for the first time I changed it to run in both main and renderer:

import Datastore from 'lowdb'
import FileSync from 'lowdb/adapters/FileSync'
import path from 'path'
import fs from 'fs-extra'
import { app, remote } from 'electron' // Import the remote module

const APP = process.type === 'renderer' ? remote.app : app // Determine which module is used in which mode according to process.type

const STORE_PATH = APP.getPath('userData') // Get the user directory for electron

const adapter = new FileSync(path.join(STORE_PATH, '/data.json')) // Initialize the json file name and storage path that lowdb reads and writes

const db = Datastore(adapter) // lowdb takes over the file

export default db // Expose

Copy the code

2. Initialization paths of development mode and production mode

At the time of development mode, through the APP. GetPath (‘ userData) access to the path of the form such as: / Users/molunerfinn/Library/Application Support/Electron (macOS below). This is a path that has been created automatically. So the initialization path already exists when developing patterns.

This is not the case in production mode. In production mode, when the application is started for the first time, the path obtained by app.getPath (‘userData’) is not created, but datastore.js has been loaded. So the initialization path doesn’t exist at this point. The user will get the following error when opening the app for the first time:

So we must check whether the path exists in datastore.js:

The FS here is from the FS-extra module

if(process.type ! = ='renderer') {
  if(! fs.pathExistsSync(STORE_PATH)) {// If no path exists
    fs.mkdirpSync(STORE_PATH) / / is created}}Copy the code

3. Initialize data

Sometimes we need to specify the basic structure of the database, such as an array, so we initialize it to []. If it is an Object and has a specific value, it specifies a specific value. While initializing data structures should not be judged every time data is read or written, it should be done when the database is created, so writing in datastore.js is appropriate.

For example, I want to initialize the list of uploads to be an array, as follows:

if(! db.has('uploaded').value()) { // Check whether the value exists
  db.set('uploaded', []).write() // Create it if it does not exist
}
Copy the code

4. Unique ID field

Most people who have used MySQL initialize a table with an incremented ID field as a unique identifier for data. While lowDB can’t easily create a custom id field, lodash-id makes it easy to automatically add a unique ID field to each newly added data.

Like:

{
  "height": 514."type": "weibo"."width": 514."id": "7f247aa7-ffeb-4bb1-87f1-a0d69824ec78"
}
Copy the code

Initialization is also convenient:

// ...
import LodashId from 'lodash-id'
// ...

const db = Datastore(adapter)
db._.mixin(LodashId) // through._mixin()
Copy the code

Initialize the complete code

Through the above pit, PicGo initialization code is as follows, for reference only:

import Datastore from 'lowdb'
import LodashId from 'lodash-id'
import FileSync from 'lowdb/adapters/FileSync'
import path from 'path'
import fs from 'fs-extra'
import { remote, app } from 'electron'

const APP = process.type === 'renderer' ? remote.app : app
const STORE_PATH = APP.getPath('userData')

if(process.type ! = ='renderer') {
  if(! fs.pathExistsSync(STORE_PATH)) { fs.mkdirpSync(STORE_PATH) } }const adapter = new FileSync(path.join(STORE_PATH, '/data.json'))

const db = Datastore(adapter)
db._.mixin(LodashId)

if(! db.has('uploaded').value()) {
  db.set('uploaded', []).write()
}

if(! db.has('picBed').value()) {
  db.set('picBed', {
    current: 'weibo'
  }).write()
}

if(! db.has('shortKey').value()) {
  db.set('shortKey', {
    upload: 'CommandOrControl+Shift+P'
  }).write()
}

export default db
Copy the code

Lowdb basic operation

The basic operation of a database is nothing more than CURD.

It represents the Create (Create), Update (Update), read (Retrieve), and Delete (Delete) operations.

The following describes the basic usage of lowDB.

create

Mainly through the set() or defaults() methods. Where defaults() specifically initializes empty JSON files. (However, similar initialization as described in the previous section can be achieved with set.)

db.defaults({ posts: [].user: {}, count: 0 })
  .write() // Be sure to explicitly call the write method to store data to JSON
Copy the code

Note that any write operation must be used explicitlywrite()Method to save.

read

db.get('posts').value() / / []
Copy the code

You can also use some of loDash’s methods to query your JSON.

Such as the find ()

db.get('posts')
  .find({ id: 1 })
  .value()
Copy the code

Note that any read operation must be explicitly usedvalue()Method to get the value.

update

Different structures are updated in different ways.

Such as assignment for objects and push() or insert() for arrays (methods provided by lowdb-id)

db.get('posts').insert({ // Insert into the array
  title: 'xxx'.content: 'xxxx'
}).write()
Copy the code

We can update objects directly with set() :

db.set('user.name'.'typicode') // Use the set method to manipulate objects
  .write()
Copy the code

You can also write:

db.set('user', {
  name: 'typicode'
}).write()
Copy the code

Very flexible, right?

You can use update to update existing data.

db.update('count', n => n + 1) The update method operates with existing values
  .write()
Copy the code

delete

An eligible item can be removed using the remove() method:

db.get('posts')
  .remove({ title: 'low! ' })
  .write()
Copy the code

An attribute can be deleted by unset:

db.unset('user.name')
  .write()
Copy the code

You can also remove items with the specified ID using removeById() provided by lodash-id:

db.get('posts')
  .removeById(id)
  .write()
Copy the code

Lowdb Actual pit used

The big catch with lowDB is that if I just go with the basics, I might have values that I stored in the main process that I couldn’t read in the renderer process.

Why? Because the db directly referenced is really just the data in memory at that point in time. Lowdb reads JSON data into memory during use. New data is written to the disk only when a write operation is required.

The main and Renderer processes get the DB that was read when the application was opened. With no extra processing, the db in memory that main gets is not the same DB as the one that renderer gets, meaning that there are not two references to the same DB, but two copies of the same DB. Renderer does not know what main does to it. In other words, if main does any reading or writing to the DB, the Renderer gets the same DB that was read when the application was opened. So the main process updates the data, and the renderer process still doesn’t get the new data.

Is there a way around that? There is. It’s just a bit of a hassle. At the beginning of all db operations, read the latest db state again:

Such as:

db.read().get('xxx').value()

db.read().set('xxx'.'xxx')
Copy the code

Force the memory area to be flushed with read() before each DB operation to ensure that the data is up to date.

A convenient way to use lowDB in Vue

Just as many people hang Axios on Vue’s prototype chain in Vue, we can use a similar method to make it easier to use LowDB in Vue.

Open the entry file for your Vue project, usually main.js

// ...
import db from '.. /datastore'
import Vue from 'vue'
// ...

Vue.prototype.$db = db
Copy the code

So we can use lowdb as this.$db in our project.

conclusion

Lowdb and the use of LOWDB in electron are introduced in detail in this paper. A lot of these are problems and potholes that I had when I was developing PicGo. Perhaps behind a few simple words in the article is my countless times of reference and debugging. Hopefully this article has given you some insight into the development of electron- Vue. The relevant code in this article can be found in the PicGo project repository. If this article can help you, it will be my happiest place. If so, please follow my blog and the rest of this series.

Note: the pictures in this article are all my personal works unless otherwise specified, please send a private message