Many people know that Vscode, a powerful editing application, is written by electron framework. Out of interest and convenience, I optimized the company’s project by using Electron to make the project into a desktop application. Without further talk, the steps are as follows:

One: Install the Electron

The electron is a bit big and takes a long time to install, so be more patient

npm install --save-dev electronCopy the code

Two: the start of the project

In the development environment, add main.js to the root directory and attach

const { app, BrowserWindow } = require('electron'// Keep a global reference to the window object. Otherwise, the window object will be closed automatically when the JavaScript object is garbage collectedlet win

function createWindow() {// Create browser window. win = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration:true}}) // load the index.html file'./public/index.html'
  win.loadFile('index.html') / / open the developer tools win. WebContents. OpenDevTools () / / when the window is closed, the event will be triggered. win.on('closed', () => {// Unreference the window object. If your application supports multiple Windows, // usually stores multiple window objects in an array. // In the meantime, you should delete the corresponding element. Win = null})} // Electron will call this function after initialization and when preparing // to create a browser window. // Parts of the API can only be used after the ready event is triggered. app.on('ready'CreateWindow) // Exit when all Windows are closed. app.on('window-all-closed', () => {// On macOS, most applications and their menu bar remain active unless the user explicitly exits with Cmd + Q.if(process.platform ! = ='darwin') {
    app.quit()
  }
})

app.on('activate', () => {// On macOS, when you click on the Dock icon and no other window opens, // usually a new window is created in the application.if(win === null) {createWindow()}}) // In this file, you can continue to write the rest of the application's main process code. // It can also be split into several files and imported with require.Copy the code

Then add commands to package.json

 "scripts": {
    "start": "electron ."
  }Copy the code

At this point, run NPM start/YARN start electron and start up.

Third, the packaging of the project

There are two ways to pack the electron application: electron- Packager and electron- Builder.

At first I used electronic-Packager, but later I switched to electronic-Builder to pack if I wanted to do automatic updates

"package": "Fabric-packager./build/ fabric-test --all --out ~/ -- fabric-version 1.0.0".Copy the code

The auto-builder is used for packaging and auto-updater is used for automatic update.

3.1 The first is the installation:

yarn add electron-builder --dev
yarn add electron-updater
npm install electron-updater --save
Copy the code

3.2 Configuring the package.json file

Note: The NSIS configuration does not affect the automatic update function, but can optimize the user experience, such as whether to allow users to customize the installation location, whether to add desktop shortcuts, whether to start immediately after the installation, configure the installation icon, etc

  "build": {    "appId": "****.app"."copyright": "back-manage"."productName": "back-manage"."nsis": {      "oneClick": true."perMachine": true."allowElevation": true."allowToChangeInstallationDirectory": true."createDesktopShortcut": true."runAfterFinish": true."installerIcon": "public/favicon1.png"."uninstallerIcon": "public/favicon1.png"    },    "publish": [{"provider": "generic"."url": "https://***"}]."dmg": {      "contents": [{"x": 410,          "y": 150,          "type": "link"."path": "/Applications"        },        {          "x": 130,          "y": 150,          "type": "file"}},"mac": {      "icon": "public/favicon1.png"."artifactName": "${productName}_setup_${version}.${ext}"."target": [        "dmg"."zip"]},"win": {      "icon": "public/favicon1.png"."target": [        "nsis"."zip"]}},Copy the code

3.2 Modifying the main.js file of the main process (introduce the electron-updater file to add automatic update detection and event monitoring)

Notice that I’m using react here, and instead of using main.js, the main process file used here is the electron.js file under public

warn:  public/electron.js not found. Please see https://medium.com/@kitze/%EF%B8%8F-from-react-to-an-electron-app-ready-for-production-a0468ecb1da3Copy the code

I modified eletron.js

/ / the introduction of electron and create a Browserwindowconst {app, BrowserWindow, Menu, ipcMain} = require ('electron'); const { autoUpdater } = require('electron-updater'); const path = require('path'); const url = require('url'); const uploadUrl ='https://***/'; // Keep a global reference to the window object to avoid the window being closed automatically when JavaScript objects are garbage collected. // Check for updates when you want to check for updates, and write your own actions after the renderer event is triggeredfunction updateHandle() {  const message = {    error: 'Check update error',    checking: 'Checking for updates... ',    updateAva: 'New version detected, downloading... ',    updateNotAva: 'This is the latest version, don't update it'  };  const os = require('os');  autoUpdater.setFeedURL(uploadUrl);  autoUpdater.on('error', error => {    sendUpdateMessage(message.error);    // sendUpdateMessage(error);  });  autoUpdater.on('checking-for-update', () => {    sendUpdateMessage(message.checking);  });  autoUpdater.on('update-available', info => {    console.log(info)    mainWindow.webContents.send('updateAvailable'.'

New version detected '

+ info.version + ', need an upgrade? ' + info.releaseNotes); // sendUpdateMessage(message.updateAva); }); autoUpdater.on('update-not-available', info => { sendUpdateMessage(message.updateNotAva); }); // Update download progress event autoupderater. On ('download-progress', progressObj => { console.log(progressObj) const winId = BrowserWindow.getFocusedWindow().id; let win = BrowserWindow.fromId(winId); win.webContents.send('downloadProgress', progressObj); }); autoUpdater.on('update-downloaded', ( event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate ) => { console.log(event, releaseNotes, releaseName, releaseDate, updateUrl, quitAndUpdate) console.log('update-downloaded'); ipcMain.on('isUpdateNow', (e, arg) => { console.log(arguments); console.log('Start updating'); // some code here to handle event autoUpdater.quitAndInstall(); }); mainWindow.webContents.send('isUpdateNow'); }); ipcMain.on("isDownload", () => { autoUpdater.downloadUpdate(); }) ipcMain.on('checkForUpdate', () = > {/ / perform automatic update check autoUpdater. CheckForUpdates (); }); }// Send an event to the renderer process via main, prompting the renderer process to updatefunction sendUpdateMessage(text) { mainWindow.webContents.send('message', text); }function createWindowMainWindow = new BrowserWindow({width: 800, height: 600, webPreferences: {webSecurity:false, allowDisplayingInsecureContent: true, allowRunningInsecureContent: true, nativeWindowOpen: true, webSecurity: false, nodeIntegration: true, // Whether node is fully supported. Default istrue nodeIntegrationInWorker: true// Whether Node integration is enabled in the Web worker preload: path.join(__dirname,'./renderer.js')}}); /* * Mainwindow.loadurl (url.format({pathname: path.join(__dirname,)) mainwindow.loadurl (url.format({pathname: path.join(__dirname,'index.html'), protocol: 'file:', slashes: true})) */ // load application ---- for react project // mainwindow.loadurl ('http://localhost:3000/'); ----react package mainwindow.loadurl (url.format({pathname: path.join(__dirname,'./index.html'), protocol: 'file:', slashes: true/ /})'http://192.168.0.11:8082'); / / open the developer tools, don't open the default / / mainWindow webContents. OpenDevTools () / / close the window when the trigger of the following events. Check mainWindow.'closed', () => { mainWindow = null; }); ipcMain.on('openDev', () => { mainWindow.openDevTools(); }); UpdateHandle ()}// This method is called app.on() when Electron has completed initialization and is ready to create a browser window'ready', createWindow); // Exit the app when all Windows are closed. App.on ('window-all-closed', () => {// In macOS, applications and menu bars are always active unless the user presses' Cmd + Q 'to exit explicitly.if(process.platform ! = ='darwin') { app.quit(); }}); app.on('activate'() => {// When you click the Dock icon in macOS and no other application window is already open, you usually rebuild a window in the applicationif(mainWindow === null) { createWindow(); }}); // You can continue in this script or use require to introduce a separate JS file.Copy the code

3.3 Add the corresponding code to detect the update in the project startup app.js

Since electron is not available in app.js, we need to inject electron in index.html

window.electron = require('electron');Copy the code

Then in the app. Js

const ipcRenderer = window.electron && window.electron.ipcRenderer; Then in componentDidMount or useEffect, add const self = this;if (ipcRenderer) {      ipcRenderer.send('checkForUpdate');      ipcRenderer.on('message', (event, message) => { console.log(message); }); // Note: the "downloadProgress" event may not trigger the problem, just limit the download speed.'downloadProgress', (event, progressObj) => {        console.log('download', progressObj);        this.downloadPercent = progressObj.percent || 0;      });      ipcRenderer.on('isUpdateNow', () => {        console.log('Update now?');        ipcRenderer.send('isUpdateNow'); }); // New version ipcrenderer. on('updateAvailable', (event, message) => {        console.log(event, message);        self.$notification.open({          message: 'Notification',          description: 'New version detected, updating... ',          onClick: () => {            console.log('Notification Clicked! '); }}); ipcRenderer.send('isUpdateNow'); }); } Remove events from the componentWillUnmount hook functioncomponentWillUnmount() {    if (ipcRenderer) {      ipcRenderer.removeAll([        'message'.'downloadProgress'.'isUpdateNow'.'updateAvailable']); }}Copy the code

Everything is in place except for a man to carry out the order.

If there is no automatic update problem, packager packages do not need to be signed. However, in order to implement automatic update, signature is required.

cannot find valid "Developer ID Application" identity or custom non-Apple code signing certificateCopy the code



First of all, to deal with this problem, you need to have an Apple developer account, either personal or corporate. I bought a personal developer account for $99 and needed a CER certificate to generate p12 files

Specific can reference links: segmentfault.com/a/119000001…

Okay, now that you’ve wrapped up a desktop application that updates automatically, isn’t that a sense of accomplishment?

However, the problem, again, packaged applications can not be copied and pasted, what to do?

Solution:

1. Install the electron – localshortcut

yarn add electron-localshortcutCopy the code

2. Add the following code to createWindow in the main process file:

  if (process.platform === 'darwin') {    Menu.setApplicationMenu(Menu.buildFromTemplate([]));    const contents = mainWindow.webContents;    const localShortcut = require('electron-localshortcut');    localShortcut.register(mainWindow, 'CommandOrControl+A', () => {      contents.selectAll();    });    localShortcut.register(mainWindow, 'CommandOrControl+C', () => {      contents.copy();    });    localShortcut.register(mainWindow, 'CommandOrControl+V', () => {      contents.paste();    });  }Copy the code

Finally, attach the corresponding Git address

refer git address: https://github.com/catherine201/electron-example.git


Thanks for reading ~ ~