In the last issue, we completed a full update of electron. In this issue, we show how to modify only some files to achieve incremental updates.

asar

After the electron software is installed, right-click on the location of the file and go to the resources directory (MAC display package contents). You can see the app.asar file. This file is the main service file of the electron program, which is not an encrypted file but actually a compressed file. We can unpack this file using the NPM package

NPM install -g asar asar extract app.asar./ NPM install -g asar asar extract app.asar./Copy the code

When I decompressed it, it was actually what was in Dist_electron/Bundled, and if we made changes to things in the rendering process, we didn’t have to do a complete packaging update, just replace JS, HTML, CSS, and our pages, so we only had to update a few million files. The advantage of incremental updates is that you don’t have to download an entire new package.

If you set asar:true, then app.asar will not be replaced when the software starts (in win) and will be used by the software. So this scheme must be replaced by ordinary is not to go, the following I introduce several schemes for you to look up.

7z-Asar7z

I’m also going to provide a plugin for 7z, so that 7Z can also open asAR,link, if your 7z is installed on drive C, putAsar.64.dll(64-bit system) intoC:\Program Files\7-Zip\Formats\In,FormatsIf not, create your own.

In scenario 1, asar:false

Asar :false (builderOptions in vue.config.js). App.asar :false (builderOptions in vue.config.js). It is an app folder, and this folder can be directly replaced, so there is no problem that can not be replaced.

In short, set asAR :false, package, go to the green package dist_electron/ WIN-Ia32-Unpacked/Resources (Win32), compress the app file into app.zip and place it on the server. The rendering process detects the incremental update and notifies the main process. The main process downloads app.zip, decompresses and replaces it.

  • Advantages: Simple and brutal.
  • Disadvantages: Slow installation and full update, main process exposed, replacement is a whole download replacement.

The steps of this programme are similar to that of programme two, and the specific approach can be referenced in the way of programme two.

App.asar. Unpacked + app.asar

App.asar. Unpacked is quite common. Due to the limitations of app.asar, for example, the file is only readable, and some node commands cannot be used, we often use this in some third-party database or DLL files. In a nutshell, you can remove the restrictions on app.asar by placing the files that should be in app.asar in the same directory as app.asar. Unpacked.

Since app.asar does not move, we can throw the changed files to app.asar. Unpacked. The main process and some unchanged things are still put in app.asar, and the incremental update will replace app.asar.

  • Advantages: you can keep files such as the main process JS inapp.asar, only replace the render process file.
  • Disadvantages: Since the main js process does not move, the version number of the environment variable injected by the main process does not change, which means that the version number obtained by the main process using the environment variable after the update is not the updated version number (can be taken from the rendering process).

Implementation steps:

1. Set the app. Asar. Unpacked

Let’s first set up which files we want to replace, bundle it up as dist_electron/ Bundled, and then use the electron builder to bundle it up as our electron file.

Vue.config. Js builderOptions extraResources: [{from: "dist_electron/ Bundled ", to: "app.asar. Unpacked ", filter: [ "!**/icons", "!**/preload.js", "!**/node_modules", "!**/background.js" ] }], files: [ "**/icons/*", "**/preload.js", "**/node_modules/**/*", "**/background.js" ],Copy the code

ExtraResources is to set up the things in app.asar. Unpacked, files is to set up the things in app.asar, here means we made dist_electron/bundled in addition to the ICONS, App.asar and other files such as background.js are put in app.asar, and the rest are put in app.asar.unpacked, and pack to see if app.asar.unpacked is what we want.

2. Build the incremental ZIP

Now we have app.asar.unpacked, but it is impossible to manually compress app.asar.unpacked every time we go into the no-install package. It is too troublesome. Adm-zip handles zip packages, fs-extra is an extension of FS, handles files

npm i adm-zip
npm i fs-extra
Copy the code

The electron- Builder provides the afterPack for the wrapped hook

Js's builderOptions add afterPack: './ afterpack.js ',./ afterpack.js: const path = require('path') const AdmZip = require('adm-zip') exports.default = async function(context) { let targetPath if(context.packager.platform.nodeName === 'darwin') { targetPath = path.join(context.appOutDir, `${context.packager.appInfo.productName}.app/Contents/Resources`) } else { targetPath = path.join(context.appOutDir, './resources') } const unpacked = path.join(targetPath, './app.asar.unpacked') var zip = new AdmZip() zip.addLocalFolder(unpacked) zip.writeZip(path.join(context.outDir, 'unpacked.zip')) }Copy the code

