跳到主要内容
版本:0.17.0+

electron howto

  1. electron best practice
    1. design of dependency workflow
    2. app path, storing db, log, etc
    3. log into file
  2. electron test
  3. sqlite best practice
    1. don't use relative path as sqlite position
  4. log electron
    1. TODO: idealog color configuration
  5. build for Windows on Mac
  6. build native dependencies and for multi platforms
  7. custom app logo
    1. a good start
    2. customize icon for unpacked app
    3. square .icon
  8. electron-builder files map
  9. ⭐️⭐️⭐️ debug binary electron
  10. ipcRenderer communication
  11. hwo to use preload.ts
    1. the preload script won't run if we just start the main process
    2. it's ok when we just use preload.js
    3. what about using a preload.ts
      1. remedy 1, the tsc solution
    4. how to remedy the electron-react-boilerplate project

electron best practice

design of dependency workflow

picture 35

How to organize your project structure quite depends on your project scale and realization.

For example, since my project is not too big, I combined almost all the constant variables into one file const.ts in each module, containing channels , error_types, defined variables, etc.

app path, storing db, log, etc

Since the userData is the sub path of appData with the name of our application, so it's better to put all of our data into userData.

// src/main/base/utils.ts
export const getRootPath = () => app.getPath('userData');

export const getLogPath = () => path.join(getRootPath(), 'main.log');

export const getDbPath = () => path.join(getRootPath(), 'hjxh_data.sqlite');

!!!tip If run under the development, since the app is run via electron ., so the userData points to the Electron, which is different from the real build version with the actual name defined in our package.json/build.

ref:

log into file

log.transports.file.resolvePath = () => getLogPath();
log.transports.file.format = '{y}-{m}-{d} {h}:{i}:{s}.{ms} {level}: {text}';

ref:

electron test

When all the ts-node / esm relative environment is prepared well, there still exists some problems.

For example, concerning about database, since I use it derived from app runtime, and then it would not be mocked unless we start our script as a real electron app rather than a script for unittest.

picture 36

sqlite best practice

don't use relative path as sqlite position

When using relative path, many packages including system would treat it on the base of current process directory.

However, if the application is build into binary and run by click, then the behavior would become a bit different since e.g. in Mac the application is run with process.cwd() = '/'.

And then if we write our data into a sqlite which is created with relative path, such as ./test.db, then the error would arise up because of permission.

So the best practice is to use AppData directory via path.join(app.getPath('appData'), dbName).

log electron

There are two places where console.log output goes:

If you log in the renderer process, you can see it in the console in the browser window. If you open the dev tools programmatically you can see this console even after building.

If you log in the main process, you can see these messages if you start the installed app or the unpacked binary via command line. In windows this would be the app.exe in the win-unpacked directory that electron-builder creates.

Another alternative would be a logger like electron-log that writes the log messages into a configured file.

ref:

And I am surprised to find that it has been in the electron-react-typescript boilerplate.

picture 9

And it's used for Auto-Update.

picture 10

ref:

However, it may not work since the output still goes into the terminal.

TODO: idealog color configuration

build for Windows on Mac

Using this command:

electron-builder build --win
picture 4

But then: picture 5

Hence, we need a mirror.

build native dependencies and for multi platforms

It seems this is auto finished by electron-builder, which is described at: This module can automatically determine the version of Electron and handle the manual steps of downloading headers and rebuilding native modules for your app.

picture 1

a good start

All we need to do is to follow: Changing electron app icon. Generate icns | by Khoa Pham | Fantageek | Medium

In which it directs us to download onmyway133/IconGenerator: 🍱 A macOS app to generate app icons

success when run after pack.

picture 1

customize icon for unpacked app

If we wanner the icon does work in unpacked environment, we need to replace the default icon in the module.

cp ./assets/icon.icns node_modules/electron/dist/Electron.app/Contents/Resources

Or we can follow the above article, right click app, and change the icon.

square .icon

When I pack for windows, it told me the icon size is at least '256 x 256'.

However I checked the raw image xx.png, and admitted that the size is far bigger than this, but only to be converted to '256 x 204'.

I realized it may be owing to the size ratio, and I changed the image file into a square one. picture 2

And then, it worked!

ref:

electron-builder files map

The current problem has become clarified, that is files not found.

We need a map, since I tried files but in vain.

picture 1

Now, the problem is that the database would auto create after first query, while it's empty and then causes errors.

picture 2

⭐️⭐️⭐️ debug binary electron

Here's what worked for me on Mac.

In terminal type lldb path/to/build.app In the opened debugger type run --remote-debugging-port=8315. It should open a window of your app. Open Chrome at http://localhost:8315/ Click on the name of the app. For example, Webpack App. If you don't see anything in the opened tab, focus on the window of your app.

ref: https://stackoverflow.com/a/56634497/9422455

ipcRenderer communication

It deserves my attention that the msg interface from main process is so-called ipcMainEvent which is not exported.

picture 79

So, for typescript support, I need to define a 'hooked ipcMainEvent' like this:

interface IpcMainEvent extends Event {
// eslint-disable-next-line @typescript-eslint/ban-types
reply: Function;
}

And when communicating, I should be careful with the difference between on and once, and don't forget to close the channel if need.

picture 74

ref:

hwo to use preload.ts

ref:

suppose:

MAIN: src/electron/main
RENDERER: src/renderer/index
PRELOAD_TS: src/electron/preload.ts
PRELOAD_JS: src/electron/preload.js
RUN_MAIN: electron MAIN, or like
RUN_RENDERER: webpack serve --config WEBPACK_DEV_CONFIG_RENDERER.js, or like

the preload script won't run if we just start the main process

First, if we only run electron MAIN or electron -r ts-node/register/transpile-only MAIN (ts support), the electron window would run successfully no matter we pointed a preload file or not since the file would only run just before the renderer process, i.e. after the main process and separate.

it's ok when we just use preload.js

If we just use preload.js, then no matter we use webpack or not, the programme would run well.

what about using a preload.ts

The main problem concerning with preload.ts is that when we use electron, we are likely to use different pack method for main process and the renderer one, and the core concept is whether the preload.ts had been compiled into preload.js and executed by the renderer.

remedy 1, the tsc solution

If we first created a preload.ts file and it doesn't export anything to with the main or renderer codes, then the only change we need to do is that to add tsc PRELOAD_TS && before the RUN_RENDERER.

It can be expected to see the preload.ts changed into preload.js and all the things goes ok.

For example, we can write pure ts type of preload, and it in fact needs no extra modification since the interface is perfect except a little dangerous since we didn't put constraint on the api request. More can be found refer to: Security considerations

picture 62

I do care about the safe problem if this is a huge app developed for commercial use.

But If I write more apis, then it would drive me to separate my apis in another file, and the renderer, as well as the main, would read from the file together. At the same time, tsc would compile the preload.ts along with the separate file to .js, then a emit skipped error may happen if the electron main to read a interface.ts file but to find there exists both interface.ts and interface.js.

how to remedy the electron-react-boilerplate project

Dive into the source coe of electron-react-boilerplate , we can know the programme startup flow under the development environment is as the following:

flowchart TD subgraph Start[npm run start] PreStart[check port in use] --> StartRender end subgraph StartRender[\`npm run start:renderer\`] subgraph WebpackDev[\`webpack serve --dev.config\`] subgraph BeforeRun[before run: \`npm run start:main\`] ElectronStart[run electron main] end end end