[toc]

background

Pick up where you left off

When developing the electron desktop, there will be all kinds of pits, just to summarize.

  • Anti-virus software damage check
  • Preventing debugging
  • Client crash report
  • Improve the client startup speed
  • Performance monitoring analysis
  • Lazy loading module
  • The scrollbar style is uniform
  • BrowserWindow error listening
  • The browserWindow A TAB opens the default browser
  • -jquery, requirejs, meteor, AngularJS cannot be used in electron-.
  • electron-bridge
  • The proxy Settings
  • System Version Comparison (MAC)
  • Multi-window management
  • Similar to vscode seamless upgrade installation

Anti-virus software damage check

For some internal core files, you can use the whitelist mechanism to check whether the file exists. If not, the software is damaged and you can exit directly.

const getBinaryFileCheckList = () = >{
    const dir = [];
    // The network-interface package is required.
    const network = require.resolve("network-interface/package"),

    dir.push(network);
    
    return dir;
}

constbinaryFileCheckList = getBinaryFileCheckList(); <! - check - >for (let e = 0; e < binaryFileCheckList.length; e++) {
    const n = binaryFileCheckList[e];

    if(! fs.existsSync(n)) { dialog.showErrorBox("Startup failed"."Application file is damaged, possibly caused by anti-virus software, please download and install again.");
        / / exit directly
        electronApp.exit(1);
        break}}Copy the code

Preventing debugging

Check argv for chrome debugging keywords such as inspect or debugging.

const runWithDebug = process.argv.find(e= > e.includes("--inspect") || e.includes("--inspect-brk") || e.includes("--remote-debugging-port"));

if(runWithDebug){
    // Exit directly.
    electronApp.quit()
}

Copy the code

Client crash report

Third-party plug-ins can be used to assist with client-side crash reporting

@sentry/electron

Chinese document

www.yuque.com/lizhiyao/dx…

Improve the client startup speed

There are several ways to speed up client startup.

Use V8 to cache data

Electorn uses V8 to run JS, which requires parsing and compilation before executing the code. The parsing and compilation processes are time-consuming and often lead to performance bottlenecks. The V8 cache function can cache the compiled bytecode, saving the time of next parsing and compilation.

Compile the code for the plug-in using the V8-compile-cache

V8-compile-cache is very simple to use. Add a line of code to the code that needs to be cached:

require('v8-compile-cache')

Copy the code

By default, v8-compile-cache is cached in the temporary folder

/v8-compile-cache-

. After the computer restarts, the file will be deleted.

()>

If you want caching to be permanent, you can specify the cache folder using the environment variable process.env.v8_compile_cache_cache_dir to avoid deletion after a computer restarts. In addition, if you want different versions of your project to correspond to different caches, you can include the code version number (or other unique identifier) in the folder name to ensure that the caches and project versions correspond exactly. Of course, this also means multiple caches for multiple versions of the project. In order not to take up too much disk space, we need to remove other versions of the cache when the program exits.

Performance monitoring analysis

Main process, you can use V8-inspect-profiler for performance monitoring. The resulting.cpuProfile file can be analyzed using the Javascript Profiler on DevTools. If a child process is started with a fork, for example, it can be monitored in the same way, with different monitoring ports.

v8-inspect-profiler

Set the startup command, add the parameter –inspect=${port}, set the v8 debug port for the main process.

/ / package. Json {" name ":" test ", "version" : "1.0.0", "main" : ". The main js ", "devDependencies" : {" electron ": "9.2.1"}, "scripts" : {" start ":" electron., inspect = 5222 "}, "dependencies" : {" v8 - inspect - profiler ":" ^ 0.0.20 "}}Copy the code

Lazy loading module

Some of the project’s dependency modules are required only when a particular function is triggered. Therefore, it is not necessary to load immediately when the application is started; it can be loaded when the method is called.

Before optimization

// import module const XXX = require(' XXX '); export function share() { ... // execute dependent method XXX ()}Copy the code

The optimized