The dist_electron directory will generate an unpacked. Zip. This is our delta package.

3. Modify the load policy

As we mentioned in the window launch section, the HTML of our rendering process is loaded through the app:// protocol, which used to be the root directory of app.asar. In this case, the rendering process file is moved out of app.asar. So we need to change this to app.asar.unpacked as the root directory.

// import {createProtocol} from 'viee-cli-plugin-electron -builder/lib' // Modify readFile(path.join(__dirName, pathName), where you can see that this protocol reads files under __dirName (' app.asar '), Create createprotocol. js import {protocol} from 'electron' import * as path from 'path' import by passing in a path to replace __dirname { readFile } from 'fs' import { URL } from 'url' export default (scheme, serverPath = __dirname) => { protocol.registerBufferProtocol( scheme, (request, respond) => { let pathName = new URL(request.url).pathname pathName = decodeURI(pathName) // Needed in case URL contains  spaces readFile(path.join(serverPath, pathName), (error, data) => { if (error) { console.error( `Failed to read ${pathName} on ${scheme} protocol`, error ) } const extension = path.extname(pathName).toLowerCase() let mimeType = '' if (extension === '.js') { mimeType =  'text/javascript' } else if (extension === '.html') { mimeType = 'text/html' } else if (extension === '.css') { mimeType = 'text/css' } else if (extension === '.svg' || extension === '.svgz') { mimeType = 'image/svg+xml' } else if (extension === '.json') { mimeType = 'application/json' } else if (extension === '.wasm') { mimeType = 'application/wasm' } respond({ mimeType, data }) }) }, (error) => { if (error) { console.error(`Failed to register ${scheme} protocol`, error) } } ) }Copy the code

The main process introduces our modified createProtocol.js

import createProtocol from './services/createProtocol' const resources = process.resourcesPath Change createProtocol('app') to createProtocol('app', path.join(resources, './app.asar.unpacked'))Copy the code

You can now load the file under app.asar.unpacked using the app:// protocol. If you load the file directly using the file:// protocol, the page will load properly. We start the rendering process with incremental update logic.

4. Analog interface

Here is not what to say, and on time as a full quantity update, don’t understand can look at the previous content, using HTTP server – simulation interface returns, modified. Env. Dev for hundreds, packaged to generate unpacked. Zip, into the server directory

{ "code": 200, "success": true, "data": { "forceUpdate": false, "fullUpdate": false, "upDateUrl": "Http://127.0.0.1:4000/unpacked.zip", "restart" : false, "message" : "I want to upgrade into hundreds", "version" : "hundreds"}}Copy the code

5. The render process is incrementally updated

The page logic here is similar to the last full update, we detect that the update sends an update to the main process using Win-increment:

