Author: He Dexing

We don’t have to talk about the introduction and demo preparation of electron, there are official related instructions and documentation. Today we are talking about some official documents are not available, I hope to help you.

Electron Project Process

The electron project packaging is essentially divided into three relatively independent processes without necessary dependencies, which can be operated independently.

  • Web business code packaging

Business code packaging is our Web code packaging, such as packaging vue project, react project, etc., packaged into a specified folder, such as dist.

  • Electron service code package

For Electron, our custom configuration, various listeners, custom menus, and a whole bunch of our custom services also need to be packaged.

  • Electron packaging

When the above two packing processes are complete, the electron packing can begin.

Custom configuration and packaging

The existing electron packing plug-ins have two types: electron- Packager and electron- Builder. Here we choose electron- Builder.

Without further ado, go straight to configuration items:

// Add build items to package.json
"build": {
    "productName": "Old bird club"."appId": "com.example.yourapp"."directories": {
      "output": "build"
    },
    "publish": [{"provider": "generic"."url": "http://www. Your website is.com/download/"}].// File configures the files that you pack locally. If you find that the configuration file is invalid, please try adding **/* after it
    "files": [
      "dist/electron/**/*".! "" node_modules/asar/**/*"]."dmg": {
      "contents": [{"x": 410."y": 150."type": "link"."path": "/Applications"
        },
        {
          "x": 130."y": 150."type": "file"}},"mac": {
      "icon": "icons/icon.icns"."artifactName": "${productName}_setup_${version}.${ext}"."target": "dmg"
    },
    "win": {
      "icon": "icons/icon.ico"."artifactName": "${productName}_setup_${version}.${ext}".// This configuration can be configured directly when you need to migrate additional files. The configuration file will be migrated when the package is packaged.
      "extraResources": {
        "from": "./config.json"."to": ".. /config.json"}},"linux": {
      "icon": "icons"."artifactName": "${productName}_setup_${version}.${ext}"
    },
    // Install configuration
    "nsis": {
      "oneClick": false."perMachine": true."allowElevation": true."allowToChangeInstallationDirectory": true."createDesktopShortcut": true."runAfterFinish": true."installerIcon": "./build/.icon-ico/icon.ico"."uninstallerIcon": "./build/.icon-ico/icon.ico"}},Copy the code

electron-builderCommon errors are reported

  1. Application entry file “build\electron. Js “in the “app.asar” does not exist. Seems like a wrong configuration.

    The solution

    (1) Please confirm whether the main configuration in your package.json is configured:

      "main": "./dist/electron/main.js".Copy the code

    (2) If main has been configured and the configuration is correct, this error is still reported. Please confirm whether your project is the REACT project! If electron – builder found rely on containing the react in dependencies, then main configuration is invalid, the solution is as follows:

    A. Add package.json/build/ to your build configuration

      "directories": {
        buildResources: "assets"
      },
      "extraMetadata": {
        main: "build/electron.js" // Here, change this to your main path
      }
    Copy the code

    B. Add a configuration to build:

      "build": {
        "extends": null
      },
    Copy the code
  2. If the React project is successfully packaged, but the page goes blank after opening it.

    The solution

    / / Install/install/install/install/install/install/install/install/install/install/install/install/install/install/install/install/install

      "homepage": ". /"
    Copy the code
  3. What can I do if the packaged client is too large?

    The solution

    Electron packaging, by default, will migrate files according to our package.json/build/file configuration:

      "files": [
        "dist/electron/**/*".// Configure the files that need to be packaged
        ! "" node_modules/asar/**/*" // Configure files that do not need to be packaged].Copy the code

    Node_modules is packaged by default (required by the node environment), so if our dependency package is too large, it is likely that the client package will be too large, so you can reduce the size of the package by configuring which dependencies are not needed.

    (1) You can install ASar, decompress app. Asar in the client, and check the packaged resources.

    (2) Do not try to say “do not configure files”. If electron does not configure files, it will pack all files (including source code).

    (3) During the packaging process, if we have additional requirements for migrating files, for example, I want to move a file and put it into our packaging resources. Additional resources can be added by configuring the extraResources configuration in package.json/build. ExtraResources is also available in build configuration and in specific package /extraResources configuration.

    • In the Build configuration, the extraResources value is an array that corresponds to all the packaged configurations. package.json/build/extraResources

        "build": {
          "productName": "Old bird club"."appId": "com.example.yourapp"."directories": {
            "output": "build"
          },
          "files": [
            "dist/electron/**/*".! "" node_modules/asar/**/*"]."extraResources": [{
            "from": "./config.json"."to": ".. /config.json"}, {"from": "./extraExe/test.exe"."to": ".. /extraExe/test.exe"
          }]
          "dmg": {
            "contents": [{"x": 410."y": 150."type": "link"."path": "/Applications"
              },
              {
                "x": 130."y": 150."type": "file"}}}]Copy the code
    • The other is within the concrete packaging configuration, which is valid only for the current packaging configuration and has a value type of object. Package. json/build/ extraResources By the way, configurations can be temporarily replaced in the package command.

        "win": {
          "icon": "icons/icon.ico"."artifactName": "${productName}_setup_${version}.${ext}"."extraResources": { // Configure this item as an additional resource
            "from": "./config.json"."to": ".. /config.json"}}Copy the code

Custom form

Windows on the MAC and Windows are different in configuration. For example, a custom form with no border is displayed. On the MAC, control buttons are displayed by the system, while in Windows, control buttons need to be customized in the business code.

The following points should be noted for custom forms:

  • TitleBarStyle specifies the display style of the control button in the upper left corner of the MAC.

    • Hidden: A full-size content window that hides the title bar and still has the standard window control button in the upper left corner.

    • HiddenInset: A window with a title bar hidden, where the control button is more distant from the window border.

    • CustomButtonsOnHover: Use custom close, Zoom out, and full-screen buttons that appear when you swipe across the top left corner of the window.

    mac: {
      height: 480.// Set the width to the width of the content
      useContentSize: false.width: 865.// Whether the window has a border
      frame: false.// Whether the window size can be changed
      resizable: false.// For MAC, control the placement of buttons
      titleBarStyle: 'hidden'.webPreferences: {
        / / the node integration
        nodeIntegration: true.plugins: true.webviewTag: true}}Copy the code
  • This parameter is required if you want to customize a window without borders

    • Frame: Indicates whether a window has a border. When there is no border, our page is displayed directly.

    • Transparent: Makes a frameless window transparent.

    Custom bezel-less windowlimitations(Below is the official transcript)

    • You cannot click through transparent areas. We will introduce an API to set window shapes to solve this problem, see Our Issue for more information.

    • Transparent Windows cannot be resized. Setting Resizable to true may cause transparent Windows to stop working on some platforms.

    • The Blur filter only works on web pages, so you cannot apply a blur effect to content located below a transparent window (such as other applications that are open on the user’s system).

    • On Windows, transparent Windows will not work when DWM is disabled.

    • On Linux, you must set –enable-transparent-visuals –disable-gpu on the command line to disable GPU and enable ARGB for window transparency. This is caused by an upstream bug that prevents alpha channel from running on some NVidia drivers on Linux machines.

    • On the Mac, transparent Windows don’t show the shadows of native Windows.

  • In the business page, define draggable areas (drag Windows)

    • By default, bezel-less Windows are not draggable. We can add attributes to styles that allow the user to click on an element and drag the window:

        -webkit-app-region: drag
      Copy the code
    • In contrast, we need to add attributes to certain elements to exclude certain elements. For example, if you configure a dragable element that contains an input field, you will find that the input field cannot be typed. In this case, we need to add input:

      -webkit-app-region: no-drag
      Copy the code
    • Currently, it seems that only rectangular shapes are supported. I don’t know if it will be updated later.

  • Parent-child modal window

    Parent-child modal window includes the concept of parent-child window and modal window, but because they are usually used together, so the concept of parent-child modal window is directly used here.

    • When a child window is created, if you set the property parent to the parent window, the child window will be displayed in the parent window.

    • Modal window is on the basis of the parent window, add model attribute, at this time the child window is not only above the parent window, the child window is not closed, the parent window can not operate. (Think of the sign-up window that opens when we play online games.)

      import { BrowserWindow } from 'electron'
      
      let child = new BrowserWindow({ parent: top, modal: true.show: false })
      child.loadURL('https://www.baidu.com')
      child.once('ready-to-show'.() = > {
        child.show()
      })
      Copy the code

    Note: Be careful when opening parent mode Windows, because the child window does not have a close button (MAC), it would be awkward if your business code is not closed (the child window cannot close, the parent window cannot close).

  • Window blank screen time

    In electron, the problem of white screen time is also a big problem, and the solutions are as follows:

    • Put some of the first screen resources locally on the client (see packaging configuration)

    • Optimize business code (do not write, baidu)

    • Set backgroundColor so that the white screen becomes a colored screen

      let win = new BrowserWindow({ backgroundColor: '#cdcdcd' })
      Copy the code
    • Loading the animation separately is also a good solution, but it takes some work

Client version detection and automatic update

  • Automatic update should be a very core function of the client program. AutoUpdater module is also built into ELECTRON, including version packaging, version detection, installation package download, installation package installation and so on.

  • We use the electron-updater, which is a dependency package for encapsulation optimization of autoUpdater in electron.

  • We can separate the autoUpdater module into a method and call it directly, with arguments as our main window:

      import { updateHandle } from './auto-updater'
    
      let mainWindow
      function createWindow () {
        // userAppConfig config for your window (see the previous article)
        mainWindow = new BrowserWindow(userAppConfig)
    
        mainWindow.loadURL(winURL)
    
        mainWindow.on('closed'.() = > {
          mainWindow = null
        })
    
        updateHandle(mainWindow)
      }
    Copy the code
      // auto-updater.js
      
      import fs from 'fs'
      import { ipcMain } from 'electron'
      import { autoUpdater } from 'electron-updater'
      import { updateURL } from './config/urlConfig' // This is our server installation package address
      // Bind detects update events
      /** * Receive render process checkForUpdate event triggers automatic update query */
    
      const updateUrl = process.platform === 'darwin' ? updateURL.mac : updateURL.windows
    
      function updateHandle (mainWindow) {
        if (process.env.NODE_ENV === 'development') {
          // If you want to debug automatic update in the development environment, you must actively set the current version
          // In the development environment, the version number will be the electron version, not the APP version
          autoUpdater.currentVersion = '0.1.0 from'
        }
        function sendUpdateMessage(obj) {
          mainWindow.webContents.send('updateMessage', obj)
        }
    
        // Set the address of the upgrade package
        autoUpdater.setFeedURL({
          provider: 'generic'.// Can also be github, S3, bintray
          url: updateUrl
        })
    
        // Triggered when an update error occurs
        autoUpdater.on('error'.(err) = > {
          fs.writeFile('update_error', err, function() {
            // callback function
          })
          sendUpdateMessage({action: 'error'.errorInfo: err})
        })
    
        // Triggered when checking for updates
        autoUpdater.on('checking-for-update'.() = > {
          sendUpdateMessage({action: 'checking'})})// Automatically downloads the configuration. If true, the upgrade package will automatically start downloading when an update is available
        autoUpdater.autoDownload = false
        // A new version was found
        autoUpdater.on('update-available'.(info) = > {
          sendUpdateMessage({action: 'updateAva'.updateInfo: info})
        })
    
        // Triggered when no updates are available
        autoUpdater.on('update-not-available'.(info) = > {
          sendUpdateMessage({action: 'updateNotAva'.updateInfo: info})
        })
    
        // Update the download progress event
        autoUpdater.on('download-progress'.(progressObj) = > {
          mainWindow.webContents.send('downloadProgress', progressObj)
        })
    
        // If the new version has been downloaded, send the message that the version has been downloaded and exit the app for installation
        autoUpdater.on('update-downloaded'.(info) = > {
          sendUpdateMessage({action: 'updateDownloaded'.updateInfo: info})
          ipcMain.on('isUpdateNow'.() = > {
            console.log('isUptaeNow')
            autoUpdater.quitAndInstall()
          })
          fs.writeFile('update-downloaded'.JSON.stringify(info), function() {
            // callback function})})// Perform automatic update checks
        ipcMain.on('checkForUpdate'.() = > {
          console.log('The main process receives the command to start querying the version... ')
          autoUpdater.checkForUpdates()
        })
    
        / / download
        ipcMain.on('downloadUpdate'.() = > {
          autoUpdater.downloadUpdate()
        })
      }
    
      export {
        updateHandle
      }
    
    Copy the code

Auto-update questions and details

  • There are a lot of things that NEED to be noted that I’ve written directly in the code.

  • The development environment automatically automatically updates the version detection, the version has been incorrect.

    • If you need to debug automatic updates in your development environment, you must actively set the current version
    • electron-updaterIn the development environment, the version number will beelectronVersion, not app version number
  • At the time of packaging, don’t forget to package. The json/build/configuration of the publish the address do you want any updates

      "publish": [{"provider": "generic"."url": "http://www.snowhe.com/myaide/download/"}]Copy the code
  • Yml and Build-effective -config.yaml (Windows is fresh. yml and a.yaml file). These two files must be updated to our folder on the server along with the installation package.

  • Package. json/build/nsis can be used to configure the installation process.

        "nsis": {
          "oneClick": false."perMachine": true."allowElevation": true."allowToChangeInstallationDirectory": true."createDesktopShortcut": true."runAfterFinish": true."installerIcon": "./build/.icon-ico/icon.ico"."uninstallerIcon": "./build/.icon-ico/icon.ico"
        }
    Copy the code
    • This configuration does not work on the MAC, forget it, since when does the MAC Install app let you customize?

    • If you are familiar with NSH files, you can also configure NSH files yourself (I am not familiar with them anyway) :

      ! macro customHeader ! macroend ! macro preInit ! macroend ! macro customInit # guid=7e51495b-3f4d-5235-aadd-5636863064f0
      ReadRegStr $0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{7e51495b-3f4d-5235-aadd-5636863064f0}" "UninstallString"
      ${If} $0! =""
      MessageBox MB_ICONINFORMATION|MB_TOPMOST  "This program has been detected on the system and will be uninstalled." IDOK
      # ExecWait $0 $1${EndIf} ! macroend ! macro customInstall ! macroend ! macro customInstallMode # set $isForceMachineInstall or $isForceCurrentInstall # to enforce one or the other modes. #set  $isForceMachineInstall ! macroendCopy the code
    • In business code, we can also make the main process version check by sending a message to the renderer process (more on message communication later).

Communication between processes

The communication between processes mainly refers to the communication between the main process and the rendering process, which is an important function of our desktop app. It can also be said to be the communication between our business code and the window.

- Understand the first, is the parent page and iframe child page communication. If you are interested, you can go to know about it. It's all about message communication. ** So the message passed is also a string (highlighted)** - Understand the second, we are a bit like publish subscriber [observer mode], see below for details.Copy the code

Communication in the renderer process is delivered via ipcRender, which provides some methods for sending synchronous or asynchronous messages from the renderer process (web pages) to the main process. The main process can also listen for webContent messages in the window to receive and send messages.

- Message communication is one-way. - It can be interpreted as a phone message, not a live call.Copy the code

How to understand ipcRender?

The relationship between the main process and the renderer process is much like the relationship between the page and the iframe in the page. (Look up postMessage if you’re not familiar with this)

Correspondingly, we need to receive in the main program ipcMain, that is, when the ipcRender in the rendering process sends messages, the ipcMain in the main process receives and sends back the results to ipcRender (and then sends messages to ipcRender), so as to achieve a two-way communication purpose.

  • So in summary, it’s really just message communication between ipcMain and ipcRender.

  • We do communication also, in the two modules corresponding to mount the message we need to achieve the type of communication we need.

How do I mount messages in ipcRender and ipcMain

  • Mount message listener in ipcMain:

    • The introduction of the head
    import { app, BrowserWindow, ipcMain } from 'electron'
    import { createChildWindow } from './utils'
    
    Copy the code
    • Create a window
      let mainWindow
    
      function createWindow () {
        /** * Initial window options */
        mainWindow = new BrowserWindow(userAppConfig)
    
        mainWindow.loadURL(winURL)
    
        mainWindow.on('closed'.() = > {
          mainWindow = null
        })
    
        // Set up a listener
        addWindowListener()
      }
    Copy the code
    • IpcMain listens for the types of messages we subscribe to
    function addWindowListener () {
      // Close the window
      ipcMain.on('closeWindow'.() = > {
        mainWindow.close()
      })
      // Hide the window
      ipcMain.on('hideWindow'.() = > {
        console.log('hide')
        mainWindow.minimize()
      })
      // Maximize the window
      ipcMain.on('maxWindow'.() = > {
        console.log('maxWindow')
        mainWindow.maximize()
      })
      // Full screen display
      ipcMain.on('fullScreen'.(e, flag) = > {
        console.log(flag)
        mainWindow.setFullScreen(flag)
      })
      // Restore the previous screen status
      ipcMain.on('restoreWindow'.() = > {
        mainWindow.restore(true)})// Generate a submodal window (disable the main window)
      ipcMain.on('addChildWindow'.(e, url, config) = > {
        console.log('addChildWindow:', e, url, config)
        createChildWindow({
          parent: mainWindow,
          modal: true,
          url
        })
      })
    }
    
    Copy the code
    • IpcMain postMessage
      function sendMessage(obj) {
        mainWindow.webContents.send('sendMessageToIpcRender', obj)
      }
      sendMessage({
      	type: 'call'.message: 'ipcRender, I'm your boss ipcMain, please help me find Chuchu Baby. '
      })
    Copy the code
    • Receive in ipcRender
    import { ipcRenderer, shell } from 'electron'
    ipcRenderer.on('sendMessageToIpcRender'.(event, info) = > {
      console.log(info)
    })
    Copy the code
    • IpcRender sends messages
    import {ipcRenderer} from 'electron'
    const info = {
    	type: 'callback'.message: 'Boss ipcMain, this is ipcRender, I can't find Chuchu Baby.'
    }
    ipcRenderer.send('sendMessageToIpcMain'.JSON.stringify(info))
    Copy the code

    The similarity and difference of communication transmission is caused by the protocol chosen. As we look at these, we will see that similar functions have a lot in common. If you want to study these protocols, you can see that there are many problems that cannot be explained normally.

    That’s all for this installment. Thank you