Export function share() {const XXX = require(' XXX '); . // execute dependent method XXX ()}Copy the code

The scrollbar style is uniform

For Windows and macOs, the default scroll width is different

function getScrollbarWidth() {
    const div = document.createElement('div');
    div.style.visibility = 'hidden';
    div.style.width = '100px';
    document.body.appendChild(div);
    const offsetWidth = div.offsetWidth;
    div.style.overflow = 'scroll';
    const childDiv = document.createElement('div');
    childDiv.style.width = '100%';
    div.appendChild(childDiv);
    const childOffsetWidth = childDiv.offsetWidth;
    div.parentNode.removeChild(div);
    return offsetWidth - childOffsetWidth;
}

Copy the code

Then set it differently depending on getScrollbarWidth.

Such as:

<! --> webkit-scrollbar-thumb{background-color:rgba(180.180.180.0.2);
     border-radius: 8px;
}

::-webkit-scrollbar-thumb:hover{
    background-color: rgba(180.180.180.0.5); } <! --> ::-webkit-scrollbar-track {border-radius:8px; } <! --> ::-webkit-scrollbar{width:8px;
    height: 8px;
}

Copy the code
document.onreadystatechange=(() = >{
 if("interactive"= = =document.readyState){
    // Process logic}})Copy the code

BrowserWindow error listening

Listen for unhandledrejection and error events

error

window.addEventListener('error'.(error) = >{

    const message = {
        message: error.message,
        source: error.source,
        lineno: error.lineno,
        colno: error.colno,
        stack: error.error && error.error.stack,
        href: window.location.href
    };
    
    // Send to the main process via ipcRender for logging.
    ipcRenderer.send("weblog", n)
},false)
Copy the code

unhandledrejection

window.addEventListener('unhandledrejection'.(error) = >{
    if(! error.reason){return;   
    }
    
    const message = {
           message: error.reason.message,
            stack: error.reason.stack,
            href: window.location.href } <! -- Sent via ipcRender to the main process for logging. --> ipcRenderer.send("weblog", n)

},false)
Copy the code

The browserWindow A TAB opens the default browser

In business, there will be an A tag on the browserWindow page. In this case, if you are in the electron container, you need to intercept it and open it through the default browser.

document.addEventListener('click'.(event) = >{
    const target = event.target;
    if(target.nodeName === 'A') {if(event.defaultPrevented){
            return;
        }
        if(location.hostname){
            event.preventDefault();
        }
        
        if(target.href){ shell.openExternal(target.href); }}},false);

Copy the code

Expose a global method for opening a browser

window.openExternalLink = ((r)=>{

    shell.openExternal(r)
});
Copy the code

-jquery, requirejs, meteor, AngularJS cannot be used in electron-.

Because Electron introduces Node.js into the runtime environment, there are a few extra variables in the DOM, such as Module, exports, and require. This causes many libraries to fail because they also need to add variables of the same name to the runtime environment.

Two ways,

  1. One way is through configurationwebPreferences.nodeIntegrationfalseBy disabling Node.js
  2. Through the top of the header inside electron-bridge.jsdelete window.require;.delete window.exports;.delete window.module;way
Const {BrowserWindow} = require('electron') const win = new BrowserWindow(format @@webPreferences: { nodeIntegration: false } }) win.show()Copy the code
<head>
<script>
window.nodeRequire = require;
delete window.require;
delete window.exports;
delete window.module;
</script>
<script type="text/javascript" src="jquery.js"></script>
</head>

Copy the code

electron-bridge

Inject the extra ELECTRON API into browserWindow via the Bridge

const {ipcRenderer: ipcRenderer, shell: shell, remote: remote, clipboard: clipboard} = require("electron"), <! -- Parameters in process -->const processStaticValues = _.pick(process, ["arch"."argv"."argv0"."execArgv"."execPath"."helperExecPath"."platform"."type"."version"."versions"]);


module.exports = (() = > ({
    ipcRenderer: ipcRenderer, // ipc renderer
    shell: shell, // shell
    remote: remote, //
    clipboard: clipboard,
    process: {
        ...processStaticValues,
        hang: () = > {
            process.hang()
        },
        crash: () = > {
            process.crash()
        },
        cwd: () = > {
            process.cwd()
        }
    }
}));

Copy the code

The proxy Settings

For proxy Settings, there are generally two modes:

  • PAC
  • HTTP

PAC

Enter the address Protocol://IP:Port

HTTP

For HTTP mode, yes

  • HTTP
  • SOCKS4
  • SOCKS5

Input Protocol: / / IP: Port

System Version Comparison (MAC)

Semver is recommended.

Multi-window management

Microelectron Windows is recommended to support dynamic window creation.

address

Similar to vscode seamless upgrade installation

Run the following command to mount DMG to the /Volumes directory on the MAC. Delete the app under /Applications and copy the app under /Volumes to /Applications. To uninstall DMG; Restart the application. This method can achieve a similar effect of seamless update.

With the help of hdiutil,

There are six main steps:

  • which hdiutil
  • hdiutil eject [/Volumes/appDisplayName latestVersion]
  • hdiutil attach [latest Dmg Path]
  • mv [local App Path] [temp dir]
  • cp -R [latest app path] [local app path]
  • hdiutil eject [/Volumes/appDisplayName latestVersion]

which hdiutil

Check whether the hdiutil executable exists.

hdiutil eject [/Volumes/appDisplayName latestVersion]

Uninstall the files under [/Volumes/appDisplayName latestVersion].

hdiutil attach [latest Dmg Path]

Install the DMG file

mv [local App Path] [temp dir]

Move the old local app directory to the tempDir directory.

cp -R [latest app path] [local app path]

Copy all files in the latest App path file to the original app directory.

hdiutil eject [/Volumes/appDisplayName latestVersion]

Uninstall the [/Volumes/appDisplayName latestVersion] file again

At each step, if it works, it works.

Example code.

const path = require("path"); const os = require('os'); const {waitUntil, spawnAsync} = require('.. /.. /utils'); const {existsSync} = require('original-fs'); const getMacOSAppPath = () => { const sep = path.sep; const execPathList = process.execPath.split(sep); const index = execPathList.findIndex(t => 'Applications' === t); return execPathList.slice(0, index + 2).join(sep); }; module.exports = (async (app) => { const {appDisplayName} = app.config; const {latestVersion, latestDmgPath} = app.updateInfo; // const macOsAppPath = getMacOSAppPath(); // temp dir const tempDir = path.join(os.tmpdir(), String((new Date).getTime())); const appDisplayNameVolumesDir = path.join('/Volumes', `${appDisplayName} ${latestVersion}`); // const latestAppPath = path.join(appDisplayNameVolumesDir, `${appDisplayName}.app`); // step 1 which hdiutil // /usr/bin/hdiutil try { const hdiutilResult = await spawnAsync('which', ['hdiutil']); if (! hdiutilResult.includes('/bin/hdiutil')) { throw new Error('hdiutil not found'); } } catch (e) { app.logger.warn(e); return { success: false, type: 'dmg-install-failed' } } // step 2 hdiutil eject appDisplayNameVolumesDir try { await spawnAsync("hdiutil", ["eject", appDisplayNameVolumesDir]) } catch (e) { e.customMessage = '[InstallMacOSDmgError] step2 volume exists'; app.logger.warn(e); } finally { const result = await waitUntil(() => ! existsSync(latestAppPath), { ms: 300, retryTime: 5 }); if (! result) { app.logger.warn('[InstallMacOSDmgError] step2 volume exists'); return { success: false } } } //step 3 hdiutil attach latestDmgPath try { await spawnAsync('hdiutil', ['attach', latestDmgPath]) } catch (e) { e.customMessage = '[InstallMacOSDmgError] step3 hdiutil attach error'; app.logger.warn(e); } finally { const result = await waitUntil(() => ! existsSync(latestAppPath), { ms: 300, retryTime: 5 }); if (! result) { app.logger.warn('[InstallMacOSDmgError] step3 hdiutil attach fail'); return { success: false } } } // step 4 mv try { await spawnAsync('mv', [macOsAppPath, tempDir]); } catch (e) { e.customMessage = '[InstallMacOSDmgError] step4 mv to tmp path error'; app.logger.warn(e); } finally { const result = await waitUntil(() => ! existsSync(tempDir), { ms: 300, retryTime: 5 }); if (! result) { app.logger.warn('[InstallMacOSDmgError] step4 cp to tmp path fail'); return { success: false, type: "dmg-install-failed" } } } // step 5 try { await spawnAsync('cp', ['-R', latestAppPath, macOsAppPath]) } catch (e) { e.customMessage = '[InstallMacOSDmgError] step5 cp to app error'; app.logger.warn(e); } finally { const result = await waitUntil(() => ! existsSync(macOsAppPath), { ms: 300, retryTime: 5 }); if (! result) { app.logger.warn('[InstallMacOSDmgError] step5 cp to app fail'); await spawnAsync('mv', [tempDir, macOsAppPath]); return { success: false, type: "dmg-install-failed" } } } // step 6 try { await spawnAsync('hdiutil', ['eject', appDisplayNameVolumesDir]) } catch (e) { e.customMessage = '[InstallMacOSDmgError] step6 hdiutil eject fail'; app.logger.warn(e); } return { success: true } });Copy the code

The project address

To this end, I stripped out the functions required by each client used in the business system and created a new template to facilitate the development of the new business system.

Coding is not easy, welcome star

Github.com/bosscheng/e…

github