<template> <div class="increment"> <div class="version"> {{config.vue_app_version}}</div> <a-button type="primary" @click=" true "> </a-button> </div> </template> <script> import cfg from '@/config' import update from '@/utils/update' import { defineComponent, getCurrentInstance } from 'vue' export default defineComponent({ setup() { const { proxy } = getCurrentInstance() const config = cfg const api = proxy.$api const message = proxy.$message function upDateClick(isClick) { api('http://localhost:4000/index.json', {}, { method: 'get' }).then(res => { console.log(res) if (cfg.NODE_ENV ! == 'development') { update(config.VUE_APP_VERSION, res).then(() => { if (! res.fullUpdate) { window.ipcRenderer.invoke('win-increment', Res)}}).catch(err => {if (err.code === 0) {isClick && messg.success (' already updated ')}})} else { Message.success (' Please update in a packaged environment ')}})} return {config, upDateClick}}) </script>Copy the code

6. Main process processing

Ipcmain.js add import increment from '.. /utils/increment' ipcMain.handle('win-increment', (_, data) => { increment(data) })Copy the code

Increment update process incre.js, download the increment package through upDateUrl, after the download is complete, we first rename the original app.asar. unPacked backup, if something goes wrong, we can restore it, and then decompress the download. Once this is done you can reload the page using reloadIgnoringCache, or you can restart the app using app.relaunch()

import downloadFile from './downloadFile' import global from '.. /config/global' import { app } from 'electron' const path = require('path') const fse = require('fs-extra') const AdmZip  = require('adm-zip') export default (data) => { const resourcesPath = process.resourcesPath const unpackedPath = path.join(resourcesPath, './app.asar.unpacked') downloadFile({ url: data.upDateUrl, targetPath: resourcesPath }).then(async (filePath) => { backups(unpackedPath) const zip = new AdmZip(filePath) zip.extractAllToAsync(unpackedPath, true, (err) => { if (err) { console.error(err) reduction(unpackedPath) return } fse.removeSync(filePath) if (data.restart) { reLoad(true) } else { reLoad(false) } }) }).catch(err => { console.log(err) }) } function backups(targetPath) { if (fse.pathexistsSync (targetPath + '.back')) {// Delete the old backup fse.removesync (targetPath + '.back')} if (fse.pathExistsSync(targetPath)) { fse.moveSync(targetPath, }} function reduction(targetPath) {if (targetPath + '.back') { fse.moveSync(targetPath + '.back', targetPath) } reLoad(false) } function reLoad(close) { if (close) { app.relaunch() app.exit(0) } else { global.sharedObject.win.webContents.reloadIgnoringCache() } }Copy the code

The encapsulated downloadFile downloadfile.js

const request = require('request') const fs = require('fs') const fse = require('fs-extra') const path = require('path')  function download(url, targetPath, cb = () => { }) { let status const req = request({ method: 'GET', uri: encodeURI(url) }) try { const stream = fs.createWriteStream(targetPath) let len = 0 let cur = 0 req.pipe(stream) req.on('response', (data) => { len = parseInt(data.headers['content-length']) }) req.on('data', (chunk) => { cur += chunk.length const progress = (100 * cur / len).toFixed(2) status = 'progressing' cb(status, progress) }) req.on('end', function () { if (req.response.statusCode === 200) { if (len === cur) { console.log(targetPath + ' Download complete ') Status = 'completed' cb(status, 100)} else {stream.end() removeFile(targetPath) status = 'error' cb(status, ' }} else {stream.end() removeFile(targetPath) status = 'error' cb(status, req.response.statusMessage) } }) req.on('error', (e) => { stream.end() removeFile(targetPath) if (len ! == cur) {status = 'error' cb(status, 'network fluctuation, } else {status = 'error' cb(status, e) } }) } catch (error) { console.log(error) } } function removeFile(targetPath) { try { fse.removeSync(targetPath) } catch (error) { console.log(error) } } export default async function downloadFile({ url, targetPath, folder = './' }, cb = () => { }) { if (! targetPath || ! url) { throw new Error('targetPath or url is nofind') } try { await fse.ensureDirSync(path.join(targetPath, folder)) } catch (error) { throw new Error(error) } return new Promise((resolve, reject) => { const name = url.split('/').pop() const filePath = path.join(targetPath, folder, name) download(url, filePath, (status, result) => { if (status === 'completed') { resolve(filePath) } if (status === 'error') { reject(result) } if (status ===  'progressing') { cb && cb(result) } }) }) }Copy the code

The basic logic of the incremental update is now complete. If you are using solution 1, you can also refer to the process and click on the render process’s detect update to see if the version becomeshundredsThere is no

Solution defect treatment

As mentioned earlier, the disadvantage of this solution is that the environment variables in the main process do not change, so we get the version number in the main process by process.env.vue_app_version. Our rendering process is repackaged, so its environment variables are accurate, and we can send configuration information from the rendering process to the main process when the page loads.

The renderer App. Vue: import CFG from '@ / config window. IpcRenderer. Invoke (' win - envConfig, CFG) global. Js: Global. EnvConfig = {} main ipcmain.js: import global from '.. /config/global' ipcMain.handle('win-envConfig', (_, data) => { global.envConfig = data })Copy the code

Instead of using process.env.vue_app_version to get the version number, use global.config.vue_app_version to get the version number, and try making a 0.0.2 package again.

added

  • This is just a simple incremental update logic, if you want a download progress ah, you can do it yourself
  • Generally speaking, this kind of incremental update package will be uploaded when the address will be saved to the database, you can do some security processing, such as the md5 or SHA attached to the file when saving, and then after the incremental update download is completed, the local check whether the consistency of the decompression, to ensure the accuracy of the file.
  • Extract and, of course, failure processing, if we have incremental updating package damage, although we have a backup, but restart or update will pull updates, if use the restart update, an infinite loop, this could be a version update restart records, after more than how many times, no longer to deal with this version of the package.

And of course there are other ways to do incremental update, and I’ve done a lot of that in the first video, so we’ll pick up the other ones in the next video.

This series of updates can only be arranged on weekends and after work, the update of more content will be slow, hope to help you, please more star or like collection support

This article address: xuxin123.com/electron/in… This post is on Github